Vue2+vue3

时间:2025-04-22 06:58:53

vue核心

一、vue概念

V+是套构建用户界面的渐进式框架。与其他重量级框架不同的是,Vue采用自底向上增量开发的设计。Vue 的核心库只关注/图层,并且非常容易学习,非常容易与其它库或已有项目整合。另一方面,Vue 完全有能力驱动采用单文件组件和Vue生态系统支持的库开发的复杂单页应用单页面开发SPA

渐进式框架:可以理解为上手简单,入门简单,但是可以渐近提高框架等级
视图层:之前我们的express它是一个MVC的框架
SPA:称之为单页面开发,是目前开发的主流,当前阶段能够很好的实现SPA开发的框架主要有三个,分别是vue/react/angular

安装

1、安装dev tools

2、安装vue2,在node中安装

yarn add vue@2

使用

<script src="../js/"></script

二、初识Vue

想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象
demo容器里的代码依然符合html规范,只不过混入了一些特殊的Vue语法
demo容器里的代码被称为【Vue模板】
Vue实例和容器是一一对应的
真实开发中只有一个Vue实例,并且会配合着组件一起使用
{{xxx}}是Vue的语法:插值表达式,{{xxx}}可以读取到data中的所有属性
一旦data中的数据发生改变,那么页面中用到该数据的地方也会自动更新(Vue实现的响应式)

注意:vue实例接管的容器不能是body更不能接管html

当vue一旦接管某一个容器以后,这个容器里面所有的东西都可以通过vue来操作,如数据,事件,样式等

<!-- 引入vue -->
<script src="../js/"></script>
</head>
<body>
    <!-- 第一个容器 -->
    <div class="box1">
        <h1>你好{{name}}</h1>
    </div>
    <!-- 第二个容器 -->
    <div class="box2">
        <h1>你好{{()}},{{ () }}</h1>
    </div>
</body>

</html>
<script type="text/javascript">
    Vue.config.productionTip = false;

    // 第一个vue实例
    new Vue({
        el: '.box1',
        data: {
            name: '初始vue1'
        }
    })

    // 第二个vue实例
    new Vue({
        el: '.box2',
        data: {
            name: '初始vue2'
        }
    })

</script>

三、模板语法

1、插值语法{{}}

功能:用于解析标签体内容

语法:{{xxx}},xxx是js表达式,且可以直接读取到data中的所有属性

<div class="box">
    <!-- 插值语法 -->
    <h1>模板语法之:{{value}}</h1>
    <!-- 对象语法.的方式 -->
    <a href="">{{}}的年龄为{{}}</a>
</div>

<script src="../js/"></script>
<script>
    new Vue({
        el: ".box",
        data: {
            value: '插值语法',
            // data的属性的key对应的values是一个对象时
            prosion: {
                name: '张三',
                age: 18
            }
        }
    })
</script>

2、指令语法v-xx

功能:用于解析标签(包括:标签属性、标签体内容、绑定事件…)

语法:

如v-bind指令用于数据绑定:v-bind:href=“xxx” 或 简写为 :href=“xxx”,xxx同样要写js表达式,且可以直接读取到data中的所有属性

注意:[v-bind:] 可以简写为[:]

3、模板中的js表达式

正确表达式:

1、{{ number + 1 }}
2、{{ ok ? 'YES' : 'NO' }}
3、{{ ('').reverse().join('') }}
4、<div v-bind:id="'list-' + id"></div>

错误表达式:

<!-- 这是语句,不是表达式 -->
{{ var a = 1 }}

<!-- 流控制也不会生效,请使用三元表达式 -->
{{ if (ok) { return message } }}
<div class="box">
    <!-- 指令语法v-bind: -->
    <a v-bind:href='url'>百度一下小写</a>
    <a v-bind:href="()">百度一下大小</a>
    <!-- [v-bind:] 可以简写为[:] -->
    <a :href="()">百度一下大小</a>
</div>

<script src="../js/"></script>
<script>
  new Vue({
    el: ".box",
    data: {
      url: '',
    }
  })
</script>

四、数据绑定

1、单向数据绑定

单向绑定(v-bind):数据只能从data流向页面

当data的值改变时,v-bind绑定的值会跟着改变,但是当反过来时v-bind绑定的值改变时,data的值不会跟着改变

单向绑定<input type="text" v-bind:value="name">
单向绑定简写<input type="text" :value="name">

2、双向数据绑定

双向绑定(v-model):数据不仅能从data流向页面,还可以从页面流向data

当data的值改变时,v-model绑定的值会跟着改变,当反过来时v-bind绑定的值改变时,data的值也会跟着改变

注意:
1.双向绑定一般都应用在表单类元素上(如:input、select等)
-model:value 可以简写为 v-model,因为v-model默认收集的就是value值

双向绑定<input type="text" v-model:value="name">
双向绑定简写<input type="text" v-model="name">

使用v-model收集表单数据

收集表单数据:

  1. 若:text框,则v-model收集的是value值,用户输入的就是value值。

  2. 若:radio为,则v-model收集的是value值,且要==给标签配置value值==。

  3. 若:为checkbox

    1. 没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值)

    2. 配置input的value属性:

    v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值)

    v-model的初始值是数组,那么收集的的就是value组成的数组

v-model的三个修饰符:
  1. lazy:失去焦点再收集数据
  2. number:输入字符串转为有效的数字
  3. trim:输入首尾空格过滤
  <div class="box">
    <form @="submit">
      <!--.trim去除前后空格  -->
      用户名<input type="text" =""><br>
      密码:<input type="password" v-model=""><br>
      <!-- .number字符串装转为number-->
      年龄:<input type="number" =""><br>
      性别:
      男<input type="radio" name="sex" v-model="" value="boy"><input type="radio" name="sex" v-model="" value="girl"><br>
      爱好:
      <input type="checkbox" name="hobby" v-model="" value="sing">
      <input type="checkbox" name="hobby" v-model="" value="jump">
      <input type="checkbox" name="hobby" v-model="" value="rap"><br>

      所在地:
      <select name="" id="" v-model="">
        <option value="">--请选择--</option>
        <option value="北京">北京</option>
        <option value="上海">上海</option>
        <option value="深圳">深圳</option>
      </select><br>
      <!--.lazy失去焦点再更新值-->
      个人简介:<textarea cols="30" rows="10" =""></textarea>
      <br>
      阅读并接受用户协议:<input type="checkbox" v-model=""><br>
      <input type="submit">
    </form>
  </div>
</body>
<script src="../js/"></script>
<script>
  let vm = new Vue({
    el: '.box',
    data: {
      userInfo: {
        username: "",
        password: "",
        age: '',
        sex: "boy",
        hobby: [],
        address: "北京",
        introduce: "",
        agree: ""
      },
    },
    methods: {
      submit () {

        console.log(this.userInfo);

      }
    }
  })


</script>

五、el与data的两种写法

1、el的两种写法

①创建vue构造函数的实例时指定

new Vue({
    // 创建vue实例对象时指定el容器
    el: '.ul',
    // data为对象类型
    data: {
        value1: "1",
    }
})

②创建vue实例后通过实例对象的原型方法**$mount()**指定

  // 在vue实例上挂载dom容器对象
  vm2.$mount(".ul")

2、data的两种写法

①data为对象类型

new Vue({
    // 创建vue实例对象时指定el容器
    el: '.ul',
    // data为对象类型
    data: {
      value1: "1",
    }
  })
  console.log(vm);

②data为函数类型,返回值为对象类型

注意:在组件中,data必须使用函数式

let vm2 = new Vue({
    // data是一个函数返回值为一个对象
    data: function () {
        return {
            value1: "你好"
        }
    }
})

六、MVVM模型:

  1. M:模型(Model) :对应 data 中的数据

  2. V:视图(View) :模板

  3. VM:视图模型(ViewModel) : Vue 实例对象

    Model 层代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑;View 代表UI 组件,它负责将数据模型转化成UI 展现出来,ViewModel 是一个同步View 和 Model的对象。

    在MVVM架构下,View 和 Model 之间并没有直接的联系,而是通过ViewModel进行交互,Model 和 ViewModel 之间的交互是双向的, 因此View 数据的变化会同步到Model中,而Model 数据的变化也会立即反应到View 上。

    ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,而View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。

    详细参考: 和 MVVM 的小细节 - 知乎 ()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r4YAtIiu-1664507085274)(C:\Users\QuMing\Desktop\vue\资源\笔记图片\Snipaste_2022-08-22_19)]

对应代码的部分

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IXFVjiLt-1664507085275)(C:\Users\QuMing\Desktop\vue\资源\笔记图片\Vue数据代理.png)]

1、data中的属性都会出现在vue实例对象上,

2、vue实例对象上的所有属性及 原型上所有属性在vue模板中都可以使用

柒、数据代理

数据代理: 通过一个对象代理对另一个对象中属性的操作

1、Object.defineProperties的 get、set方法


let name = 'zs';//第三方数据
let age = 20;//第三方数据
let use = {}

Object.defineProperties(use, {
    name: {
           get: function () {
               return name//过去name属性时返回第三方name
           },
           set: function (value) {
               name = value//设置name属性时不是设置自身name而是设置第三方name的值,这样的话name的值就是动态变化的
           }
       },
       age: {
           get: function () {
               return age
           },
           set: function (value) {
               age = value
           }
       }
   })

2、数据代理,

//实际对象
let obj1 = {
    name: "张三"
}
//代理对象
let obj2 = {}

// 通过代理对象的get和set来操作实际的对象属性
Object.defineProperty(obj2, 'name', {
    get: function () {
        // 获取代理对象的属性时通过get拦截实际返回的是实际对象的属性
        return obj1.name;
    },

    set: function (value) {
        // 修改代理对象的属性时通过get拦截实际修改的是实际对象的属性
        obj1.name = value;
    }
})

console.log(obj2.name);//张三
obj2.name = "黎明"
console.log(obj2.name);//黎明
obj1.name = "zs"
console.log(obj2.name);//zs

vue中的数据代理

  • 在创建vue实例对象时,vue将data中的数据全部复制一份保存在.实例对象的_data对象中,_

  • 然后Vue给vue实例中添加_data中同名的属性并给属性设置【访问器属性】的getter和setter方法,_

  • 并且setter方法中会增加一个方法,这个方法会让vue重新解析模板

  • 当实例获取_data中同名属性时,会调用访问器属性的getter方法,但返回是_data中同名的值

  • 当实例修改_data中同名属性时,会调用访问器属性的setter方法,但修改的是_data中同名的值。

  • 调用setter方法的同时会重新解析模板(解析模板的后续:生成新的虚拟 DOM----->新旧DOM 对比 -----> 更新页面)

  • 这样的话当data中数据变化时,_ data中会变化,vm中也就跟着变化,

    提示:我们知道vue实例对象上的所有属性及 原型上所有属性在vue模板中都可以使用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4c2sgG8h-1664507085276)(C:\Users\QuMing\Desktop\vue\资源\笔记图片\Snipaste_2022-08-22_19)]

注意事项????不被响应的数据

在vue2中,数组中的单元进行整体替换是不会被响应的

<body>
  <div class="box">
    姓名:<span>{{arr[0].name}}</span>
    年龄:<span>{{arr[0].age}}</span>
  </div>
</body>
<script src="../js/"></script>
<script>
  let vm = new Vue({
    el: ".box",
    data: {
      // 当整体修改数组的单元时,数据不被被监事到变化,页面数据不会响应
      // 当把数组的下标为0 的元素整体替换时,数据不会被监视到,页面没有响应
      arr: [{ name: "张三", age: 18 }, { name: "王五", age: 20 }]
    },
  })
</script>

先在控制台修将数组下标为0的元素整体修改、

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FGSOsTwD-1664507085277)(C:\Users\QuMing\Desktop\vue\资源\笔记图片\Snipaste_2022-08-25_17)]

❓ 修改后:vm中的data数据并没有变化。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ArN0TQkF-1664507085277)(C:\Users\QuMing\Desktop\vue\资源\笔记图片\Snipaste_2022-08-25_17)]

解决方法

1、使用vm.$set

vm.$set(,0,{name:“zs”,age:19})

2使用入栈的方法,

({ name: ‘zs’, age: 19 })

Vue检测数据的原理

1、Vue监视数据的原理:

  • vue会监视data中所有层次的数据

2、如何监测对象中的数据?

通过setter实现监视,且要在new Vue时就传入要监测的数据。

  • 对象中后追加的属性,Vue默认不做响应式处理

  • 如需给后添加的属性做响应式,请使用如下API:

  • (target,propertyName/index,value) 或

  • vm.$set(target,propertyName/index,value)

3、如何监测数组中的数据?

通过包裹数组更新元素的方法实现,本质就是做了两件事:

  • 调用原生对应的方法对数组进行更新
  • 重新解析模板,进而更新页面

4、在Vue修改数组中的某个元素一定要用如下方法:
使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
() 或 vm.$set()

5、特别注意:() 和 vm.$set() 不能给vm 或 vm的根数据对象 添加属性!!!

八、事件处理

事件绑定语法:

​ 1、v-on:事件名=‘‘方法名’’

​ 2、简写: @事件名=‘‘方法名’’

注意:

1、调用函数时不传参数,默认会传一个event对象

2、调用函数传参数时,如果还要使用event对象,需要要添加** e v e n t ∗ ∗ 实参, event**实参, event实参,even就是event对象

3、事件回调函数中的this指向是vm 或 组件实例对象

4、事件回调函数需要配置在methods对象中最终会在vm上

5、如果想拿到data里面的数据,应该使用**this. d a t a ∗ ∗ ,但是可以简写,去掉 data**,但是可以简写,去掉 data,但是可以简写,去掉data也可以明出处

鼠标事件

<body>
    <div class="box">
        <button v-on:click="fun(1,$event)">按钮1</button>
        <button @click="fun(1,$event)">按钮2</button>
    </div>
</body>

<script src="../js/"></script>
<script>
    let vm = new Vue({
        el: ".box",
        data: {
            value: "小白"
        },
        // 事件的回调函数都写在methods中
        methods: {
            fun (a, e) {
                console.log(a, e);
                // alert(`初始vue的${this.$}`)
                alert(`初始vue的${this.value}`)
            }
        }
    })
</script>

键盘事件

<body>
    <div class="box">
        <input type="text" @="fun">
    </div>
</body>
<script src="../js/"></script>
<script>
    new Vue({
        el: ".box",
        data: {},
        methods: {
            fun () {
                console.log(event.target.value);
            }
        }
    })
</script>

九、事件修饰符

小技巧:修饰符可以连续写

功能性修饰符

  1. stop:停止事件冒泡
  2. prevent:阻止标签的默认行为
  3. once:事件只触发一次
  4. self:当前事件只有自己触发时才执行回调
  5. capture:事件捕获阶段执行
  6. passive:事件默认行为立即执行,无需等待回调
  7. native修饰符,给组件绑定事件时需要加native修饰符

针对于鼠标的

.left
.right
.middle

针对于键盘的修饰符

.enter
.tab(必须配合keydown使用)
.delete (捕获“删除”和“退格”键)
.esc
.space
.up
.down
.left
.right

针对于系统的修饰符(用法特殊)

  1. 配合keyup使用:按下修饰符键的同时,再按下其他键。随后释放其他键,事件才会触发。
  2. 配合keydown使用:正常触发事件。

自定义键名:.自定义键名 = 键码

.ctrl
.alt
.shift
.meta

十、计算属性computed

1、定义:要用的属性不存在,要通过已有属性计算得来

2、原理:底层借助了方法提供的getter和setter(计算属性默认只有 getter,不过在需要时你也可以提供一个 setter:)

3、get函数什么时候执行?

  • 初次读取计算属性时会执行一次
  • 当计算属性==所依赖的属性发生改变时会被再次调用==,其他时候获取name时都是从缓存中读取的,增加效率

4、set函数什么时候执行?

  • 当name属性改变时会调用set方法

5、优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便

6、注意:

  • 计算属性最终会出现在vm上,直接读取使用即可
  • 被vue管理的函数不要写箭头函数。
  • 如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变
<body>
  <div class="box"><input type="text" v-model:value="frilstername"><br><input type="text" v-model:value="lastername"><br>
    全名<span>{{name}}</span>
  </div>
</body>
<script>
  let vm = new Vue({
    el: ".box",
    data: {
      frilstername: "张",
      lastername: "三"
    },
    methods: {
    },
    computed: {
      name: {
        // 当读取name属性时,get就会被调用,
        // 注意:初次读取name属性时和所依赖的数据发生变化时才会调用get
        // 其他获取name时都是从缓存中读取的,增加效率
        get () {
          console.log("get方法被调用了");
          // get方法中的this执行vm
          return this.frilstername + this.lastername
        },
        // 当name属性改变时会调用set方法
        set (value) {
          console.log("get方法被调用了");
          // get方法中的this指向vm
          this.frilstername = value[0]
          this.lastername = value[1]
        }
      }
    }
  })
</script>

计算属性简写形式

计算属性简写前提:当所用的计算属性只考虑读取,不考虑修改时,才可以简写

<body>
  <div class="box"><input type="text" v-model:value="frilstername"><br><input type="text" v-model:value="lastername"><br>
    全名<span>{{username}}</span>
  </div>
</body>

<script>
  let vm = new Vue({
    el: ".box",
    data: {
      frilstername: "张",
      lastername: "三"
    },
    computed: {
      // 当所用的计算属性只是用来获取,不会修改时,才可以简写,将计算属性的set方法省略,
      // 计算属性可以不写成对象形式,计算属性直接是一个函数 相当于get方法
      // username: function () {return  + },


      // 再进一步简写:无构造函数的函数,不要function
      username () { return this.frilstername + this.lastername }
    }
  })
</script>

十一、属性监视watch

  • 当被监视的属性变化时, 回调函数自动调用, 进行相关操作
  • 监视的属性必须存在,才能进行监视
  • 监视的两种写法:
    • (1).new Vue时传入watch配置
    • (2).通过vm.$watch监视

???? 注意:watch默认不监测对象内部值的改变(只监视一层),要想深度监视需要配置开启深度监视

第一种监视方法

<body>
  <div class="box">
    姓名:<samp>{{}}</samp>
    年龄:<span>{{}}</span>
    <span>{{use}}</span>
    <button @click="fun">切换</button>
  </div>
</body>

<script src="../js/"></script>
<script>
  new Vue({
    el: ".box",
    data: {
      user: { name: "zs", age: 18 }
    },
    methods: {
      fun () {
        // this._data. = "王五"
        this.user = { name: "wz", age: 20 }
      }
    },
    computed: {
      use () {
        return this.user.name + this.user.age
      }
    },
    watch: {
      // 监视user属性,并不是监视user属性的内容发生变化
      user: {

        // 1、handler():当user属性发生改变时,调用handler()方法
        // newvalue:被监视属性变化后的值,oldvalue:被监视属性变化前的值
        handler (newvalue, oldvalue) {
          console.log("监视到属性改变了", newvalue, oldvalue);
        },
        // 2、immediate:初始化时是否调用handler()方法
        immediate: true
      },

      // 监视计算属性
      use: {
        handler (newele, oldele) {
          console.log(newele, oldele);
        }
   }
    }
   })
  </script>

第二种方法

第二种方法使用vm原理对象的$watch()方法

vm.$watch( expOrFn, callback, [options] )

  // 第二种方法使用vm原理对象的$watch()方法
  vm.$watch("要监视的属性", {
    immediate: true,
    handler (newele, oldele) {}
  })

深度监视deep

备注:

(1).Vue自身可以监测对象内部值的改变,但Vue提供的watch默认不可以

(2).使用watch时根据数据的具体结构,决定是否采用深度监视

    watch: {
      user: {
        handler (newvalue, oldvalue) {
          console.log("监视到属性改变了", newvalue, oldvalue);
        },
        immediate: true,
        // 开启深度监视
        deep:true
      }
    }

单值监听

监听复杂属性的具体的某个属性值

 watch: {
      "": {
        handler (newele, oldele) {
          console.log(newele, oldele);
        }
      }
    }

watch简写

监视属性要想简写:只能写handler()配置项,不能写其他配置项。和计算属性简写一样。

   //第一种监视方法简写
	watch: {
      //监视属性要想简写:只能写handler()配置项,不能写其他配置项。和计算属性简写一样。 
      // function就相当于handler函数
      user: function (newvalue, oldvalue) {
        console.log("监视到属性改变了", newvalue, oldvalue);
      },

      // 继续简写:无构造函数的函数
      user (newvalue, oldvalue) {
        console.log("监视到属性改变了", newvalue, oldvalue);
      },
    }

  // 第二种方法使用vm原型对象的$watch()方法简写
  vm.$watch("user",function (newele, oldele) { })

十二、computed和watch的区别

computed是计算属性是个属性,需要有返回值,watch是监视属性值的变化,执行处理函数,不需要返回值,

computed能完成的功能,watch都可以完成
watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作

两个重要的小原则:

1.所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或 组件实例对象

2.所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数,这样this的指向才是vm 或 组件实例对象

十三、绑定样式

1、外联:绑定class样式三种写法

  1. 字符串写法: 适用于动态的只设置一个class样式
  2. 数组写法:适用于动态的一次设置多个class样式
  3. 对象写法:适用用于不确定用不用要设置的样式
<style>
   .a {
     width: 100px;
     height: 100px;
     border: 1px solid black;
   }

   .b {
     background-color: aqua;
   }

   .c {
     border-radius: 10px;
   }

   .d {
     box-shadow: black 5px 5px;
   }
 </style>
</head>

<body>
 <div class="box">
   <!-- 给d有基础样式的div加样式, -->
   <div class="a" v-bind:class="classStr"></div>
   <div class="a" v-bind:class="classAarry"></div>
   <div class="a" v-bind:class="classObj"></div>
 </div>
</body>
<script src="../js/"></script>

<script>
 let vm = new Vue({
   el: ".box",
   data: {
     // <!-- 字符串写法: 适用于动态的只设置一个class样式-->
     classStr: "b",
     // <!-- 数组写法:适用于动态的一次设置多个class样式 -->
     classAarry: ["b", "c", "d"],
     // <!-- 对象写法:适用用于不确定用不用要设置的样式 -->
     classObj: { b: false, c: false, d: false }
   }
 })
</script>

2、内联:绑定style两种写法

  1. 内联样式对象写法
  2. 内联样式数组写法
<body>
  <div class="box">
    <div v-bind:style="innerClassObj"></div>
    <div v-bind:style="innerClassObj"></div>
  </div>
</body>
<script src="../js/"></script>

<script>
  let vm = new Vue({
    el: ".box",
    data: {
      // 内联样式对象写法
      innerClassObj: { width: "100px", height: "100px", border: "1px solid" },
      // 内联样式数组写法
      innerClassArray: [{with:'100px'},{height:'100px'},{border:"1px solid"}]
    }
  })
</script>

十四、指令

已经学过的指令:

v-bind、v-model、v-on、

1、条件渲染

v-if
  • 语法:

(1).v-if=“表达式”

(2).v-else-if=“表达式”

(3).v-else=“表达式”

  • 适用于:切换频率较低的场景

  • 特点:不展示的DOM元素直接被移除

  • 注意:v-if可以和:v-else-if、v-else一起使用,但要求结构不能被打断

<body>
  <div class="box">
    <map v-if="item==0">{{item}}号</map>
    <map v-else-if="item==1">{{item}}号</map>
    <map v-else-if="item==2">{{item}}号</map>
    <map v-else>其他{{item}}号</map>
    <button @click="click">增加</button>
  </div>
</body>
<script src="../js/"></script>
<script>
  new Vue({
    el: ".box",
    data: {
      item: 0
    }, methods: {
      click () {
        this.item++
      }
    }
  })
</script>
v-show

语法:v-show=“表达式”
适用于:切换频率较高的场景
特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉(display:none)

备注:

  • 使用v-if的时,元素可能无法获取到,而使用v-show一定可以获取到
  • v-if 是实打实地改变dom元素,v-show 是隐藏或显示dom元素
    <map v-show="item>3">show元素的展示</map>

2、列表渲染

v-for指令
  • 语法:v-for=“(item, index) in xxx” :key=“yyy”
  • 用于展示列表数据:用什么标签展示数据就将v-for写在谁身上
  • 可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)

<body>
  <div class="box">

    <ul>
      <!-- 用什么标签展示数据就将v-for写在谁身上 -->
      <!-- 1、遍历数组 ,
        item:数组中的元素,
        index:数组的索引,
        arr:数组对象,
        key:当前标签的唯一标识-->
      <li v-for="(item,index) in arr" v-bind:key="">{{item}}---{{index}}</li>
    </ul>

    <ul>
      <!-- 2、遍历对象 ,
        value:数组中的元素,
        key:数组的索引,
        obj:数组对象,
        key:当前标签的唯一标识-->
      <li v-for="(value,key) in obj" v-bind:key="key">{{key}}---{{value}}</li>
    </ul>


    <ul>
      <!-- 3、遍历字符串(很少用) ,
        value:数组中的元素,
        key:数组的索引,
        obj:数组对象,
        key:当前标签的唯一标识-->
      <li v-for="(char,index) in str" v-bind:key="index">{{index}}---{{char}}</li>
    </ul>


    <ul>
      <!-- 4、遍历数字(很少用) ,
        number:默认从1开,
        index:数字的索引,
        5:遍历的数字,
        key:当前标签的唯一标识-->
      <li v-for="(number,index) in 5" v-bind:key="index">{{index}}---{{number}}</li>
    </ul>

  </div>
</body>
<script src="../js/"></script>
<script>
  let vm = new Vue({
    el: '.box',
    data: {
      arr: [
        { id: 1, name: "zs1", age: 17 },
        { id: 2, name: "zs2", age: 18 },
        { id: 3, name: "zs3", age: 19 },
        { id: 4, name: "zs4", age: 20 },
      ],

      obj: { name: "王五", age: 18, height: 180 },
      str: "枫叶荻花秋瑟瑟"
    },
  })


</script>
key的原理

key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】, 随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:

1、旧虚拟DOM中找到了与新虚拟DOM相同的key:
①.若虚拟DOM中内容没变, 直接使用之前的真实DOM!
②.若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM。
2、旧虚拟DOM中未找到与新虚拟DOM相同的key
创建新的真实DOM,随后渲染到到页面。

虚拟dom

虚拟 DOM (Virtual DOM,简称 VDOM) 是一种编程概念,意为将目标所需的 UI 通过数据结构“虚拟”地表示出来,保存在内存中,然后将真实的 DOM 与之保持同步

1、虚拟DOM的本质

从本质上来说,Virtual DOM 是一个 JavaScript 对象,通过对象的方式来表示 DOM 结构。将页面(真实dom树)抽象为 JS 对象的形式(虚拟dom树),配合不同的渲染工具,使跨平台渲染成为可能

相当于在js与DOM之间做了一个缓存,利用patch(diff算法)对比新旧虚拟DOM记录到一个对象中按需更新, 最后创建真实、的DOM

2、vue虚拟dom原理流程

  1. 编译:Vue 模板被编译为了渲染函数:即用来返回虚拟 DOM 树的函数。这一步骤可以通过构建步骤提前完成,也可以通过使用运行时编译器即时完成。
  2. 挂载:运行时渲染器调用渲染函数,遍历返回的虚拟 DOM 树,并基于它创建实际的 DOM 节点。这一步会作为,响应式副作用执行、,因此它会追踪其中所用到的所有响应式依赖。
  3. 更新:当一个依赖发生变化后,副作用会重新运行,这时候会创建一个更新后的虚拟 DOM 树。运行时渲染器遍历这棵新树,将它与旧树进行比较,然后将必要的更新应用到真实 DOM 上去。

vue编译模板(template),生成渲染函数(render),运行时通过渲染器调用渲染函数,可以得到一个虚拟节点树(vue中每个组件都是一个虚拟节点树,),第一次挂载(虚拟DOM生成真实DOM)时,不会进行新旧虚拟DOM比较,然后每当数据变化时,会创建一个新的虚拟DOM树,会进行新旧虚拟DOM比较,通过diff算法比较虚拟节点的异同,相同的虚拟节点不会转为真实dom,不同的虚拟节点会转为真实DOM,然后和旧的真实dom进行拼接,把相同的部分拿过来。

3、作用:

1、首次渲染大量的 DOM 时,由于多了一层虚拟 DOM 的计算,会比 innerHTML 插入慢。,但是 Virtual DOM的优势不在于单次的操作,而是在大量、频繁的数据更新下,能够对视图进行合理、高效的更新

尤雨溪在社区论坛中说道:框架给你的保证是,你不需要手动优化的情况下,我依然可以给你提供过得去的性能。

2、具备跨平台的优势–由于 Virtual DOM 是以 JavaScript 对象为基础而不依赖真实平台环境,所以使它具有了跨平台的能力,比如说浏览器平台、Weex、Node 等。

用index作为key可能会引发的问题:

1、若对数组数据进行:头部添加,头部删除等会造成数组的索引重排,那么key也会重排,虚拟dom对比时发现相同key的新旧虚拟dom内容不一样,就会把变化的虚拟dom重新生成新的真实dom,替换掉原来的真实dom,不会复用旧的真实dom,这样就降低了渲染的效率。

2、如果结构中还包含输入类的dom,会产生错误dom更新,=>界面有问题。

开发中如何选择key?
  1. 做好使用每条数据的唯一标识作为key,比如id、手机号、身份证号,学号等唯一值。
  2. 如果不存在对数据的头部添加、头部删除等造成数组索引重排的操作,仅用于渲染列表展示,将index作为key是没有问题的。

了解vue中key的原理需要一些前置知识。

就是vue的虚拟dom,vue会根据 data中的数据生成虚拟dom,如果是第一次生成页面,就将虚拟dom转成真实dom,在页面展示出来。

虚拟dom有啥用?每次vm._data 中的数据更改,都会触发生成新的虚拟dom,新的虚拟dom会跟旧的虚拟dom进行比较,如果有相同的,在生成真实dom时,这部分相同的就不需要重新生成,只需要将两者之间不同的dom转换成真实dom,再与原来的真实dom进行拼接。我的理解是虚拟dom就是起到了一个dom复用的作用,还有避免重复多余的操作,渲染效率高

列表的过滤和排序案例
<div class="box">
 <input type="text" placeholder="请输出名字" v-model:value="value">
 <button @click="sortType=0">原顺序</button>
 <button @click="sortType=1">按年龄升序</button>
 <button @click="sortType=-1">按年龄降序</button>
 <ul>
   <li>编号---姓名---年龄---性别</li>
   <!-- 要展示的数据,实际上是查询条件所得到的一个结果集:是一个计算后的属性 -->
   <li v-for="(item, index) in prosonsComponent" :key="">
     {{}}---{{}}--{{}}---{{}}
   </li>
 </ul>
</div>

<body>
 <script src="../js/"></script>
 <script>
   let vm = new Vue({
     el: '.box',
     data: {
       // 用户输入的查询字符串
       value: "",
       // 排序类型
       sortType: 0,
       prosons: [
         { id: 1, name: "马冬梅", age: 19, sex: '女' },
         { id: 2, name: "马冬雨", age: 18, sex: '女' },
         { id: 3, name: "周杰伦", age: 20, sex: '男' },
         { id: 4, name: "周星驰", age: 23, sex: '男' },
       ]
     },
     computed: {
       prosonsComponent: {
         get () {

           // filter()过滤器返回满足条件的元素。
           // 注意:判断字符串中是否包含空字符串:'你好'.includes("")结果为true;'你好'indexOf('')=0不为-1
           console.log('你好'.includes(""));
           let arr = this.prosons.filter((ele, index) => {
             return ele.name.indexOf(this.value) != -1;
           })

           // 判断是否需要排序
           if (this.sortType) {
             arr.sort((a, b) => {
               // a,b为arr数组中的相邻元素
               // 排序规则:前减后为升序,后减前为降序,注意相减的数是年龄不是对象
               return this.sortType == 1 ? a.age - b.age : b.age - a.age
             })
           }
           return arr
         }
       }
     }
   })
 </script>
<template>标签的使用

它仅仅是一个包装元素,不会在页面中做任何渲染,只接受控制属性(指令控制),
通过将其他标签包裹起来相当一个容器,但是节点自身不会在页面展示,没有生成真实的dom

<template v-if="ok">
  <h1>Title</h1>
  <p>Paragraph 1</p>
  <p>Paragraph 2</p>
</template>

3、其他内置指令

#### v-text
  1. 作用:向其所在的节点中渲染文本内容。

  2. 与插值语法的区别:v-text会替换掉节点中的内容,{{xx}}则不会。

    <map v-text="msg"></map>
    
v-html

1.作用:向指定节点中渲染包含html结构的内容。

2.与插值语法的区别:

  • v-html会替换掉节点中所有的内容,{{xx}}则不会。
  • v-html可以识别html结构。

3.严重注意:v-html有安全性问题!!!!

  • 在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击。
  • 一定要在可信的内容上使用v-html,永不要用在用户提交的内容上!
v-cloak

v-cloak指令(没有值):

  • 本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性。

  • 使用css配合v-cloak可以解决网速慢时页面展示出{{xxx}}的问题。

    <style>
        [v-cloak]{
            display:none;
        }
    </style>
    
    <div id="root">
        <h2 v-cloak>{{name}}</h2>
    </div>
    
v-once

v-once所在节点在初次动态渲染后,就视为静态内容了。

以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能

<div id="root">
    <h2 v-once>初始化的n值是:{{ n }}</h2>
    <h2>当前的n值是:{{ n }}</h2>
    <button @click="n++">点我n+1</button>
</div>
v-pre
  • 跳过其所在节点的编译过程,不会让vue解析

  • 可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译

    <div id="root">
        <h2 v-pre>Vue其实很简单</h2>
        <h2 >当前的n值是:{{n}}</h2>
        <button @click="n++">点我n+1</button>
    </div>
    

十五、自定义指令

1、函数式自定义指令默认有两个钩子函数,bind()和update()
2、对象式自定义指令默认有一些列钩子函数,常用bind()、inserted()和update()等
3、==构造函数的执行时机==
 - bind():指定与元素成功绑定时(一上来)
 - inserted():指令所在元素被插入页面时(元素被插入页面才能获取真实dom)
 - update():指令所在的模板被重新解析时:(数据一变化,模板就会被重新解析)

 4、钩子函数的常用参数
 1. ​`el`:指令所绑定的元素,可以用来直接操作 DOM
 2. ​`binding`:一个对象,包含以下 property:
	- `name`:指令名,不包括 `v-` 前缀
 	- value`:指令的绑定值,例如:`v-my-directive="1 + 1"` 中,绑定值为 `2
 	- `oldValue`:指令绑定的前一个值,仅在 `update` 和 `componentUpdated` 钩子中可用。无论值是否改变都可用
 	- expression`:字符串形式的指令表达式。例如 `v-my-directive="1 + 1"` 中,表达式为 `"1 + 1"
 	- arg`:传给指令的参数,可选。例如 `v-my-directive:foo` 中,参数为 `"foo"
 	- modifiers`:一个包含修饰符的对象。例如:`` 中,修饰符对象为 `{ foo: true, bar: true }

1、局部自定义指令:

指写在vm实例中或组件实例中

 <body>
   <div class="box">
     <h1 v-text="n"></h1>
     <!-- 自定义指令业务1、页面一上来就有值 -->
     <h1 v-big="n"></h1>
     <!-- 自定义指令业务2、页面一上来就有值,且元素获取焦点 -->
     <input type="text" v-fbind:value="n">
     <button @click="n++">n++</button>
   </div>
 </body>
 <script src="../js/"></script>
 
 <script>
   let vm = new Vue({
     el: '.box',
     data: {
       n: 1
     },
     directives: {
       // 1、自定义指令,函数式,
       // <!--ele:big指令绑定的dom元素,  binding:一个对象:有value属性,指令的绑定值-->
       // 函数式自定义指令默认有两个钩子函数,bind()和update()
       big (ele, binding) {
         ele.innerText = binding.value * 10
       },
 
       // 2、自定义指令对象式
       fbind: {
         //对象指令有一系列钩子函数:如下
         // 钩子函数也有两个参数element,binding
         // 指定与元素成功绑定时(一上来)
         bind (ele, binding) {
           console.log('bin');
         },
         // 指令所在元素被插入页面时(元素被插入页面才能获取真实dom)
         inserted (ele, binding) {
           ele.value = binding.value
           ele.focus()
         },
         //指令所在的模板被重新解析时:(数据一变化,模板就会被重新解析)
         update (ele, binding) {
           ele.value = binding.value
         }
       }
     }
   })
</script>

2、全局自定义指令

指令写在Vue构造函数的directive()方法中

十六、过滤器

定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)。

语法:
1、全局过滤器:(name,callback) ,全局过滤器要写在创建实例vm之前

2、局部过滤器:new Vue{filters:{}}
使用过滤器:{{ xxx | 过滤器名}} 或 v-bind:属性 = “xxx | 过滤器名”

备注:

​ 1.过滤器也可以接收额外参数、多个过滤器也可以串联

​ 2.并没有改变原本的数据, 是产生新的对应的数据

<body>
  <div class="box">
    <span>{{msg | timeFormater}}</span>
  </div>
</body>
<script src="../js/"></script>
<!-- 引入事件格式化插件 -->
<script src="/ajax/libs/dayjs/1.11.5/"></script>
<script>
  let vm = new Vue({
    el: '.box',
    data: {
      msg: "1661604238206"
    },
    // 过滤器,局部过滤器
    filters: {
      timeFormater (value) {
         //使用第三方时间格式化插件dayjs 
        return dayjs(value).format('YYYY MM-DD HH:mm:ss SSS' )
      }
    }
  })
</script>

十七、生命周期

  1. beforeCreate(创建前):数据监测(getter和setter)和初始化事件还未开始,此时 data 的响应式追踪、event/watcher 都还没有被设置,也就是说不能访问到data、computed、watch、methods上的方法和数据。
  2. created(创建后):实例创建完成,实例上配置的 options 包括 data、computed、watch、methods 等都配置完成,但是此时渲染得节点还未挂载到 DOM,所以不能访问到 $el属性。
  3. beforeMount(挂载前):在挂载开始之前被调用,相关的render函数首次被调用。此阶段Vue开始解析模板,生成虚拟DOM存在内存中,还没有把虚拟DOM转换成真实DOM,插入页面中。所以网页不能显示解析好的内容。
  4. mounted(挂载后):在el被新创建的 vm.$el(就是真实DOM的拷贝)替换,并挂载到实例上去之后调用(将内存中的虚拟DOM转为真实DOM,真实DOM插入页面)。此时页面中呈现的是经过Vue编译的DOM,这时在这个钩子函数中对DOM的操作可以有效,但要尽量避免。一般在这个阶段进行:开启定时器,发送网络请求,订阅消息,绑定自定义事件等等
  5. beforeUpdate(更新前):响应式数据更新时调用,此时虽然响应式数据更新了,但是对应的真实 DOM 还没有被渲染(数据是新的,但页面是旧的,页面和数据没保持同步呢)。
  6. updated(更新后) :在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。此时 DOM 已经根据响应式数据的变化更新了。调用时,组件 DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。
  7. beforeDestroy(销毁前):实例销毁之前调用。这一步,实例仍然完全可用,this 仍能获取到实例。在这个阶段一般进行关闭定时器,取消订阅消息,解绑自定义事件。
  8. destroyed(销毁后):实例销毁后调用,调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务端渲染期间不被调用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n4FVlVHe-1664507085278)(C:\Users\QuMing\Desktop\vue\资源\笔记图片\)]

十八、template配置项

如果 el 绑定的容器没有任何内容,就一个空壳子,但在 Vue 实例中写了 template,就会编译解析这个 template 里的内容,生成虚拟 DOM,最后将 虚拟 DOM 转为 真实 DOM 插入页面(其实就可以理解为 template 替代了 el 绑定的容器的内容)。

注意:template配置属性,和template标签是不同的

使用:需要有根标签包裹

  const shool = ({
    data: function () {
      return {
        schoolName: '学校名称',
        address: '学校地址',
      }
    },
    template: `
      <div>
        <h1>{{schoolName}}</h1>
        <h1>{{address}}</h1>
      </div>`
  })

组件开发

组件化开发也叫虚拟dom(virtual-dom)开发

我们平常在做开发的时候,有时候发现页面上面有很多布局或结构都是相同的时候,我们不得不把这些代码都写一次,然后复制粘贴 ,这样代码会变得非常多

1、对组件的理解

用来实现局部功能效果的代码集合(html+css+js+image……),

2、组件化

当应用中的功能都是多组件的方式来编写的, 那这个应用就是一个组件化的应用,。(组件组合和嵌套)

一个应用web网页由很多组件组成,而这些组件又分别由很多小组件组合而成。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hbQDHEXx-1664507085279)(C:\Users\QuMing\Desktop\vue\资源\笔记图片\Snipaste_2022-08-29_12)]

传统编写网页方式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TAN5zKEN-1664507085280)(C:\Users\QuMing\Desktop\vue\资源\笔记图片\)]

使用组件编写网页

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CqP8CxBa-1664507085280)(C:\Users\QuMing\Desktop\vue\资源\笔记图片\)]

3、vue使用组件的步骤

Vue中使用组件的三大步骤:
  1. 定义组件(创建组件)

使用(options)创建(const school = (options) 可简写为:const school = options),其中options和new Vue(options)时传入的那个options几乎一样,但也有点区别;

区别如下:

  • el不要写,为什么? ——— 最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。
  • data必须写成函数,为什么? ———— 避免组件被复用时,数据存在引用关系。
  1. 注册组件
  • 局部注册:靠new Vue的时候传入components选项,键是组件名,值是创建组件时的引用名字
  • 全局注册:靠(‘组件名’,组件的引用)
  1. 使用组件(写组件标签)

在容器中使用组件,写组件标签

  <div class="box">
    <!-- 第三部:使用组件 -->
    <scool></scool>
    <student></student>
  </div>

几个注意点:

1、关于组件名:

一个单词组成:

第一种写法(首字母小写):school
第二种写法(首字母大写):School

多个单词组成:

第一种写法(kebab-case命名):my-school
第二种写法(CamelCase命名):MySchool (需要Vue脚手架支持)

备注:
(1)组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行。
(2)可以使用name配置项指定组件在开发者工具中呈现的名字

2、使用组件的方法

  <div class="box">
    <!-- 双标签法 -->
    <scool></scool>
    <!-- 单标签法 -->
    <student/>
  </div>

4、非单文组件(几乎不用)

一个**.tml**文件中包含n个组件

<body>
 <div class="box">
   <!-- 第三部:使用组件 -->
   <scool></scool>
   <student></student>
   <student></student>
 </div>
</body>

<script src="../js/"></script>
<script>
 // const shool = ({
 // el: '.box',//组件没有el属性,最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。
 // "data"选项应该是一个函数,在组件定义中返回每个实例的值。每个组件都有自己的data属性,而不是所以组件共享一个data属性,
 // data: {
 //   schoolName: '学校名称',
 //   address: '学校地址',
 //   studentName: '学生姓名',
 //   age: 19
 // },
 // })


 // 第一步:创建组件
 // 创建school组件
 const shool = Vue.extend({
   data: function () {
     return {
       schoolName: '学校名称',
       address: '学校地址',
     }
   },
   template: `
     <div>
       <h1>{{schoolName}}</h1>
       <h1>{{address}}</h1>
       <button @click='fun'>点击提示学校名</button></button>
     </div>`,
   methods: {
     fun () {
       alert(this.schoolName)
     }
   }
 })
 // 创建student组件
 const student = Vue.extend({
   data: function () {
     return {
       studentName: '学生姓名',
       age: 19
     }
   },
   template: `
     <div>
         <h1>{{studentName}}</h1>
         <h1>{{age}}</h1>
     </div>`
 })


 // 注册全局组件
 Vue.component('student', student)


 // 创建vm实例,管理组件
 let vm = new Vue({
   el: '.box',
   // 第二步:注册局部组件
   components: {
     //key:组件名,value:组件引用名
     scool: shool,
   }
 })
</script>

5、非单文组件的嵌套

>注意:
>
>1. 子组件必须要在父组件前面定义,
>2. 子组件组测在哪个父组件中,就在那个父组件中使用,
>3. 一般定义一个管理其他组件的组件,这个组件名一般为app,只需要在vm中注册app组件就可以了,其他组件注册到app组中
<body>
 <div class="box">
   <!-- 第三部:使用组件,使用app组件,其他组件就都使用了-->
   <app></app>
 </div>
</body>

<script src="../js/"></script>
<script>


 // 创子组件。 student组件,子组件要放在父组件前定义
 const student = Vue.extend({
   data: function () {
     return {
       studentName: '学生姓名',
       age: 19
     }
   },
   template: `
     <div>
         <h1>{{studentName}}</h1>
         <h1>{{age}}</h1>
     </div>`
 })


 // 创建父组件,school组件
 const school = Vue.extend({
   data: function () {
     return {
       schoolName: '学校名称',
       address: '学校地址',
     }
   },
   // 子组件注册在哪个组件中,就在哪个组件中使用
   template: `
     <div>
       <h1>{{schoolName}}</h1>
       <h1>{{address}}</h1>
       <button @click='fun'>点击提示学校名</button></button>
       <student></student>
     </div>`,
   methods: {
     fun () {
       alert(this.schoolName)
     }
   },
   components: {
     //在父组件中使用子组件
     student: student
   }
 })

 // 创建一个app组件,其他组件都注册到这个组件中

 const app = Vue.extend({
   template: `
     <div>
       <school></school>
     </div>`,
   components: {
     school: school
   }
 })

 // 创建vm实例,通过管理所有组件的集合app组件来管理组件
 let vm = new Vue({
   el: '.box',
   // 第二步:注册app组件
   components: {
     app
   }
 })
</script>

6、VueComponent

  1. 组件本质是一个VueComponent的构造函数,且不是程序员定义的,是()生成的。
  const student = Vue.extend({
    data: function () {
      return {
        studentName: '学生姓名',
        age: 19
      }
    },
    template: `
      <div>
          <h1>{{studentName}}</h1>
          <h1>{{age}}</h1>
      </div>`
  })
  
  
  console.log(student);
  /* 
  ƒ VueComponent(options) {
              this._init(options);
          }
  */


2、当使用组件时(或),Vue解析时会帮我们创建组件的实例对象,即Vue帮我们执行的:new VueComponent(options)。

3、特别注意:每次调用,返回的都是一个全新的VueComponent!!!!(这个VueComponent可不是vue实例对象)

关于this指向:

组件配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【VueComponent实例对象】。
new Vue(options)配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【Vue实例对象】。

​ 4、VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)。Vue的实例对象,以后简称vm。

7、一个重要的内置关系。

一个重要的内置关系:.proto ===

为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue原型上的属性、方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CryQFtig-1664507085281)(C:\Users\QuMing\Desktop\vue\资源\笔记图片\)]

8、单文组件

一个**.vue**文件中只包含一个组件,vue组件包含三部分:、


<template>
  <div>
    <h1>{{name}}</h1>
    <h1>{{age}}</h1>
  </div>
</template>

<script>

//完整写法: 定义组件并把组件暴露出去,
export default ({
  data: function () {
    // 组件名
    name: 'Student'
    return {
      name: '学生姓名',
      age: 19
    }
  },
})

// 简写:()可以省略
// export default {

// }

</script>

<style>
  
</style>

9、使用vue脚手架开发组件

1、组件中的数据

组件的数据;自己内部的数据(data)+外部数据(props)

vue的组件与组件之前,及vue的内部都是相互独立,相互隔离,默认不进行相互通信,通过props接收外部数据

2、组件接收外部数据

1、props组件配置项:接收外部传递的数据

功能:让组件接收外部传过来的数据,

注意:子组件中所有的 prop 都将会刷新为最新的值。这意味着你应该在一个子组件内部改变 prop

所以:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。

1、父组件传递数据<Demo name="xxx"/>在子组件标签中传递数据

  • 静态传递数据:不使用指令绑定
  • 动态传递数据:使用指令绑定。可以在父组件中向子组件动态传递参数。
<template>
 <div>
   <!-- 在外部给组件传递数据 -->
   <!-- 不使用指令绑定属性,属性的值是字符串,静态传递数据 -->
   <Student name='张三' age='19'/>
   <Student name="李四" age='20'/>
   <!-- 使用v-bind指令绑定属性,就可以写表达式了,动态传递数据 -->
   <Student name="赵六" :age='21'/>
 </div>
</template>

2、子组件接收数据:

  1. 第一种方式(只接收):props:['name']
  //1、简单接收
  props: ['name', 'age'],
  1. 第二种方式(限制类型):props:{name:String}
  // 2、接收参数:对类型限制
  props: {
    name: String,
    age: Number
  }
  1. 第三种方式(限制类型、限制必要性、指定默认值)
  props: {
    name: {
      type: String,//name是String类型
      required: true, //name属性是必须的
    },
    age: {
      type: Number,//age是String类型
      default: 30//name属性的默认值为30
    }
  }
2、数据流的单向性

单向数据流,父级向子级传递动态数据时,父级数据的更新会向下流动到子组件中,但是反过来则不行;

子级不能更改父级传递过来的数据,一旦更改就会报错,破坏了数据的统一

3、破坏数据流单向性

正常情况下,我们是不需要破坏数据流的单向性的,但是如果某些特殊场景需要我们去改变,这个时候也可以使用下面的两种方式

1、 利用对象的堆栈原理

vue在进行组件传值的的时候使用的是浅拷贝规则 ,所以如果我们传递的是一个基本数据类型,数据在传递以后是互不影响 的,如果要让两个数据之间有相互的关系,可以使用对象来完成

2、通过vue官方提供的方法–自定义事件
3、剩余参数$attrs

父组件传递给子组件多出来的参数,剩余参数$attrs,组件实例对象上的属性

  mounted() {
      (this.$attrs);
    }
4、inheritAttrs继承属性

如果在子组件配置对象中设置,inheritAttrs:false,剩余参数属性不会继承,不会出现在子组件的标签上

3、自定义事件

$emit():子组件向父组件传递数据

父组件向子组件派发事件(相当于给子组件传递了一个自定义事件参数)

组件实例上有** e m i t ( ) ∗ ∗ 方法使用 t h i s . emit()**方法使用this. emit()方法使用this.emit()方法触发派发的事件,并且可以传递参数但不是必须的

语法:$emit(‘派发的事件名’,‘王五’)

<template>
<div>
<!-- 父组件向子组件派发自定义事件(相当于给子组件传递了一个自定义事件参数),子组件通过props接收派发的事件,使用$emit()带触发事件,并传递参数-->

 <!-- 向子组件派发eventName事件 -->
 <Student name="赵六" :age='21' sex='男' @eventName="fun"/>
 姓名<span>{{name}}</span>
</div>
</template>

<script>
import School from './components/'
import Student from './components/'
export default {
name: 'App',
components: {
 School,
 Student,
}, data () {
 return {
   name:'张三'
 }
},
methods: {
   //自定义事件的回调函数
 fun(name){
    = name
 }
}
}
</script>

子组件触发派发的事件

<template>
<div>
  //触发派发的事件(事件名要和派发的事件名一致) ,并传递参数
 <button @click="$emit('eventName','王五')">向父组件传递参数</button>
</div>
</template>

<script>
export default {
name: 'Student',
}
</script>

4、组件插槽slot

理解:组件中的插槽可以理解成我们之前的电脑里同的主板,这个主板可以确定大多数的功能,对于不确定的东西则预留一个插槽,方便后期插入东西进去

这个思维方式与组件很相似,我在封装组件的时候,其实也可以一些公共的确定的HTML封装起来,对于那些不确定的东西我们则给可以给它预留一下插槽

适用于 父组件 ===> 子组件

子组件留下一部分空间用来给父组件去填充的,而留下的空间使用的占位符就叫插槽(slot)

作用:如果想在件组件的开始标签与结束标签中插入内容,须预留插槽用去完成

分类:默认插槽(非具名插槽)、具名插槽、作用域插槽

1、在组件中定义插槽标签

<template>
	<div>
		<!-- 1、不写name为默认插槽 ,-->
		<slot></slot>
		<slot>不插入内容的默认插槽</slot>

		<!-- 2、具名插槽 使用name给插槽起名字-->
		<slot name="slot1"></slot>
		<slot name="slot2"></slot>

		<!-- 3、作用域插槽 ,将插槽的属性(除了name属性)打包成一个对象,作为实参传递给v-slot='形参'的形参,-->
		<slot name='slot3' :user='user' sex='男'></slot>
<	/div>
</template>

2、在其他组件中使用插槽

<template>
<div>
	<!-- 使用Student组件的插槽 -->
	<Student>
		<!-- 1、使用的是子组件的默认插槽 -->
		<h1>向子组件插入一个h1标签</h1>

		<!-- 2、使用的是子组件的具名插槽 结合template标签,使用v-slot:插槽名 指名使用哪个插槽-->
		<template v-slot:slot1>
  			<h1>使用具名插槽1</h1>
		</template>

		<template v-slot:slot2>
  			<h1>使用具名插槽2</h1>
		</template>

		<!-- 3、使用的是子组件作用域插槽扩大组件的作用域,使用v-slot='形参',形参接收作用域插槽打包过来的对象 -->
		<template v-slot:slot3='value'>
  			<h1>使用作用域插槽{{}}</h1>
		</template>

		<!-- 同名解构方式 -->
		<template v-slot:slot3='{user,sex}'>
  			<h1>使用作用域插槽{{user}}</h1>
  			<h1>使用作用域插槽{{sex}}</h1>
		</template>
	</Student>

</div>
</template>

剩余插槽$slots

和剩余参数** a t t r s ∗ ∗ 类似,即:使用的插槽数量比定义的插槽数量多时,多于的会放入子组件实例对象的 ∗ ∗ attrs**类似,即:使用的插槽数量比定义的插槽数量多时,多于的会放入子组件实例对象的** attrs类似,即:使用的插槽数量比定义的插槽数量多时,多于的会放入子组件实例对象的slots**属性上

获取$slots属性

(this.$slots);

5、动态组件

vue内置了一个组件,该组件是一个动态的,

用法:组件会在 currentTabComponent 改变时改变,要让那个组件显示就把这个组件名当做参数传入到is的表达式中

<component v-bind:is="currentTabComponent"></component>

currentTabComponent 可以包括

​ 1、已注册组件的名字

​ 2、一个组件的选项对象

<template>
<div>
 <!-- 条件显示dom -->
 <School v-if="student==true" />
 <Student name="赵六" :age='21' v-if="student==false" />
 <button @click="student = !student">点击显示dom</button>

   
 <!-- 动态组件会在 `currentTabComponent` 改变时改变 -->
 <!-- component组件是vue内置的组件 
         currentTabComponent 可以包括
               1、已注册组件的名字
               2、一个组件的选项对象 
 -->
 <component v-bind:is="currentTabComponent"></component>
 <button @click="click1">使用component切换组件</button>
</div>
</template>

<script>
import School from './components/'
import Student from './components/'
export default {
name: 'App',
components: {
 School,
 Student,
}, 
data () {
 return {
   student: true,
   currentTabComponent: 'Student'
 }
},
methods: {
 click1 () {
   if ( == 'School')  = 'Student'
   else  = 'School'
 }
}
}
</script>

6、styple样式标签的 scoped属性

scoped,作用域,表示当前的样式只在当组件中有效,会在标签上加上一个唯一标识。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2fBhoKbB-1664507085282)(C:\Users\QuMing\Desktop\vue\资源\笔记图片\Snipaste_2022-08-30_18)]

<style lang="scss" scoped>

</style>

7、标签的ref属性:获取组件实例

ref属性

  • 被用来给元素或子组件注册引用信息(id的替代者)

  • 应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)

  • 使用方式:
    打标识


    获取:this.$

❓ 注意:1、当ref写在组件上时,通过this**. r e f s . x x x ∗ ∗ 获取的是虚拟 d o m ,要再往下找, t h i s . ​ ∗ ∗ **获取的是虚拟dom,要再往下找,this.​** refs.xxx获取的是虚拟dom,要再往下找,this.​refs**.xxx.​**$el**

2、只要是通过v-for来生成的元素,它通过this.$refs[xxx]得到的就是一个数组

<template>
<div>
 <img src="./assets/">
 <!-- 使用ref给元素打标记,来获取dom元素 -->
 <h1 v-text="msg" ref="h1"></h1>
 <button @click="click">点击输出上的dom元素</button>

 <!-- 给组件打标记 -->
 <School ref="school"></School>
 <Student></Student>
</div>
</template>

<script>
import School from './components/'
import Student from './components/'
export default {
name: 'App',
components: {
 School: School,
 Student: Student
},
methods: {
 click () {
   // 组件对象上的$refs对象中有所有对应标记的dom元素和组件对象
   (this.$refs);
   (this.$refs.h1);//获取好h1元素
   (this.$);//获取School组件对象

 }
}
}
</script>

组件实例对象上的属性 $refsd对象中有所有打标机的dom元素和组件实例对象

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iEKtuLbK-1664507085283)(C:\Users\QuMing\Desktop\vue\资源\笔记图片\Snipaste_2022-08-30_13)]

8、mixin混入

  • 混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。

  • 一个混入对象可以包含任意组件选项。

  • 当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项,是一个merge(合并)操作。

选项合并

  • data数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先

  • 值为对象的选项,例如 methodscomponentsdirectives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。

  • 声明周期函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。

1、定义mixin,通常在mixin文件下创建文件,来定义mixin

// 定义一个非单文mixin组件
exports.a = {
   data () {
        return {
            name: 'zs',
            age: 19
        }
   },
   // 函数
   methods: {
        fun() {
            console.log(2);
        }
   },
   // 挂载后
   mounted () {
        console.log('mixin的挂载钩子');
   },
   // 销毁前
   beforeDestroy () {
        console.log('mixin的销毁前钩子');
   },
}

2、使用mixin

局部混入,局部混入在组件中导入

// 导入mixin
import {a} from '../mixin/'

在组件实例中使用mixin使用使用

export default {
// 使用mixin
mixins:[a],
}
</script>

全局混入(慎用),全局混入要在入口文件中导入,在Vue构造函数上挂载

// 全局混入
Vue.mixin(a)

9、插件

插件通常用来为 Vue 添加全局功能。插件的功能范围没有严格的限制

插件必须暴露一个install()方法,通过() 使用这个插件

本质:包含install方法的一个对象,install的第一个参数是Vue构造器,第二个以后的参数是插件使用者传递的数据。

1、定义插件

// 定义插件:本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据。

// 向外暴露install()方法
export default {
install (Vue, options) {
      // 在Vue构造函数上挂载方法
      Vue.fun = function () {
           console.log("在Vue构造函数上挂载的方法执行了");
      }

      Vue.prototype.$fun1 = function () {
           console.log("在Vue构造函数的原型上挂载的方法执行了");
      }

      Vue.mixins({
           created() {
               console.log("混入");
           },
      })

      Vue.directive('color',{
           bind(ele,bingding){
               console.log(ele);
           },
      })

}
}

2、使用插件

先导入插件

// 导入插件
import plugin from './'

在使用插件

// 使用插件,全局使用
Vue.use(plugin,{a:1,b:2})

自定义指令

指令是具有一组生命周期的钩子:

  • created : 在绑定元素的属性或事件监听器被应用之前调用。
  • beforeMount : 指令第一次绑定到元素并且在挂载父组件之前调用。。
  • mounted : 在绑定元素的父组件被挂载后调用。。
  • beforeUpdate: 在更新包含组件的 VNode 之前调用。。
  • updated: 在包含组件的 VNode 及其子组件的 VNode 更新后调用。
  • beforeUnmount: 当指令与在绑定元素父组件卸载之前时,只调用一次。
  • unmounted: 当指令与元素解除绑定且父组件已卸载时,只调用一次。

钩子函数参数

指令钩子函数会被传入以下参数:

  • el:指令所绑定的元素,可以用来直接操作 DOM。
  • binding:一个对象,包含以下 property:
    • name:指令名,不包括 v- 前缀。
    • value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2
    • oldValue:指令绑定的前一个值,仅在 updatecomponentUpdated 钩子中可用。无论值是否改变都可用。
    • expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"
    • arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"
    • modifiers:一个包含修饰符的对象。例如: 中,修饰符对象为 { foo: true, bar: true }
  • vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。
  • oldVnode:上一个虚拟节点,仅在 updatecomponentUpdated 钩子中可用。

案例

局部指令

  directives: {
    // 指令就是一组钩子函数
    // 定义局部指令focus,在模板中使用时加v前缀
    focus: {
      /* 指令的定义,每个钩子函数都有参数
            el 指令绑定到的元素。这可用于直接操作 DOM。
            binding 
       */
      // 被绑定元素插入父节点时调用的钩子
      inserted: function (el, binding) {
        el.focus()
      },
    }
  }

全局指令

全局指令挂载到Vue上

Vue.directive('focus', {
  // 当被绑定的元素插入到 DOM 中时……
  inserted: function (el) {
    // 聚焦元素
    el.focus()
  }
})

指令函数简写

局部

  directives: {
    // 简写包含两个钩子函数 bind 和 update 时触发相同行为而不关心其它的钩子
    focus (el, binding) {
      el.focus
    }
  }

全局

Vue.directive('color-swatch', function (el, binding) {
  el.style.backgroundColor = binding.value
})

10、全局事件总线

一种组件间通信的方式,适用于任意组件间通信。

使用步骤:

1、安装全局事件总线:在入口文件文件中

new Vue({
	......
	beforeCreate() {
		Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
	},
    ......
}) 

2、使用事件总线:

1、接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。

methods(){
  demo(data){......}
}
......
//在mounted钩子函数中,绑定自定义事件
mounted() {
  this.$bus.$on('xxxx',this.demo)
}

2、提供数据:在其他组件中触发自定义事件,this.$bus.$emit('xxxx',数据)

最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。

11、消息订阅与发布

一种组件间通信的方式,适用于任意组件间通信。

使用步骤:

1、安装pubsub:

npm i pubsub-js
OR
yarn add pubsub-js

2、引入:

import pubsub from 'pubsub-js'

接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。

最好在beforeDestroy钩子中,用(pid)去取消订阅。

<script>
// 先导入pubsub
import PubSub from 'pubsub-js'
export default {
 name: 'School',
 mounted () {
   //订阅消息,使用()订阅的消息名为hello,当收到消息的时候会触发回调函数,第一个参数为消息名,第二个参数为发送过来的数据
   // 返回值为当前消息的id,用来销毁这条消息的订阅的相当于定时器的id,并将这个id挂载到this上,再beforeDestroy销毁前钩子中可以用到,
   this.pid = PubSub.subscribe('hello', (msgName, data) => {
     console.log("接收到了消息");
     console.log(msgName, data);
   })
 },
   //最好在beforeDestroy钩子中,用(pid)去取消订阅。
 beforeDestroy () {
   PubSub.unsubscribe(pid)//去取消订阅。
 },
}
</script>

提供数据:(‘消息名’,数据)

<template>
 <div>
   <span>{{user}}</span>
   <button @click="fun">点击给学校发送消息</button>
 </div>
</template>
<script>
   
// 导入pusub插件
import PubSub from 'pubsub-js'
export default {
 name: 'Student',
 data: function () {
   return {
     user: { name: '张三', age: 19 }
   }
 },
 methods: {
   fun () {
     // 发送消息,第一个参数为发送消息的名字,第二个参数为发送消息的参数
     ('hello', )
   }
 },
}
</script>

12、代理请求

使用axios发起请求

1、下载安装包

$ npm install axios
or
$ yarn add axios

2、引入

import axios from "axios";

3、发起请求


<template>
  <div>
    <button @click="fun">发送请求</button>
  </div>
</template>

<script>
//导入axios
import axios from "axios";

export default {
  name: 'App',
  },
  methods: {
    fun () {
       //发送请求 
      ('http://127.0.0.1:5000/v1/food/getlist/1').then(
        (resolve) => {
          ();
        }
      ).catch((rejiect) => {
        (rejiect);
      })
    }
  },
}
</script>
使用代理服务器解决跨域问题

原理:浏览器不再直接向资源服务器发送请求,而是将请求发送到代理服务器,代理服务器和本地时同源的,然后代理服务器将本次请求转发到资源服务器,服务器和服务器之间不存在跨域问题,然后请求回来的数据返回到代理服务器,代理服务器再返回给浏览器。

代理服务器:nigx反向代理、vue-cli的代理服务器

vue-cli代理服务器的配置:通过 中的 选项来配置

方式一

说明:

  1. 优点:配置简单,请求资源时直接发给前端(8080)即可。
  2. 缺点:不能配置多个代理,不能灵活的控制请求是否走代理。
  3. 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器 (优先匹配前端资源)
const { defineConfig } = require('@vue/cli-service')

// 使用的是CommonJS规范
module.exports = defineConfig({
  transpileDependencies: true,
  publicPath: "./",//默认是网络地址,本地打不开,要想本地打开,设置为./或者为空
  // 语法检查关闭
  lintOnSave: false,
  // 开启代理服务器,端口号为资源服务器端口,
  devServer: {
    proxy: 'http://localhost:8889'
  }
})

方式二

说明:

  1. 优点:可以配置多个代理,且可以灵活的控制请求是否走代理。
  2. 缺点:配置略微繁琐,请求资源时必须加前缀。
const { defineConfig } = require('@vue/cli-service')

// 使用的是CommonJS规范
module.exports = defineConfig({
  transpileDependencies: true,
  publicPath: "./",//默认是网络地址,本地打不开,要想本地打开,设置为./或者为空
  // 语法检查关闭
  lintOnSave: false,
  
  // 方式二:
  devServer: {
    proxy: {
      '/api': {// '/api':请求前缀,通过判断请求的url是否匹配这个前缀,来决定是否使用代理
        target: 'http://localhost:8889',//target:就是方式一的地址,端口号是请求资源的地址。
        pathRewrite: { '^/api': '' }//正则重写,重写url,将前缀替换为空,发送的时候将http://127.0.0.1:3000/api/getUser ==转换为=> http://127.0.0.1:3000/getUser 
         ws: true,//用于支持websocket
         changeOrigin: true//url欺骗(修改的是请求头中的host),true为开启欺骗,不写默认开启,代理服务器将开启欺骗后,代理服务器请求的的端口号和资源服务器端口号保持一致,
      },
    }
  }
})

请求第地址的改变,

  methods: {
    fun () {
      // 访问代理服务器的端口8080,不再是资源服务器的端口号
      axios.get('http://localhost:8080/v1/food/getlist/1').then(
        (resolve) => {
          console.log(resolve.data);
        }
      ).catch((rejiect) => {
        console.log(rejiect);
      })
    }
  },

13、隐藏的声明周期钩子:nextTick()

  1. 语法:this.$nextTick(回调函数)

  2. 作用:在下一次 DOM 更新结束后执行其指定的回调。

    它会判断当前的代码是否可以在本生命周期执行,如果不可以会顺延到下一个生命周期,如果还不行则继续顺延,直到可以执行为止

  3. 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。

14、Vue封装的过渡与动画

提示:使用样式的几种方法,

  • 局部样式:直接在各自的组件使用,
/* lang:使用的预处理语言,scope:样式只在当前组件中有效 */
<style lang="scss" scope>
    /* 内容 */
</style>
  • 全局样式:定义在App组件中的样式,没有使用scope属性
<style lang="scss">

</style>
  • 外部样式:在外部定义一个css文件,一般在assets文件中创建一个css文件夹在css文件夹中定义外部css样式,然后在要使用该样式的组件的
1、使用原生实现过渡和动画

1、实现过渡

<template>
<div>
<ul class="ul">
<li v-for="(ele,index) of user" key="">
  {{}}--{{}}
</li>
</ul>
</div>
</template>

<script>
export default {
name: 'Gd',
methods: {
// 给所有li定义动画,封装成一个函数
fun () {
('li').forEach((ele, index) => {
   = `margin-left:0;  transition: all ${index * 1000}ms linear 10ms;`
})
}
},
data () {
return {
user: [
  { id: 1, name: 'zs' },
  { id: 2, name: 'zl' },
  { id: 3, name: 'ww' },
  { id: 4, name: 'tq' },
]
}
},
mounted () {

// () 第一次生成dom时内容没有变化,没有进行虚拟dom对比,需要使用定时器
setTimeout(() => {
// 在mounted声明周期函数中调用动画方法
()
}, 1)
},


}
</script>



/* lang:使用的预处理语言,scope:样式只在当前组件中有效 */
<style lang="scss" scope>
* {
margin: 0;
padding: 0;
}
.ul {
li {
  background-color: aqua;
  width: 100%;
  margin-left: -100vw;
  height: 50px;
  text-align: center;
  &:nth-child(2n) {
    background-color: yellow;
  }
}
}
</style>

2、实现动画

<template>
<div>
  <ul class="ul">
    <li v-for="(ele,index) of user"
        :key=""
        :style='`animation: aaa 1s linear ${index * 100}ms forwards;`'>
      {{}}--{{}}--{{index}}
    </li>
  </ul>
</div>
</template>

<script>
export default {
name: 'Donghua',
data () {
  return {
    user: [
      { id: 1, name: 'zs' },
      { id: 2, name: 'zl' },
      { id: 3, name: 'ww' },
      { id: 4, name: 'tq' },
    ],
    style: {

    }
  }
},
}
</script>



/* lang:使用的预处理语言,scope:样式只在当前组件中有效 */
<style lang="scss" scope>
* {
margin: 0;
padding: 0;
}
.ul {
li {
  background-color: aqua;
  width: 100%;
  transform: translateX(-100%);
  height: 50px;
  text-align: center;
  &:nth-child(2n) {
    background-color: yellow;
  }
}
}

@keyframes aaa {
from {
  transform: translateX(-100%);
}
to {
  transform: translateX(100%);
}
}
</style>
2、vue的过渡和动画

使用transition组件

特点:

  1. 在使用transition标签时标签内只能同时显示一个元素
  2. transition本身不会渲染出元素节点
  3. transition组件可以给元素在某些特定的时刻添加上特定的属性,从而实现动画效果(6个类)
  4. transition组件可以使用mode标签属性的过渡模式
  5. transition的name属性值默认的是v可以通过name修改属性名前缀

注意:

  • transition 组件不只可以写过渡,也可以写动画,
  • 不给transition 使用name属性取名字,六个类名默认以v开头

1、概念:Vue 在插入、更新或者移除 DOM 时,提供多种不同方式的应用过渡效果

2、原理:当插入或删除包含在 transition 组件中的元素时,Vue 将会做以下处理:

  1. 自动嗅探目标元素是否应用了 CSS 过渡或动画,如果是,在恰当的时机添加/删除 CSS 类名
  2. 如果过渡组件提供了 JavaScript 钩子函数,这些钩子函数将在恰当的时机被调用。
  3. 如果没有找到 JavaScript 钩子并且也没有检测到 CSS 过渡/动画,DOM 操作 (插入/删除) 在下一帧中立即执行。(注意:此指浏览器逐帧动画机制,和 Vue 的 nextTick 概念不同)

3、可以过渡条件:Vue 提供了 transition 的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡

  • 条件渲染 (使用 v-if)
  • 条件展示 (使用 v-show)
  • 动态组件
  • 组件根节点

4、过渡的类名:

  • 元素进入的样式:
    1. v-enter:进入的起点
    2. v-enter-active:进入过程中
    3. v-enter-to:进入的终点
  • 元素离开的样式:
    1. v-leave:离开的起点
    2. v-leave-active:离开过程中
    3. v-leave-to:离开的终点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sSzme19b-1664507085284)(C:\Users\QuMing\Desktop\vue\资源\笔记图片\Snipaste_2022-08-31_19)]

单元素/组件的过渡
<template>
<div>

 <!-- 不给transition 使用name属性取名字,六个类名默认以v开头-->
 <button @click="show = !show">
   Toggle
 </button>
 <!-- 元素显示可隐藏 -->
 <transition>
   <h1 class="h"
       v-if="show">hello</h1>
 </transition>

</div>
</template>

<script>
export default {
name: 'Animation1',
data () {
 return {
   show: false
 }
},
}
</script>

<style lang="scss" scoped>
.h {
background-color: aqua;
width: 100%;
}

// 元素进入时
// 进入前
.v-enter {
opacity: 0;
}
// 进入后
.v-enter-to {
opacity: 1;
}
// 进入中
.v-enter-active {
transition: all 2s;
}

// 元素离开时
// 离开前
.v-leave {
opacity: 1;
}
// 离开后
.v-leave-to {
opacity: 0;
}
// 离开中
.v-leave-active {
transition: all 2s;
}


/* 简写方式---------------- */
.v-enter,
.v-leave-to {
opacity: 0;
}

.v-enter-to,
.v-leave {
opacity: 1;
}
.v-enter-active,
.v-leave-active {
transition: all 3s linear;
}
</style> 

动画

动画实现上面效果

注意:使用动画就不需要下面的class,因为动画已经有了起点和终点,只需要中间过程

  • .v-enter,
  • .v-leave-to
  • .v-enter-to,
  • .v-leave
<template>
  <div>

    <!-- 不给transition 使用name属性取名字,六个类名默认以v开头-->
    <button @click="show = !show">
      Toggle
    </button>
    <!-- 元素显示可隐藏 -->
    <transition>
      <h1 class="h"
          v-if="show">hello</h1>
    </transition>

  </div>
</template>

<script>
export default {
  name: 'Animation1',
  data () {
    return {
      show: false
    }
  },
}
</script>

<style lang="scss" scoped>
/* 定义动画*/
@keyframes aaa {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

.h {
  background-color: aqua;
  width: 100%;
}

// 使用动画方式实现
/* 使用动画就不需要下面的classl,因为动画已经有了起点和终点,只需要中间过程
.v-enter,
.v-leave-to
.v-enter-to,
.v-leave */

.v-enter-active {
  animation: aaa 2s linear forwards;
}
.v-leave-active {
  animation: aaa 2s linear reverse forwards;
}
</style> 

3、自定义过渡的类名
  • 自定义过渡类名:将名字和第三方库的类名相同时,可以直接将第三方动画库引入直接使用非常方便

    • enter-class
    • enter-active-class
    • enter-to-class (2.1.8+)
    • leave-class
    • leave-active-class
    • leave-to-class (2.1.8+)

    下载第三方库

     yarn add 
    or
     npm install  --save
    

    导入插件

    import '';
    

    使用

    <template>
      <div>
        <button @click="show = !show">
          Toggle
        </button>
    
        <!-- 给六个class起别名 -->
        <!-- animate__animated为动画的基类, -->
        <transition name="a" enter-active-class="animate__animated animate__bounce" leave-active-class="animate__animated animate__backOutUp">
          <h1 class="h"
              v-if="show">hello</h1>
        </transition>
    
      </div>
    </template>
    
    <script>
    // 导入动画库
    import '';
    
    export default {
      name: 'Animation2',
      data () {
        return {
          show: false
        }
      },
    }
    </script>
    
    <style lang="scss" scoped>
    .h {
      background-color: aqua;
      width: 100%;
    }
    </style> 
    
    
4、transition生命周期钩子
<transition
      
v-on:before-enter="beforeEnter"<!--元素进场动画或过渡执行前执行的钩子  -->
v-on:enter="enter"<!-- 元素进场动画或过渡执行中执行的钩子 -->
v-on:after-enter="afterEnter"<!-- 元素进场动画或过渡执行后执行的钩子 -->
v-on:enter-cancelled="enterCancelled"<!-- 进场时过渡或动画中断时执行的钩子 -->

v-on:before-leave="beforeLeave"<!-- 元素离场动画或过渡执行前执行的钩子 -->
v-on:leave="leave"<!-- 元素离场动画或过渡执行中执行的钩子 -->
v-on:after-leave="afterLeave"<!-- 元素离场动画或过渡执行后执行的钩子 -->
v-on:leave-cancelled="leaveCancelled"<!-- 离场时过渡或动画中断时执行的钩子 -->
>
<!-- ... -->
</transition>

1、使用

<template>
<div>

<button @click="show = !show">
Toggle
</button>

<!-- 给六个class起别名 -->
<!--  -->
<!-- animate__animated为动画的基类, -->
<transition name="a" enter-active-class="animate__animated animate__bounce" leave-active-class="animate__animated animate__backOutUp" v-on:before-enter="beforeEnter" v-on:enter="enter" v-on:after-enter="afterEnter">

<h1 class="h" v-if="show">hello</h1>
</transition>

</div>
</template>

<script>
// 导入动画库
import '';

export default {
name: 'Animation2',
data () {
return {
show: false
}
},
methods: {
beforeEnter (el,done) {
 = 'red'
// done()
('进场时动画或过渡执行前执行的钩子');
},
enter (el, done) {
 = 'red'

done();
('进场时动画或过渡执行中执行的钩子');
},
afterEnter (el,done) {
 = 'pink'
// done();

('进场时动画执行后执行的钩子');
}
}
}
</script>

<style lang="scss" scoped>
.h {
// background-color: aqua;
width: 100%;
}
</style> 
5、appear一上来就执行过渡

多个元素实现过渡或动画时,一上来就执行,可以使用appear

 <transition appear name="a">

 </transition>
6、多个元素的过渡

多个元素实现过渡或动画需要使用

多个元素过渡 相同的元素需简要加key,设置唯一的值来标记以让 Vue 区分它们

<template>
<div>

 <button @click="show = !show">
   Toggle
 </button>
 <!-- 多个元素过渡 是只能同时显示一个元素->
 <transition appear name="a">
   <h1 class="h" v-if="show" >hello1</h1>
   <h1 class="h" v-if="!show">hello2</h1>
 </transition>

</div>
</template>

<script>
export default {
name: 'Animation3',
data () {
 return {
   show: false
 }
},
}
</script>

<style lang="scss" scoped>
/* 定义动画*/
@keyframes aaa {
from {
 transform: translateX(-100%);
}
to {
 transform: translateX(0);
}
}

.h {
background-color: aqua;
width: 100%;
}


.a-enter-active {
animation: aaa 3s linear forwards;
}
.a-leave-active {
animation: aaa 3s linear reverse forwards;
}
</style> 

7、过渡模式

同时生效的进入和离开的过渡不能满足所有要求,所以 Vue 提供了过渡模式

  • in-out:新元素先进行过渡,完成之后当前元素过渡离开。(新元素先进场,当前元素后离场)
  • out-in:当前元素先进行过渡,完成之后新元素过渡进入。(当前元素先离场,新元素再进场)

in-out效果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NWAPoIvv-1664507085285)(C:\Users\QuMing\Desktop\vue\资源\笔记图片\GIF 2022-8-31 )]

out-in效果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S2DO0ebZ-1664507085285)(C:\Users\QuMing\Desktop\vue\资源\笔记图片\GIF 2022-8-31 )]

代码

<template>
<div>

 <button @click="show = !show">
   Toggle
 </button>
 <!-- 多个元素过渡 相同的元素需简要加key,设置唯一的值来标记以让 Vue 区分它们-->
 <!-- 过渡模式:
 in-out:新元素先进行过渡,完成之后当前元素过渡离开。
 out-in:当前元素先进行过渡,完成之后新元素过渡进入。 
 -->
 <transition appear name="a" mode="out-in">
   <h1 class="h" v-if="show" :key="1">hello1</h1>
   <h1 class="h" v-if="!show" :key="2">hello2</h1>
 </transition>

</div>
</template>

<script>
export default {
name: 'Animation3',
data () {
 return {
   show: false
 }
},
}
</script>

<style lang="scss" scoped>
/* 定义动画*/
@keyframes aaa {
from {
 // opacity: 0;
 transform: translateX(-100%);
}
to {
 // opacity: 1;
 transform: translateX(0);
}
}

.h {
background-color: aqua;
width: 100%;
// transform: translateX(-100%);
}

// 使用动画方式实现
/* 使用动画就不需要下面的classl,因为动画已经有了起点和终点,只需要中间过程
.v-enter,
.v-leave-to
.v-enter-to,
.v-leave */

.a-enter-active {
animation: aaa 0.5s linear forwards;
}
.a-leave-active {
animation: aaa 0.5s linear reverse forwards;
}
</style> 

8、多组件过渡

多个组件的过渡简单很多 - 我们不需要使用 key 我们只需要使用动态组件

<template>
<div>
 <!-- 点击切换组件,组件显示有过渡效果 -->
 <button @click="fun">
   Toggle
 </button>
 <transition appear name="a" mode="out-in">
   <!-- 多个组件过渡 -->
   <!--动态组件使用 component,is的值为要显示组件名 -->
   <component :is="componentId"></component>
 </transition>

</div>
</template>

<script>

import Student from './'
import School from './'
export default {
name: 'Animation4',
data () {
 return {
   show: false,
   componentId: "Student"
 }
},
components: {
 Student,
 School
},
methods: {
 fun () {
   if ( == 'Student')  = 'School'
   else  = 'Student'
 }
}



}
</script>

<style lang="scss" scoped>
/* 定义动画*/
@keyframes aaa {
from {
 opacity: 0;
 // transform: translateX(-100%);
}
to {
 opacity: 1;
 // transform: translateX(0);
}
}

.h {
background-color: aqua;
width: 100%;
// transform: translateX(-100%);
}

.a-enter-active {
animation: aaa 0.5s linear forwards;
}
.a-leave-active {
animation: aaa 0.5s linear reverse forwards;
}
</style> 

9、列表过渡

使用组件

特点:

  1. transition-group可以给对个元素进行过渡或动画,可以显示多个元素
  2. transition-group默认渲染一个元素,可以通过 tag 属性 指定为其他元素。
  3. 过渡模式不可用,因为我们不再相互切换特有的元素。
  4. 内部元素总是需要提供唯一的 key attribute 值。
  5. CSS 过渡的类将会应用在内部的元素中,而不是这个组/容器本身。
1、多元素使用过渡或动画
<template>
<div>
<button @click="show=!show">
Toggle
</button>
<!--  transition-group组件的 mode属性无法使用,没有效果-->
<transition-group name="List" tag="ul" appear >
<h1 v-show="show" key="1">hello</h1>
<h1 v-show="!show" key="2">hi</h1>
</transition-group>
</div>
</template>

<script>

export default {
name: 'List',
data () {
return {
show: false,
}
},
}
</script>

<style lang="scss" scoped>
/* 定义动画*/
@keyframes aaa {
from {
opacity: 0;
// transform: translateX(-100%);
}
to {
opacity: 1;
// transform: translateX(0);
}
}

.h {
background-color: aqua;
width: 100%;
// transform: translateX(-100%);
}

.List-enter-active {
animation: aaa 0.5s linear forwards;
}
.List-leave-active {
animation: aaa 0.5s linear reverse forwards;
}
</style> 

2、列表的进入/离开过渡
<template>
<div>
<!--  transition-group mode属性无法使用-->
id<input type="text" v-model="id"><br />
name<input type="text" v-model="name"><br />
age<input type="text" v-model="age"><br />
<!-- 数组尾部添加 -->
<button @click="push">push</button>
<!-- 数组头部添加 -->
<button @click="unshift">unshift</button>

<transition-group tag="ul" appear>
<li v-for="(item, index) in list" :key="" ref="li" @click="del(index)">
  {{}}--{{}}--{{index}}
</li>
</transition-group>

</div>
</template>

<script>

export default {
name: 'List1',
data () {
return {
name: "",
age: '',
id: '',
list: [
  { id: 1, name: '张三', age: 18 },
  { id: 2, name: '王五', age: 20 },
  { id: 3, name: '赵六', age: 19 },
]
}
},
methods: {
push () {
({ id: , name: , age:  })
},

unshift () {
({ id: , name: , age:  })
},
del (index) {
(index, 1)
}
},

mounted () {
this.$((element, index) => {
//  = `transition-delay:${index * 300}ms;`
 = `animation-delay:${index * 500}ms;`
});
}

}

</script>

<style lang="scss" scoped>
/* 定义动画*/
@keyframes aaa {
from {
opacity: 0;
transform: translateX(-100%);
}
to {
opacity: 1;
transform: translateX(0);
}
}

@keyframes bbb {
from {
}
to {
transform: translateY(-50px);
}
}

li {
background-color: aqua;
width: 100%;
height: 50px;
text-align: center;
&:nth-last-child(2n) {
background-color: pink;
}
}

/* 使用过渡 */
// .v-enter,
// .v-leave-to {
//   opacity: 0;
//   transform: translateX(-100%);
// }

// .v-enter-to,
// .v-leave {
//   opacity: 1;
//   transform: translateX(0);
// }
// .v-enter-active,
// .v-leave-active {
//   transition: all 1s linear;
// }

// 列表定位的排序过渡
.v-move {
transition: all 1s linear;
// animation: bbb 1s linear forwards;
}
// .v-move-to{
//   animation: aaa 1s linear forwards;
// }
/*使用动画 */
.v-enter-active {
animation: aaa 1s linear forwards;
// animation-delay: 1s;
}
.v-leave-active {
animation: aaa 1s linear reverse forwards;
}
</style> 

3、.v-move :列表的定位过渡

当在列表中插入或删除数据时,列表垂直方向应该有个过渡,.v-move样式可以实现

也可以自定义名字:通过 move-class 自定义别名,可以使用第三方动画库

15、keep-alive组件

vue及所有的组件正常情况下都要经历上面的四个过程,从创建到销毁,但是有一个特殊情况,主是这个组件吃了长生不死药,它不死,对于这种情况,我们叫上keep-alive
在vue的内部,有一个自带的组件叫 在这个组件下面的内容是不会被销毁掉的,也就是不 会执行 destory 这个过程

默认情况下,一个组件实例在被替换掉后会被销毁。这会导致它丢失其中所有已变化的状态 —— 当这个组件再一次被显示时,会创建一个只带有初始状态的新实例。

<KeepAlive> 是一个内置组件,它的功能是在多个组件间动态切换时缓存被移除的组件实例。

  1. include 只缓存谁
  2. exclude 除了谁不缓存,其他都缓存

include和exclude会根据组件的 name 选项进行匹配,所以组件如果想要条件性地被 KeepAlive 缓存,就必须显式声明一个 name 选项。

声明周期钩子函数:

<!-- 以英文逗号分隔的字符串 -->
<KeepAlive include="a,b">
  <component :is="view" />
</KeepAlive>

<!-- 正则表达式 (需使用 `v-bind`) -->
<KeepAlive :include="/a|b/">
  <component :is="view" />
</KeepAlive>

<!-- 数组 (需使用 `v-bind`) -->
<KeepAlive :include="['a', 'b']">
  <component :is="view" />
</KeepAlive>

实例

    <transition appear name="a" mode="out-in">
      <!-- 多个组件过渡 -->
      <!--动态组件使用 component,is的值为要显示组件名 -->
      <keep-alive include="List1">
      <component :is="componentId"></component>
      </keep-alive>
    </transition>

16、keep-alive组件的两个声明周期钩子

两个新的生命周期钩子

作用:keep-alive组件所独有的两个钩子,用于捕获被缓存组件的激活状态。

  • activated组件被激活时触发。
  • deactivated组件失活时触发。

Vue工程化:使用Vue脚手架

Vue工程化是为了应付在单页面开发里面的大型项目,它提全套的解决方案,使用Vue全家桶完成整个项目的开发,期间可能会使用到第三方的框架

在进行工程化开发的时候,Vue需要借用于webpack来实现工程化,因为SPA本质上面只有一个页面,所有页面跳都是虚拟的,通过组件来进行的,而这些组件也不能够直接在浏览器里面运行,所以需要借用于webpack来进行统译

webpack与vue的结合配置非常麻烦,对于初学者为说相当复杂,但是VUE提供一种便捷的方案去创建工程化的项目,这种东西叫“脚手架”

vue-cli(俗称:vue 脚手架)是 vue 官方提供的、快速生成 vue 工程化项目的工具。

特点:① 开箱即用,② 基于 webpack,③ 功能丰富且易于扩展,④ 支持创建 vue2 和 vue3

1、初始化脚手架

1、安装

npm install -g @vue/cli
# OR
yarn global add @vue/cli

2、创建一个vue项目:

vue create 项目名
# OR
vue ui

运行并开启一个服务命令:启动一个开发服务器 (基于 webpack-dev-server) 并附带开箱即用的模块热重载 (Hot-Module-Replacement)。

npm run serve
# OR
yarn serve

编译命令:会在 dist/ 目录产生一个可用于生产环境的包

yarn build

3、脚手架文件结构

├── node_modules
├── public
│ ├── : 页签图标
│ └── : 主页面
├── src
│ ├── assets: 存放静态资源
│ │ └──
│ │── component: 存放组件
│ │ └──
│ │── : 汇总所有组件
│ │── : 入口文件
├── .gitignore: git版本管制忽略的配置
├── : babel的配置文件
├── : 应用包配置文件
├── : 应用描述文件
├── :包版本控制文件

public公共目录,存放了一个文件,这是单页面的html文件
src目录是我们的程序员的目录 ,我们所开发的vue代码全在这里面
.这是babel的配置文件
 这是postcss的配置文件
.browserslistrc这个是用于配置浏览器列表兼容性的,以前是写在里面的
作为一个程序员,它主要使用的目录就是src目录

src目录结构

assets静态资源目录 ,在vue开发过程当 ,我们可以把图片,CSS以及SCSS等放在里面
components目录用于存放vue的组件(模块组件,也叫公共组件)
router文件夹里面存放路由文件,一般情况下,里面只有一个文件
store文件夹里面存放vuex的状态,action以及mapper,mutation等
views文件夹,存放vue的虚拟页面(或)
 项目当中的根组件,入口组件,当相于我们之前学习的<div ></div>
这个是整个程序的入口文件,也是webpack的入口文件,所有程序从这个文件启动

4、修改vue的默认配置

vue-cli基于webpack,vue脚手架把 配置文件隐藏了,不能直接查看

查看:使用vue inspect > 可以查看到Vue脚手架的默认配置。

修改:使用可以对脚手架进行个性化定制,配置详情见:配置参考 | Vue CLI ()

// 使用的是CommonJS规范
module.exports = defineConfig({
  transpileDependencies: true,
  publicPath: "./",//默认是网络地址,本地打不开,要想本地打开,设置为./或者为空
  // 语法检查关闭
  lintOnSave: false
})

5、重要的几个文件详情

1、 入口文件
/*该文件是项目的入口文件 */

// 引入vue
import Vue from 'vue'
// 引入app组件
import App from './'

//全局配置
Vue.config.productionTip = false

new Vue({
  render: h => h(App)//注册App组件
}).$mount('#app')//指定容器,挂载到哪个容器上
render
的render函数

来个不同版本 vue 的区别

与的区别:
    是完整版的Vue,包含:核心功能+模板解析器。
    是运行版的Vue,只包含:核心功能;没有模板解析器。
因为没有模板解析器,所以不能使用template配置项,需要使用render函数接收到的createElement函数去指定具体内容。

使用脚手架默认生成的入口文件引入的vue不是完整版的。

// 引入vue,使用es6模块化语法导入的不是完整版的vue,
import Vue from 'vue'

实际引入的文件:配置文件中"module"属性所指向的文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7h2xtHXU-1664507085286)(C:\Users\QuMing\Desktop\vue\资源\笔记图片\Snipaste_2022-08-30_12)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SKJBgTdL-1664507085287)(C:\Users\QuMing\Desktop\vue\资源\笔记图片\Snipaste_2022-08-30_12)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eKezfywI-1664507085288)(C:\Users\QuMing\Desktop\vue\资源\笔记图片\Snipaste_2022-08-30_12)]

vue包含两大块:vue核心+模板解析器

残缺版的vue不包含模板解析器,

// 1、render函数完整写法
  render(createElement) {
    console.log(typeof createElement);
    // 这个 createElement 回调函数能创建元素
    // 因为残缺的vue 不能解析 template,所以render就来帮忙解决这个问题
    // createElement 能创建具体的元素
    return createElement('h1', 'hello')
}



//2、因为 render 函数内并没有用到 this,所以可以简写成箭头函数。
new Vue({
  // render: h => h(App),
  render: (createElement) => {
    return createElement(App)
  }
}).$mount('#app')

//再简写:
 render: createElement => createElement(App)
最后把 createElement 换成 h 就完事了。

h()hyperscript 的简称——意思是“能生成 HTML (超文本标记语言) 的 JavaScript”。这个名字来源于许多虚拟 DOM 实现默认形成的约定。一个更准确的名称应该是 createVnode(),但当你需要多次使用渲染函数时,一个简短的名字会更省力。

2、汇总所有组件
/* 该文件汇总所有组件 */
<template>
  <div>
    <img src="./assets/">
    <!-- 使用组件 -->
    <School></School>
    <Student></Student>
  </div>
</template>

<script>
// 引入组件
import School from './components/'
import Student from './components/'
// 创建App组件并暴露,App组件汇总所有组件
export default {
  name: 'App',
  components: {
    // 注册组件,完整写法
    School: School,
    Student: Student
  }
}
</script>

<style>
</style>
3、主页面(容器所在的页面)
4<!DOCTYPE html>
<html lang="">
 <head>
   <meta charset="utf-8">
   <!-- 针对ie浏览器的一个特殊配置 ,含义是让ie浏览器以最高的渲染级别渲染页面-->
   <meta http-equiv="X-UA-Compatible" content="IE=edge">
   <meta name="viewport" content="width=device-width,initial-scale=1.0">
   <!-- 配置页签图标 -->
   <link rel="icon" href="<%= BASE_URL %>">
   <!-- 配置网页标题 -->
   <title><%=  %></title>
 </head>
 <body>
   <!--当浏览器不支持js时 noscript标签中的元素就会渲染 -->
   <noscript>
     <strong>We're sorry but <%=  %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
   </noscript>
   <!-- 容器 -->
   <div id="app"></div>
   <!-- built files will be auto injected -->
 </body>
</html>

vue3

Vue3.0新特性

Composition Api (最核心)
v-model更改
v-for的key节点上的使用情况更改
v-if和v-for对同一元素的优先级更高
ref内部v-for不再注册引用数组
功能组件只能使用普通函数创建
异步组件需要使用defineAsyncComponent创建方法
所有插槽都通过$slots
在destroyed生命周期的选项已更名为unmounted
在beforeDestroy生命周期的选项已更名为beforeUnmount

Vue3.0优缺点

优点:

  • 将Vue内部的绝大部分api对外暴露,使Vue具备开发大型项目的能力,例如compile编译api等

  • webpack的treeshaking(tree shaking 是 DCE 的一种方式,它可以在打包时忽略没有用到的代码。)支持度友好

  • 使用Proxy进行响应式变量定义,性能提高1.2~2倍

  • ssr快了2~3倍

  • 可在Vue2.0中单独使用composition-api插件,或者直接用它开发插件

  • 对typescript支持更加友好

  • 面向未来:对于尤雨溪最近创新的vite开发服务器(舍弃webpack、底层为Koa框架的高性能开发服务器),直接使用Vue3.0语法

    缺点:

  1. vue3将不再支持IE11,Vue 在 版本仍然支持 IE11,如果你想使用类似 Vue 3 的新特性,可以等等 Vue 2.7 版本。这次的 RFC 宣布,将会对 2.7 版本做向后兼容,移植 的部分新功能,以保证两个版本之间相似的开发体验。

  2. 对于习惯了Vue2.0开发模式的开发者来说,增加了心智负担,对开发者代码组织能力有体验,同时也是能力提升的机会吧,

    特别喜欢Vue作者的而设计初心:让开发者随着框架一起成长

体验Vue3.0的四种姿势

通过CDN:6

通过 Codepen的浏览器 playground

脚手架 Vite

npm init vite-app hello-vue3 # OR yarn create vite-app hello-vue3

尤大开发的新工具vite,下一代前端开发与构建工具,原来是利用浏览器现在已经支持ES6的import;遇到import会发送一个http请求去加载对应的文件,vite拦截这些请求,做预编译,就省去了webpack冗长的打包事件,提升开发体验。

vue3工程结构

1、文件


// 1、引入的不再是Vue构造函数,引入的是一个createApp工厂函数(类型express的express工厂函数)
// import Vue from 'vue'//vue2
import { createApp } from 'vue';//vue3

import App from './';//导入全局组件

// vue2
// new Vue({
//   render: h => h(App)//注册App组件
// }).$mount('#app')//指定容器,挂载到哪个容器上

// vue3
//2、创建应用实例,类似vm,但app比vm轻。
const app =  createApp(App)
//3、指定接管的容器
app.mount('#app')

文件

<template>
<!-- vue3的模板中可以没有根标签 -->
<nav>
 <router-link to="/">Home</router-link> |
 <router-link to="/about">About</router-link>
</nav>
  <router-view/>
   </template>
   
  <style lang="scss">
  
</style>

常用的Composition API

1、setup()

  • setup为vue3的一个新的配置项
  • 是一个函数是所有组合api的表演舞台,组件中所用到的:数据、方法等等,均要配置在setup中

注意点:

  1. 尽量不要与配置混用
    • 配置(data、methos、computed…)中可以访问到setup中的属性、方法。
    • 但在setup中不能访问到配置(data、methos、computed…)。
    • 如果有重名, setup优先。
  2. setup不能是一个async函数,因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合)

参数:props:父组件传过来的参数;值为对象,,注意只有组件内部声明接收了属性,此对象才有值。

context:Setup上下文对象,上下文对象暴露了其他一些在 setup 中可能会用到的值(attrs,slots,emit,expose)

  • attrs(剩余参数): 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于 this.$attrs
  • slots(剩余插槽): 收到的插槽内容, 相当于 this.$slots
  • emit(触发自定义事件): 分发自定义事件的函数, 相当于 this.$emit

return:1、返回一个对象时,则对象中的属性、方法, 在模板中均可以直接使用。(重点关注!)
2、返回一个渲染函数,则可以自定义渲染内容。(了解)

setup()执行时机:在beforeCreate之前就执行一次,this是undefined,如何获取组件实例?:getCurrentInstance()

<script>
import { ref } from 'vue';
export default {
  name: 'App',

    setup (props, context) {
    let name = 'zs'
    let age = 18;
    // 上下文context
    // 透传 Attributes(非响应式的对象,等价于 $attrs)
      
    ()

    // 插槽(非响应式的对象,等价于 $slots)
    ()

    // 触发事件(函数,等价于 $emit)
    ()

    // 暴露公共属性(函数)
    ()

    // 返回一个对象
    return { name, age}
    // 返回一个渲染函数
    return () => h('div', '你好')
  }
}
</script>

2、ref()

作用: 定义一个响应式的数据,ref() 将传入参数的值包装为一个带 .value 属性的 ref 对象:

  • 语法: const xxx = ref(initValue)
    • 创建一个包含响应式数据的引用对象(reference对象,简称ref对象)
    • JS中操作ref数据:
    • 模板中读取ref数据: 不需要.value,直接:<div>{{xxx}}</div>
  • 备注:
    • 接收的数据可以是:基本类型、也可以是对象类型。
    • 基本类型的数据:响应式依然是靠()getset完成的。
    • 对象类型的数据:内部 “ 求助 ” 了Vue3.0中的一个新函数—— reactive函数。
<template>
<!--- 模板中读取ref数据: 不需要.value,直接:<div>{{xxx}}</div> -->
<h1>{{name}}{{age}}</h1>
<h1>{{user}}</h1>
<button @click="fun">按钮</button>
</template>
<script>
import { ref } from 'vue';
export default {
name: 'App',
setup (props, context) {
 // 当包装的是基本数据类型时ref()返回的是一个响应式数据引入,使用的是()的get与set完成的。
 let name = ref('zs')
 let age = ref(18);
 // 当包装的是对象类型时,ref()返回的是一个proxy代理对象,借助了reactive(),使用的是es6的Proxy代理
 let user = ref({ name: 'zs', age: 30 })
 // JS中操作数据ref: 
 function fun () {
    = 'ww'
    = 20
    = "小龙女"
    = '18'
 }
 return { name, age, fun, user }
}
}
</script>

ref作为标签属性时

在vue3中也可以通过ref来获取元素.

但是在vue3中没有$refs这些东西.

3、reactive().

  • 作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用ref函数)
  • 语法:const 代理对象= reactive(源对象)接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象)
  • reactive定义的响应式数据是“深层次的”。
  • 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作。
<template>
  <!--- 模板中读取数据: 不需要.value,直接:<div>{{xxx}}</div> -->
  <h1>{{name}}{{age}}</h1>
  <h1>ref{{}}--{{}}</h1>
  <h1>reactive{{}}--{{}}</h1>
  <button @click="fun">按钮</button>
</template>

<script>
import { reactive, ref } from 'vue';
export default {
  name: 'App',
  setup (props, context) {
    let user2 = reactive({
      name: '张三',
      age: 30,
      hoppy: ['唱', '跳', 'rap']
    })

    function fun () {
       = '李四'
       = 20
      [0] = '篮球'
    }

    return { name, age, fun, user1, user2 }
  }
}
</script>

4、Vue3.0中的响应式原理

的响应式
  • 实现原理:

    • 对象类型:通过()对属性的读取、修改进行拦截(数据劫持)。

    • 数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。

      Object.defineProperty(data, 'count', {
          get () {}, 
          set () {}
      })
      
  • 存在问题

    • 新增属性、删除属性, 界面不会更新。
    • 直接通过下标修改数组, 界面不会自动更新。
Vue3.0的响应式
  • 实现原理:

    • 通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。

    • 通过Reflect(反射): 对源对象的属性进行操作。

    • MDN文档中描述的Proxy与Reflect:

      • Proxy:/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy

      • Reflect:/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect

        new Proxy(data, {
        	// 拦截读取属性值,
              //在get方法里面,有三个参数,分别如下
              // 1. target:被代理的对象,
              // 2. properName:当前操作的属性,(当前操作属性是根据当前操作的属性来变化的)
          	  // 3. receiver:代理人
            get (target, prop) {
            	return Reflect.get(target, prop)
            },
            // 拦截设置属性值或添加新属性
              //在set方法里面,有三个参数,分别如下
              // 1. target:被代理的对象
              // 2. properName代表当前操作的属性
              // 3. 要对当前属性设置的值
            set (target, prop, value) {
            	return Reflect.set(target, prop, value)
            },
            // 拦截删除属性
            deleteProperty (target, prop) {
            	return Reflect.deleteProperty(target, prop)
            }
        })
        
        proxy.name = 'tom'  
        

5、computed()

  • 与中computed配置功能一致

  • 写法

    <template>
      <input type="text" ="a">
      <input type="text" ="b">
      <h1>{{c}}</h1>
    </template>
    
    <script>
    import { computed, reactive, ref } from 'vue';
    // import Demo1 from './components/'
    export default {
      name: 'App',
      setup (props, context) {
        let a = ref('');
        let b = ref('')
    
        // computed()计算属性函数
        // 1、简写方法,
        // let c = computed(() =>  + )
        // 2、完整写法
        let c = computed({
          get () { return  +  },
          set () { }
        })
    
        return { a, b, c }
      }
    }
    </script>
    

6、watch()

  • 与中watch配置功能一致
  • 两个小“坑”:
    • 监视ref定义的响应数据式,如果ref是对象,监视的是对象的属性时,无法监视到对象属性的变化
      • 解决方法:①开启深度监视。②监视的时候带上value
    • 监视reactive定义的响应式数据时:监视的是对象本事oldValue无法正确获取、强制开启了深度监视(deep配置失效)。
    • 监视reactive定义的响应式数据中某个属性时(这个值也是一个对象):deep配置有效。
<template>
  <button @click="fun">修改值</button>
  <span>{{}}</span>
  <span>{{}}</span>
  <span>{{}}</span>
</template>

<script>
import { computed, reactive, ref, watch } from 'vue';
// import Demo1 from './components/'
export default {
  name: 'App',
  setup (props, context) {
    let a = ref(12);
    let b = ref('你好')

    let p = reactive({
      name: "张三",
      age: 19,
      hoppy: ['唱', '跳', 'rap']
    })
    // computed()计算属性函数
    // 简写方法,
    // let c = computed(() =>  + )
    // 完整写法
    let c = computed({
      get () { return  +  },
      set () { }
    })
    // 情况1:监视ref数据。watch()有三个参数,最后一个参数为一个配置项
    watch(a, (newV, oldV) => { ('数据变化了', newV, oldV); })

    // 情况2:监视多个ref数据
    watch([a, b], (nv, ov) => { ('数据变化了', nv, ov); })

    // 情况3:监视reactive数据
    // 默认强制开启了深度监视,deep配置失效,oldValue不能正常监视
    watch(p, (nv, ov) => { ('数据变化了', nv, ov); }, { deep: true })

    // 情况4:监视reactive数据的某个属性
    // 默认强制开启了深度监视,deep配置失效
    watch(() => , (nv, ov) => { ('数据变化了', nv, ov); }, {})


    // 情况4:监视reactive数据的多个属性
    // 默认强制开启了深度监视,deep配置失效
    watch([() => , () => ], (nv, ov) => { ('数据变化了', nv, ov); }, {})

    // 特殊情况,监视reactive数据的某个属性,这个属性也是对象或者是数组,此时deep有效
    watch(() => , (nv, ov) => { ('数据变化了', nv, ov); }, { deep: true })


    function fun () {
       += '~'
      ++
      [0] = '学习'
    }
    return { a, b, c, p, fun }
  }
}
</script>


7、watchEffect()

  • watch的套路是:既要指明监视的属性,也要指明监视的回调。

  • watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。

  • watchEffect有点像computed:

    • 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。
    • 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。
    //watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。
    watchEffect(()=>{
        const x1 = sum.value
        const x2 = person.age
        console.log('watchEffect配置的回调执行了')
    })
    

8、provide() 与 inject(),依赖注入

  • 作用:实现祖与后代组件间通信

  • 套路:父组件有一个 使用provide('数据名',数据) 来提供数据,后代组件有一个 inject('数据名') 使用这些数据

  • 应用场景:我们可以在入口组件上使用provide来声明一些公共的信息,然后在任何组件中都可以使用inject来调用

  • 具体写法:

    1. 祖组件中:

      setup(){
      	......
          let car = reactive({name:'奔驰',price:'40万'})
          provide('car',car)
          ......
      }
      
    2. 后代组件中:

      setup(props,context){
      	......
          const car = inject('car')
          return {car}
      	......
      }
      

9、nextTick

页面重绘完成以后触发的函数

<template>
  <div>
    <button @click="change" ref="btn">{{ msg }}</button>
  </div>
</template>

<script>
import { nextTick, ref } from "vue";
export default {
  setup() {
    let msg = ref("hello");
    let btn = ref(null);
    function change() {
      // 更新数据
       = "world";
      ();
      // 数据的更新引发了视图重绘,且重绘完成
      nextTick(() => {
        ();
      });
    }
    return { msg, btn, change };
  },
};
</script>

10、vue3生命周期钩子

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NCyL8qSy-1664507085289)(C:\Users\QuMing\Desktop\vue\资源\笔记图片\)]

11 自定义hook函数

  • 什么是hook?—— 本质是一个函数,把setup函数中使用的Composition API进行了封装。

  • 类似于中的mixin。

  • 自定义hook的优势: 复用代码, 让setup中的逻辑更清楚易懂。

12 toRef()和toRefs()

单文件组件<script setup组合式API语法糖

1、基本使用

要启用该语法,需要在 <script> 代码块上添加 setup属性,<script>标签中代码会被编译成组件 setup() 函数的内容

与普通的 <script> 只在组件被首次引入的时候执行一次不同,<script setup> 中的代码会在每次组件实例被创建的时候执行

<script setup>
...
</script>

2、顶层的绑定会被暴露给模板

当使用 <script setup> 的时候,任何在 <script setup> 中声明的顶层的绑定 (包括变量,函数声明,以及 import 导入的内容) 都能在模板中直接使用:

import 导入的内容也会以同样的方式暴露。可以在模板表达式中直接使用导入的 暴露的函数,而不需要通过 methods 选项来暴露它:

<template>
  <div class="app">
    <h1>{{name1}}</h1>
    <h1>{{age1}}</h1>
    <h1>{{name2}}</h1>
    <h1>{{age2}}</h1>
    <h1>{{}}</h1>
    <h1>{{}}</h1>
    <ul>
      <li v-for="(e,i) in " :key="i">
        {{e}}
      </li>
    </ul>
    <button @click="fun">点击</button>
  </div>
</template>

<script setup>
import { reactive, ref } from 'vue'
 //变量
let name1 = '张三'
let age1 = 18
// ref 在模板中使用的时候会自动解包:
let name2 = ref('王五')
let age2 = ref(20)
let p = reactive({
  name: '赵六',
  age: 22,
  hoppy: ['唱', '跳', 'rap']
})
//函数
function fun () {
  ("触发了方法", , )
}

// 不需要return返回变量和函数了,顶层的绑定 (包括变量,函数声明,以及 import 导入的内容) 都能在模板中直接使用
</script>

3、使用组件

<template>
<!-- 使用组件 -->
    <Demo1/>
</template>
<script setup>
   //导入其他组件,其他组件可以不用写name 
import Demo1 from './components/'
</script>

4、动态组件

<template>
  <div class="app">
    <!-- 使用组件 -->
    <Demo1 />
    <!-- 使用动态组件 -->
    <button @click="fun">切换组件</button>
    <component :is="flage ? Demo1:Demo2 "></component>
  </div>
</template>

<script setup>
import { reactive, ref } from 'vue'
import Demo1 from './components/'
import Demo2 from './components/'

let flage = ref(true)
function fun () {
   = !
}
</script>

5、defineProps()defineEmits()

  • defineProps 接收与 props 选项相同的值,defineEmits 接收与 emits 选项相同的值。(用法和vue2一样)
  • definePropsdefineEmits 他们不需要导入,且会随着 <script setup> 的处理过程一同被编译掉。

1、defineProps()

<template>
  <div class="demo1">
    <h1>{{name}}</h1>
    <h1>{{age}}</h1>
  </div>
</template>

<script setup>
//不需要导入 defineProps
import { ref } from 'vue'
// 接收父组传过来的数据
// 1、数组写法,
const props = defineProps(['name', 'age'])

// 2、对象写法
const props = defineProps({
  name: String,
  age: Number
})

// 3、对象写法的拓展
const props = defineProps({
  name: {
    type: String,
  },
  age: {
    type: Number
  }
})
</script>

2、defineEmits()

<template>
  <div class="demo1">
     //使用emit('事件名',参数),来触发父组件的自定义事件 
    <button @click="emit('sayHell','123')">触发父组件自定义事件</button>
  </div>
</template>

<script setup>
//不需要导入defineEmits
import { ref } from 'vue'
//不能使用this, 接收所有传给该组件的事件
const emit = defineEmits(['sayHell'])
</script>

6、defineExpose

作用:


父组件 :

```vue
<template>
	<Child ref="child" />
</template>

<script setup>
import { ref, onMounted } from 'vue'
import Child from '@/components/'

let child = ref(null);

onMounted(() => {
	(); // Child Components
	(); // 123
})
</script>

7、useSlots()useAttrs()

剩余参数和剩余插槽

useSlotsuseAttrs 是真实的运行时函数,它的返回与 等价

<template>
  <div class="demo1">
    <h1>{{name}}</h1>
    <h1>{{attrs}}</h1>
  </div>
</template>
<script setup>
import { ref, useAttrs, useSlots } from 'vue'
const emit = defineEmits(['sayHell'])
const pros = defineProps(['name'])

// 相当于vue2的剩余参数, defineProps()中没有接收的参数
const attrs = useAttrs()

// 相当vue2的剩余插槽,当父组件中使用了子组件中没有定义的插槽时,这个插槽就会在useSlots()中
const slots = useSlots()
</script>

8、 <script setup>与普通的 <script> 一起使用

vue2路由

  1. 理解: 一个路由route)就是一组映射关系(key - value),多个路由需要路由器router)进行管理。
  2. 前端路由:key是路径,value是组件。

1、基本使用

1、安装vue-router:不推荐手动安装,建议和vue-cli脚手架一起安装,不需要配置了

注意:vue2安装vue-router3版本,vue3安装vue-router4版本

npm install vue-router@4
or
yarn add vue-router@4

2、在文件中,导入路由插件,并将路由插件挂载到全局在全局都可以使用路由插件,

import Vue from 'vue'
import App from './'

Vue.config.productionTip = false
// 1、引入路由器插件
import VueRouter from 'vue-router'
// 2、将路由器插件挂载到全局
Vue.use(VueRouter)


// 3、引入路由器,默认找到router文件夹下的
import router from './router'

new Vue({
  // 4、路由器配置项
  router,
    
  render: h => h(App)
}).$mount('#app')

3、在router文件中创建文件,编写路由器,配置各个路由

/* 该文件专门用于创建整个应用的路由器 */
import Vue from 'vue'

// 引入组件
import About from '../components/'
import Home from '../components/'

//  引入路由器插件
import VueRouter from 'vue-router'

//创建一个路由器实例,并配置各个路由 ,并将路由器暴露出去
export default new VueRouter({
  routes: [

    // 第一组路由,每个路由都需要映射到一个组件
    {
      path: '/about',//路由路径
      // name: 'home',
      component: About//path对应的组件
    },

    // 第二组路由,
    {
      path: '/home',
      // name: 'home',
      component: Home//path对应的组件
    },
  ]
})

4、

  • 写切换标签,在入口组件中,使用 Home 实现切换,,
  • 并使用 指定路由对应组件展示的位置
<template>
  <div >
    <nav>
      <!--router-link 前端路由标签,最后会转化为a标签,不能直接写a标签-->
      <!-- to要跳转的路径,应和路由器中各个路由中的patn保持一致 -->

      <router-link to="/home">Home</router-link> |
      <router-link to="/about">About</router-link>
    </nav>
    <!-- 指定路由对应的组件出现的位置 -->
    <router-view />
  </div>
</template>

<style lang="scss">
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}

nav {
  padding: 30px;

  a {
    font-weight: bold;
    color: #2c3e50;
    //点击router-link时会将该class添加到当前router-link上
    &.router-link-exact-active {
      color: #42b983;
    }
  }
}
</style>

2、几个注意点

  • 路由组件通常存放在pages文件夹,一般组件通常存放在components文件夹。
  • 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。
  • 每个组件都有自己的** r o u t e 属性 ∗ ∗ ,里面存储着自己的路由信息。整个应用只有一个 r o u t e r ,可以通过组件的 route属性**,里面存储着自己的路由信息。整个应用只有一个router,可以通过组件的 route属性,里面存储着自己的路由信息。整个应用只有一个router,可以通过组件的router属性获取到。

3、嵌套路由(多级路由)

1、配置嵌套规则,在路由器文件夹router的文件中配置路由规则

import Vue from 'vue'

// 引入组件
import About from '../pages/About'
import AboutP1 from '../pages/AboutP1'
import AboutP2 from '../pages/AboutP2'
import Home from '../pages/Home'
import HomeP1 from '../pages/HomeP1'
import HomeP2 from '../pages/HomeP2'
//  引入路由器插件
import VueRouter from 'vue-router'


export default new VueRouter({
  routes: [

    // 第一组路由,每个路由都需要映射到一个组件
    {
      path: '/about',
      // name: 'home',
      component: About,//path对应的组件
      // 二级路由,子路由
      children: [
        {
          // 除了一级路由的path需要加/,子路由不需要加/
          path: 'aboutp1',
          component: AboutP1
        },
        {
          path: 'aboutp2',
          component: AboutP2
        }
      ]
    },

    // 第二组路由,
    {
      path: '/home',
      // name: 'home',
      component: Home,
      children: [
        {
          path: 'homep1',
          component: HomeP1
        },
        {
          path: 'homep2',
          component: HomeP2
        }
      ]
    },

2、在父路由组件中写跳转标签

//About路由组件
<template>
  <div>
    <h1>About</h1>
    <router-link to="/about/aboutp1">p1</router-link>
    <router-link to="/about/aboutp2">p2</router-link>
    <router-view></router-view>
  </div>
</template>
//Home路由组件
<template>
  <div>
    <h1>Home</h1>
    <router-link to="/home/homep1">p1</router-link>
    <router-link to="/home/homep2">p1</router-link>
    <router-view />
  </div>
</template>

4、路由的query参数

三级路由后,一般不会创建多个路由组件了,而是通过创建一个路由组件,通过路由参数来动态的展示数据,

query传参两个写法:

1、to的字符串写法

2、to的对象写法

   <router-link :to="{
          //path:子路由路径
          path:'/home/homep1/homepchild',
          //query:给子路由传递的参数
          query:{
            name:,
            age:
          }
        }">

案例:

2、父路由传参:

<template>
  <div>
    <ul>
      <li v-for="(item, index) in user" :key="index">
        <!-- 1、跳转并携带query参数,to的字符串写法 -->
        <!-- <router-link :to="`/home/homep1/homepchild?name=${}&age=${}`">
			{{}}--{{}}
    	</router-link> -->

        <!-- 2、跳转并携带query参数,to的对象写法 -->
        <router-link :to="{
          path:'/home/homep1/homepchild',
          query:{
            name:,
            age:
          }
        }">
          {{}}--{{}}
        </router-link>
      </li>
    </ul>
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'HomeP1',
  data () {
    return {
      user: [
        { id: 1, name: 'zs', age: 18 },
        { id: 2, name: 'ls', age: 19 },
        { id: 3, name: 'ww', age: 12 },
      ]
    }
  }
}
</script>

子路由接收参数:

<template>
  <div>
    <!-- this.$route中右路由所有信息包括参数,问号传参通过query获取-->
    <h1>姓名:{{$}}</h1>
    <h1>年龄:{{$}}</h1>
  </div>
</template>

<script>
export default {
  name: 'HomePchild',
}
</script>

5、命名路由

作用:可以简化路由的跳转。

1、给路由命名

{
	path:'/demo',
	component:Demo,
	children:[
		{
			path:'test',
			component:Test,
			children:[
				{
                   name:'hello' //给路由命名
					path:'welcome',
					component:Hello,
				}
			]
		}
	]
}

2、简化跳转,简化后只能使用对象写法。

<!--简化前,需要写完整的路径 -->
<router-link to="/demo/test/welcome">跳转</router-link>

<!--简化后,直接通过名字跳转,只能使用对象写法 -->
<router-link :to="{name:'hello'}">跳转</router-link>

<!--简化写法配合传递参数 -->
<router-link :to="{
		name:'hello',
		query:{
		   id:666,
         title:'你好'
		}
	}"
>跳转</router-link>

重定向

分类:

  • path重定向,
  • name重定向
  • 函数重定向
  {
    path: '/',
    name: '/',
  	// path重定向到index
    redirect: '/index',
    // name重定向
    redirect: 'index',
    // 函数重定向
    redirect: () => {
      return { path: '/index' }
    }
  },
  {
    path: '/index',
    name: 'index',
    component: () => import('../views/HomeView')
  },

6、路由的params参数

1、配置路由,配置params参数占位符


    {
      path: '/home',//lujin
      component: Home,//path对应的组件
      children: [
        {
          path: 'homep1',
          component: HomeP1,
          children: [
            {
               //使用占位符表示这个位置是参数,:name和:age都是占位符
              path: 'homepchild/:name/:age',
              component: HomePchild,
              // 给路由起名,简化路由跳转时的书写
              name: 'homepchildname',
            }
          ]
        }
      ]
    },

2、传递参数,

两种写法:

  • 字符串写法,
  • 对象写法,传递params参数时不能使用path,必须使用name

特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!

<template>
  <div>
    <ul>
      <li v-for="(item, index) in user" :key="index">
        <!-- 1、跳转并携带params参数,to的字符串写法 -->
        <router-link :to="`/home/homep1/homepchild/${}/${}`">
          {{}}--{{}}
        </router-link>
        <!-- 2、跳转并携带query参数,to的对象写法 ,使用name代替path来跳转,简化深层路径的书写-->
        <router-link :to="{
          name:'homepchildname',
          params:{
            name:,
            age:
          }
        }">
          {{}}--{{}}
        </router-link>
      </li>
    </ul>
    <router-view></router-view>
  </div>
</template>
<script>
export default {
  name: 'HomeP1',
  data () {
    return {
      user: [
        { id: 1, name: 'zs', age: 18 },
        { id: 2, name: 'ls', age: 19 },
        { id: 3, name: 'ww', age: 12 },
      ]
    }
  }
}
</script>

3、接收参数

<template>
  <div>
    <h1>姓名:{{$}}</h1>
    <h1>年龄:{{$}}</h1>
  </div>
</template>

在vue3使用button传参

1、传参

<button @click="$({ path:'/demo1/1',query:{name:'zs'} })"> 传参</button>

2、接收

<template>
  <div>
    <h1>{{$}}</h1>
    <h1>{{$}}</h1>
  </div>
</template>

7、路由的props配置项

作用:让路由组件更方便的收到参数,获取参数时不用写** r o u t e . p a r a m s 或 ∗ ∗ 或** route.params

有三种写法:

  • props值为对象,该对象中所有的key-value的组合最终都会通过props传给HomePchild组件(参数直接在路由传,缺点写死了)
  • props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给HomePchild组件(参数在父组件中传,只能传params形式参数)
  • props值为函数,该函数返回的对象中每一组key-value都会通过props传给HomePchild组件(什么参数都能传,参数在路由中传)

1、配置路由

{
      path: '/home',
      component: Home,
      children: [
        {
          path: 'homep1',
          component: HomeP1,
          // 三级路由
          children: [
            {
              path: 'homepchild/:name/:age',
              component: HomePchild,
              // 给路由起名,简化路由跳转时的书写
              name: 'homepchildname',

//第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给HomePchild组件(参数直接在路由传,缺点写死了)
              props: { name: 'zz', age: 30 },

//第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给HomePchild组件(参数在父组件中传,只能传params形式参数)
               props: true,

//第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给HomePchild组件(什么参数都能传,参数在路由中传)
         //$route为:为当前路由组件的路由对象,路由的所有信息在这个对象上
              // 1、传递query参数
              // props ($route) {
              //   return {
              //     name: $,
              //     age: $,
              //   }
              // }

              //2、传递parase参数 
              props ($route) {
                return {
                  name: $route.params.name,
                  age: $route.params.age,
                }
              }
            }
          ]
        }
      ]
    }

父组件传递参数

<template>
  <div>
    <ul>
      <li v-for="(item, index) in user" :key="index">
        <!-- 1、跳转并携带params参数,to的字符串写法 -->
        <!-- <router-link :to="`/home/homep1/homepchild/${}/${}`">
          {{}}--{{}}
        </router-link> -->

        <!-- 2、跳转并携带query参数,to的对象写法 ,使用name代替path来跳转,简化深层路径的书写-->
        <router-link :to="{
          name:'homepchildname',
          params:{
            name:,
            age:
          }
        }">
          {{}}--{{}}
        </router-link>
      </li>
    </ul>
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'HomeP1',
  data () {
    return {
      user: [
        { id: 1, name: 'zs', age: 18 },
        { id: 2, name: 'ls', age: 19 },
        { id: 3, name: 'ww', age: 12 },
      ]
    }
  }
}
</script>

接收参数:使用props配置项接收

<template>
  <div>
    <!-- 简化了参数的接收,不用写$或$ -->
    <h1>姓名:{{name}}</h1>
    <h1>年龄:{{age}}</h1>
  </div>
</template>

<script>
export default {
  name: 'HomePchild',
  props: ['name', 'age']
}
</script>

8、<router-link>的replace属性

原理是浏览器bom的locatoin对象的replace()方法,替换浏览器地址栏,网页会跳到新的地址

  • 作用:控制路由跳转时操作浏览器历史记录的模式

  • 浏览器的历史记录有两种写入方式:分别为push和replace,push是追加历史记录,replace是替换当前记录。路由跳转时默认为push查看:replace方法与href的区别

  • 如何开启replace模式:<router-link replace …>News

          <!-- 历史记录为replace模式完整写法 -->
          <router-link to="/home" :replace="true">Home</router-link> |
          <!-- replace模式简写写法 -->
          <router-link to="/about" replace>About</router-link>
    

9、编程式路由导航

作用:不借助<router-link> 实现路由跳转,让路由跳转更加灵活

使用路由器对象$router的 go()、push()、replace()、back()、forward()方法可以实现跳转,

组件默认是push()可以添加属性replace,改成replace()方式,但是只能实现这两种,

如果是其他标签的话需要借助$router路由对象,这个对象挂载到了Vue上了全局都可以使用,

<template>
  <div>
    <ul>
      <li v-for="(item, index) in user" :key="index">
        <!-- 2、跳转并携带query参数,to的对象写法 ,使用name代替path来跳转,简化深层路径的书写-->
        <router-link :to="{
          name:'homepchildname',
          params:{
            name:,
            age:
          }
        }">
          {{}}--{{}}
        </router-link>
          <!--使用button进行跳转 -->
        <button @click="push(item)">push查看</button>
        <button @click="replace(item)">replace查看</button>
          
      </li>
    </ul>
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'HomeP1',
  data () {
    return {
      user: [
        { id: 1, name: 'zs', age: 18 },
        { id: 2, name: 'ls', age: 19 },
        { id: 3, name: 'ww', age: 12 },
      ]
    }
  },
  methods: {
    push (item) {
      // 接祖$router对象原型上的跳转方法push()
      (this.$router);
      this.$({
        name: 'homepchildname',
        params: {
          name: ,
          age: 
        }
      })
    },
    replace (item) {
      this.$({
        name: 'homepchildname',
        params: {
          name: ,
          age: 
        }
      })
    }
  },
}
</script>

10、缓存路由组件

作用:让不展示的路由组件保持挂载,不被销毁。

    <!-- 缓存路由组件,include只缓存哪个组件,值为组件的名称 -->
    <keep-alive include="HomeP1">
      <router-view />
    </keep-alive>

12、路由元信息meta

有时,希望将任意信息附加到路由上,如过渡名称、谁可以访问路由等。路由配置对象的meta属性来实现,并且它可以在路由地址和导航守卫上都被访问到

13、路由守卫(导航守卫)

就是vue-router的跳转的一套钩子函数

作用:对路由进行权限控制,

分类:全局守卫(前置和后置)、独享守卫、组件内守卫

1、全局守卫

所有路由都能用得到的钩子 得写在router/中

1、全局前置守卫:beforeEach()初始化的时候被调用(一上来的时候),每次路由切换之前被调用

vue2中有第三个参数next:放行函数,根据条件放行,
vue3,使用return false来阻止默认行为

/* 该文件专门用于创建整个应用的路由器 */
import Vue from 'vue'

// 引入组件
import About from '../pages/About'
import AboutP1 from '../pages/AboutP1'
import AboutP2 from '../pages/AboutP2'
import Home from '../pages/Home'
import HomeP1 from '../pages/HomeP1'
import HomeP2 from '../pages/HomeP2'
import HomePchild from '../pages/HomePchild'

//  引入路由器插件
import VueRouter from 'vue-router'

//创建一个路由器实例,并配置各个路由 ,并暴露出去
const router = new VueRouter({
  routes: [
    {
      path: '/home',
      component: Home,
      children: [
        {
          path: 'homep1',
          component: HomeP1,


          // 路由元信息mata,保存过渡名称、路由权限条件等等
          // 保存路由权限。当前路由只有name为zs 才能访问,其他路由没有写路由元信息不进行权限鉴定,
          meta: { name: 'zs' },

          // 三级路由
          children: [
            {
              path: 'homepchild/:name/:age',
              component: HomePchild,
              name: 'homepchildname',
              props ($route) {
                return {
                  name: $route.params.name,
                  age: $route.params.age,
                }
              }
            }
          ]
        },
        {
          path: 'homep2',
          component: HomeP2,
        }
      ]
    },
  ]
})

// 全局前置路由守卫beforeEach()----初始化的时候被调用(一上来的时候),每次路由切换之前被调用。

// 参数是一个回调函数,回调函数的参数:1、to:跳到哪个路由,2、from:从哪个路由来,
//  vue2中有第三个参数next:放行函数,更具条件放行,
// vue3,使用return false来阻止默认行为
router.beforeEach((to, from, next) => {
  console.log("@@");
  // 先通过路由元信息判断哪些路由需要进行权限判断,再判断权限是否正确
  //如果路由中有name属性就说明需要进行权限鉴定,否则为undefind,自动类型转换为false
  if (to.meta.name) {
    // 如果当前访问的用户时有权限,则放行
    
    if (localStorage.getItem('name') === 'zz') {
      next()
    } else {
      console.log("没有权限访问");
    }
  } else {
    //不进行权限判断的直接放行
    next()
  }


})

export default router


2、全局后置守卫

回调函数中,vue2中只有连个参数,vue3有三个参数,第三个 为error,错误信息

//2、全局后置路由守卫afterEach()----初始化的时候被调用(一上来的时候),每次路由切换之后被调用。一般修用来改网页的标题
((to, from) => {
  ();
  if () {
     = 

  } else {
     = 'vue_test'
  }
})

2、(局部守卫)路由独享的守卫

beforeEnter()

某个具体的路由才能用得到的钩子,写在具体的路由中,已配置的形式存在

//子路由
children: [
        {
          path: 'homep1',
          component: HomeP1,
          // 路由元信息,保存过渡名称、路由权限条件等等
          // 保存路由权限。当前路由只有name为zs 才能访问,其他路由没有写路由元信息不进行权限鉴定,
          meta: {
            name: 'zs',
            title: '页面2'
          },
          // 路由独有守卫(局部守卫),执行和全局前置守卫一样,
          beforeEnter :(to, from, next) =>{
            if (to.meta.name) {
              // 如果当前访问的用户时有权限,则放行

              if (localStorage.getItem('name') === 'zZ') {
                next()
              } else {
                console.log("没有权限访问");
              }
            } else {
              //不进行权限判断的直接放行
              next()
            }
          },

3、组件内守卫

注意事项:

1、beforeRouteEnter()中不能获取组件实例 this ,因为当守卫执行前,组件实例还没被创建,不过,你可以通过传一个回调给 next来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。

beforeRouteEnter (to, from, next) {
  next(vm => {
    // 通过 `vm` 访问组件实例
  })
}
//进入守卫:通过路由规则,进入该组件时被调用
beforeRouteEnter (to, from, next) {
    // 1、在渲染该组件的对应路由被 confirm 前调用
    // 不!能!获取组件实例 `this`
    // 因为当守卫执行前,组件实例还没被创建
},
//离开守卫:通过路由规则,离开该组件时被调用
beforeRouteLeave (to, from, next) {
}
<script>
export default {
  name: 'HomeP1',
  //进入守卫:通过路由规则,进入该组件时被调用
  beforeRouteEnter (to, from, next) {
    ('通过路由规则进入了改组件');
    next()
  },
  //离开守卫:通过路由规则,离开该组件时被调用
  beforeRouteLeave (to, from, next) {
    ('通过路由规则离开该组件了');
    next()
  }
}
</script>

14、路由器的两种工作模式

对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。

hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。

hash模式:

地址中永远带着#号,不美观 。
若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
兼容性较好。

history模式:

地址干净,美观 。
兼容性和hash模式相比略差。
应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。

例子:

const router = new VueRouter({
//配置路由器的工作模式
  mode: 'hash',
  routes: [
  ...
  ]

vue3的不同

import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router'

// 创建路由器
const router = createRouter({
  // 路由的模式/样式,和vue2不同的时不是在路由配置项中使用
  // history: createWebHashHistory(),//hash
  history: createWebHistory(),//非hash模式
  // 路由表
  routes
})

15、路由懒加载(按需加载组件)

当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就会更加高效。

Vue Router 支持开箱即用的动态导入,这意味着你可以用动态导入代替静态导入:

1、一次加载一个组件

  {
    path: '/about',
    name: 'about',
    // 使用import()函数,动态导入组件,路由匹配到时才导入组件使用,而不是静态一上来全部带入,影响页面的加载
    component: () => import(/* webpackChunkName: "about" */ '../views/')
  }

2、

一次加载多个组件(很少用)

  {
    path: '/demo1',
    name: 'demo1',
    // 按需加载多个组件,加载多个文件时,在路由组件展示时需要指定<router-view />展示的名称。
    components: {
      Demo1: () => import('../components/Demo1'),
      Demo2: () => import('../components/Demo2'),
    }
  },
<template>
  <nav>
    <router-link to="/demo1">Demo1</router-link> |
    <router-link to="/about">About</router-link> |
    <router-link to="/">Home</router-link>
  </nav>

  <router-view name="Demo1" />
  <router-view name="Demo2" />
</template>

16、更改路由的样式

直接修改a标签的样式,在选中时,会动态的添加两个类名,router-link-exact-active router-link-active,我们可以直接操作这两个类名,也可以手动指定 active-class=“aaa”

17.vue3如何获取router/route实例

在vue2可以直接通过this获取router/route,

在vue3获取的方法:

1、先获取组件实例:getCurrentInstance(),通过组件实例来获取

<script>
import { getCurrentInstance } from 'vue'
export default {
  setup () {
    const { proxy } = getCurrentInstance()
    //proxy.$root.$router和proxy.$root.$route
    console.log(proxy.$root.$router);
    console.log(proxy.$root.$route);

    return {proxy}
  }
}
</script>

2、模板中可直接使用** r o u t e r ∗ ∗ 和 router**和 routerroute

<template>
  <div>
    <h1>{{$router}}</h1>
    <h1>{{$route}}</h1>
  </div>
</template>

3、可以通过vue-router的composition api去获取实例(推荐)

<script>
import { getCurrentInstance } from 'vue'
//使用vue-router的composition api
import { useRoute, useRouter } from 'vue-router'
export default {
  setup () {
    const route = useRoute()
    const router = useRouter()


    return { proxy }
  }
}
</script>

18、过渡动效,

在vue3/vue-router4下使用keep-alive

旧版的语法已经失效

<keep-alive>
    <router-view v-if="$"></router-view>
</keep-alive>
<router-view v-if="!$"></router-view>

新版官方推荐使用
、 和

transition 和 keep-alive 现在必须通过 v-slot API 在 RouterView 内部使用:

<router-view v-slot="{ Component }">
  <transition>
    <keep-alive>
      <component :is="Component" />
    </keep-alive>
  </transition>
</router-view>

官方推荐的方式是只有keep-alive模式,却没有不需要keep-alive的使用方法

vuex全局状态管理

概念

在Vue中实现集中式状态(数据管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信

什么时候使用vuex:

  • 1、多个组件依赖于同一状态(数据)
  • 2、来自不同组件的行为需要变更同一状态

总结一句话,全局共享的数据就使用vuex

使用场景:用户的状态购物车,订单信息存储在vuex中,任何时候都可以使用这些信息。

安装

建议和vue-cli一起安装,版本默认对应好了。

使用

全局挂载插件:通过在入口文件中挂载store,从而实现在实例对象中产生一个$store对象,但vue3中this不再指向组件实例了

所以可以使用同获取路由器实例一样,动态引入。当一个vue加载了vuex的store以后,在内部就会有一个** s t o r e ∗ ∗ 的属性,使用 store**的属性,使用 store的属性,使用store在模板中操作仓库,也可以从vuex中解构一个useStore方法,在js中操作仓库

vue2:中通过this.$store获取仓库

在模板中:$store

在js中:this.$store

vue3:从vuex中解构一个useStore方法,调用该方法获取store仓库,或者先获取实例在获取$.store(不建议)

在模板中:$store

在js中:import {useStore} from 'vuex'
// 获取store仓库
let  store = useStore()

store仓库内容

初始化数据、配置actions、配置mutations,操作文件

所有的环境都是在store仓库中,默认找的时该文件夹中的文件

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
// 1、准备actions-用于响应组件中的动作
actions: {
},

// 2、准备mutations-用于操作数据(state)
mutations: {
},

// 3、准备state-用于存储数据
state: {
 sum: 0
},

getters: {
},
modules: {
}
})

vuex原理图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KKkBpqtj-1664507085290)(C:\Users\QuMing\Desktop\vue\资源\笔记图片\Snipaste_2022-09-07_01)]

分析:

vue组件中使用this.$store获取vuex仓库这个仓库存储了Action对象,Mutations对象,State对象

this.$(‘方法名’, 参数),调用Action对象中对应的方法并传递参数,Action对象中的方法addodd (context, value)都有两个参数

context和value,value:传过来的参数,context对象是一个迷你版的$store,身上有commit(‘addodd’, value)方法,调用方法后会触发Mutations对象中对应的方法addodd (state, value),该方法有两个参数,value:Action对象传过来的参数,stateState对象,里面有共享数据,

vue2组件中调用

组件中读取vuex中的数据:$

组件中修改vuex中的数据:$('action中的方法名',数据)$('mutations中的方法名',数据)

methods: {
add () {
this.$store.dispatch('jia', this.n)
},
jian () {
this.$store.dispatch('jian', this.n)
},
addodd () {
this.$store.dispatch('addodd', this.n)
},
addtime () {
this.$store.dispatch('addtime', this.n)
}
},

2、store仓库的配置

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
// 准备actions-用于响应组件中的动作
actions: {

// 没有判断操作可以直接跳过actions
jia (context, value) {
console.log('actions执行');
context.commit('jia', value)
},
jian (context, value) {
context.commit('jian', value)
},
addodd (context, value) {
if (context.state.sum % 2 != 0) {
  context.commit('addodd', value)
}
else console.log('不是奇数');
},
addtime (context, value) {
setTimeout(() => {
  context.commit('addtime', value)
}, 300)
}
},
// 准备mutations-用于操作数据(state)
mutations: {

jia (state, value) {
console.log('mutations执行');
state.sum += value
},
jian (state, value) {
console.log('mutations执行');
state.sum -= value
},
addodd (state, value) {
console.log('mutations执行');
state.sum += value
},
addtime (state, value) {
state.sum += value
}
},
 
// 准备state-用于存储数据
state: {
sum: 0
},
getters: {
},
modules: {
}
})

直接跳过Action对象处理

还可以直接在组件中调用commit()方法,跳过Action对象的处理。

备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写dispatch,直接编写commit

methods: {
add () {
// 直接在组件中调用commit()方法
 this.$store.commit('jia', this.n)
   },
   jian () {
 this.$store.commit('jian', this.n)
 },
   addodd () {
 this.$store.dispatch('addodd', this.n)
 },
   addtime () {
 this.$store.dispatch('addtime', this.n)
 }
   },

getters的使用

getters-用于将state中的数据进行加工,类似计算属性


  // 准备state-用于存储数据
  state: {
    sum: 1
  },

  // getters-用于将state中的数据进行加工,类似计算属性
  getters: {
      //getters中的函数都有一个参数state
    bigsum (state) {
        //必须有返回值
      return state.sum * 10
    }
  }

四个map方法的使用

先引入

import {mapState, mapGetters, mapActions, mapMutations} from 'vuex'

1、**mapState方法:**用于帮助我们映射state中的数据为计算属性

  // 准备state-用于存储数据
  state: {
    sum: 1,
    name: 'zs',
    age: 18
  },




computed: {
    //借助mapGetters生成计算属性:bigSum(对象写法)
     ...mapState({ name: 'name', age: 'age' }),

    //借助mapGetters生成计算属性:bigSum(数组写法)要求计算属性名和state中的属性值同名
    ...mapState(['name', 'age']),
},

2、**mapGetters方法:**用于帮助我们映射getters中的数据为计算属性

  // getters=用于将state中的数据进行加工,类似计算属性
  getters: {
    bigsum (state) {
      return state.sum * 10
    },
    newName (state) {
      return state.name + ':'
    },
    newAge (state) {
      return state.age + 2
    }
  },




computed: {
    //借助mapGetters生成计算属性:bigSum(对象写法)
    ...mapGetters({ newName: 'newName', newAge: 'newAge' }),

    //借助mapGetters生成计算属性:bigSum(数组写法)
    ...mapGetters(['newName', 'newAge'])
}

3、**mapActions方法:**用于帮助我们生成与actions对话的方法,即:包含$(xxx)的函数

    <button @click="add(n)">+</button>
    <button @click="jian(n)">-</button>

   


  methods: {  
    addodd () {
      this.$store.dispatch('addodd', this.n)
    },
    addtime () {
      this.$store.dispatch('addtime', this.n)
    },

    // 借助mapMutations,用于帮助我们生成与actions对话的方法,即:包含$(xxx)的函数
    ...mapActions({ addodd: 'addodd', addtime: 'addtime' }),
    // 数组形式
    ...mapActions(['addodd', 'addtime'])

  },

4、**mapMutations方法:**用于帮助我们生成与mutations对话的方法,即:包含$(xxx)的函数

    <button @click="addodd(n)">当前求和为奇数再相加</button>
    <button @click="addtime(n)">等一等再相见</button>  


  methods: {
    add () {
      this.$store.commit('add', this.n)
    },
    jian () {
      this.$store.commit('jian', this.n)
    },

    // 将上面两个方法转换为下面的方法了
    // add为methods中的方法名,add为$(xxx)中的方法名
    // 用于帮助我们生成与mutations对话的方法,即:包含$(xxx)的函数
    ...mapMutations({ add: 'add', jian: 'jian' }),
    // 两个方法同名时可以写成数组形式
    ...mapMutations(['add', 'jia'])
  }

的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信

什么时候使用vuex:

  • 1、多个组件依赖于同一状态(数据)
  • 2、来自不同组件的行为需要变更同一状态

总结一句话,全局共享的数据就使用vuex

使用场景:用户的状态购物车,订单信息存储在vuex中,任何时候都可以使用这些信息。

安装

建议和vue-cli一起安装,版本默认对应好了。

使用

全局挂载插件:通过在入口文件中挂载store,从而实现在实例对象中产生一个$store对象,但vue3中this不再指向组件实例了

所以可以使用同获取路由器实例一样,动态引入。当一个vue加载了vuex的store以后,在内部就会有一个** s t o r e ∗ ∗ 的属性,使用 store**的属性,使用 store的属性,使用store在模板中操作仓库,也可以从vuex中解构一个useStore方法,在js中操作仓库

vue2:中通过this.$store获取仓库

在模板中:$store

在js中:this.$store

vue3:从vuex中解构一个useStore方法,调用该方法获取store仓库,或者先获取实例在获取$.store(不建议)

在模板中:$store

在js中:import {useStore} from 'vuex'
// 获取store仓库
let  store = useStore()

store仓库内容

初始化数据、配置actions、配置mutations,操作文件

所有的环境都是在store仓库中,默认找的时该文件夹中的文件

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
// 1、准备actions-用于响应组件中的动作
actions: {
},

// 2、准备mutations-用于操作数据(state)
mutations: {
},

// 3、准备state-用于存储数据
state: {
 sum: 0
},

getters: {
},
modules: {
}
})

vuex原理图

[外链图片转存中…(img-KKkBpqtj-1664507085290)]

分析:

vue组件中使用this.$store获取vuex仓库这个仓库存储了Action对象,Mutations对象,State对象

this.$(‘方法名’, 参数),调用Action对象中对应的方法并传递参数,Action对象中的方法addodd (context, value)都有两个参数

context和value,value:传过来的参数,context对象是一个迷你版的$store,身上有commit(‘addodd’, value)方法,调用方法后会触发Mutations对象中对应的方法addodd (state, value),该方法有两个参数,value:Action对象传过来的参数,stateState对象,里面有共享数据,

vue2组件中调用

组件中读取vuex中的数据:$

组件中修改vuex中的数据:$('action中的方法名',数据)$('mutations中的方法名',数据)

methods: {
add () {
this.$store.dispatch('jia', this.n)
},
jian () {
this.$store.dispatch('jian', this.n)
},
addodd () {
this.$store.dispatch('addodd', this.n)
},
addtime () {
this.$store.dispatch('addtime', this.n)
}
},

2、store仓库的配置

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
// 准备actions-用于响应组件中的动作
actions: {

// 没有判断操作可以直接跳过actions
jia (context, value) {
console.log('actions执行');
context.commit('jia', value)
},
jian (context, value) {
context.commit('jian', value)
},
addodd (context, value) {
if (context.state.sum % 2 != 0) {
  context.commit('addodd', value)
}
else console.log('不是奇数');
},
addtime (context, value) {
setTimeout(() => {
  context.commit('addtime', value)
}, 300)
}
},
// 准备mutations-用于操作数据(state)
mutations: {

jia (state, value) {
console.log('mutations执行');
state.sum += value
},
jian (state, value) {
console.log('mutations执行');
state.sum -= value
},
addodd (state, value) {
console.log('mutations执行');
state.sum += value
},
addtime (state, value) {
state.sum += value
}
},
 
// 准备state-用于存储数据
state: {
sum: 0
},
getters: {
},
modules: {
}
})

直接跳过Action对象处理

还可以直接在组件中调用commit()方法,跳过Action对象的处理。

备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写dispatch,直接编写commit

methods: {
add () {
// 直接在组件中调用commit()方法
 this.$store.commit('jia', this.n)
   },
   jian () {
 this.$store.commit('jian', this.n)
 },
   addodd () {
 this.$store.dispatch('addodd', this.n)
 },
   addtime () {
 this.$store.dispatch('addtime', this.n)
 }
   },

getters的使用

getters-用于将state中的数据进行加工,类似计算属性


  // 准备state-用于存储数据
  state: {
    sum: 1
  },

  // getters-用于将state中的数据进行加工,类似计算属性
  getters: {
      //getters中的函数都有一个参数state
    bigsum (state) {
        //必须有返回值
      return state.sum * 10
    }
  }

四个map方法的使用

先引入

import {mapState, mapGetters, mapActions, mapMutations} from 'vuex'

1、**mapState方法:**用于帮助我们映射state中的数据为计算属性

  // 准备state-用于存储数据
  state: {
    sum: 1,
    name: 'zs',
    age: 18
  },




computed: {
    //借助mapGetters生成计算属性:bigSum(对象写法)
     ...mapState({ name: 'name', age: 'age' }),

    //借助mapGetters生成计算属性:bigSum(数组写法)要求计算属性名和state中的属性值同名
    ...mapState(['name', 'age']),
},

2、**mapGetters方法:**用于帮助我们映射getters中的数据为计算属性

  // getters=用于将state中的数据进行加工,类似计算属性
  getters: {
    bigsum (state) {
      return state.sum * 10
    },
    newName (state) {
      return state.name + ':'
    },
    newAge (state) {
      return state.age + 2
    }
  },




computed: {
    //借助mapGetters生成计算属性:bigSum(对象写法)
    ...mapGetters({ newName: 'newName', newAge: 'newAge' }),

    //借助mapGetters生成计算属性:bigSum(数组写法)
    ...mapGetters(['newName', 'newAge'])
}

3、**mapActions方法:**用于帮助我们生成与actions对话的方法,即:包含$(xxx)的函数

    <button @click="add(n)">+</button>
    <button @click="jian(n)">-</button>

   


  methods: {  
    addodd () {
      this.$store.dispatch('addodd', this.n)
    },
    addtime () {
      this.$store.dispatch('addtime', this.n)
    },

    // 借助mapMutations,用于帮助我们生成与actions对话的方法,即:包含$(xxx)的函数
    ...mapActions({ addodd: 'addodd', addtime: 'addtime' }),
    // 数组形式
    ...mapActions(['addodd', 'addtime'])

  },

4、**mapMutations方法:**用于帮助我们生成与mutations对话的方法,即:包含$(xxx)的函数

    <button @click="addodd(n)">当前求和为奇数再相加</button>
    <button @click="addtime(n)">等一等再相见</button>  


  methods: {
    add () {
      this.$store.commit('add', this.n)
    },
    jian () {
      this.$store.commit('jian', this.n)
    },

    // 将上面两个方法转换为下面的方法了
    // add为methods中的方法名,add为$(xxx)中的方法名
    // 用于帮助我们生成与mutations对话的方法,即:包含$(xxx)的函数
    ...mapMutations({ add: 'add', jian: 'jian' }),
    // 两个方法同名时可以写成数组形式
    ...mapMutations(['add', 'jia'])
  }

相关文章