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收集表单数据
收集表单数据:
若:text框,则v-model收集的是value值,用户输入的就是value值。
若:radio为,则v-model收集的是value值,且要==给标签配置value值==。
若:为checkbox
没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值)
配置input的value属性:
v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值)
v-model的初始值是数组,那么收集的的就是value组成的数组
v-model的三个修饰符:
- lazy:失去焦点再收集数据
- number:输入字符串转为有效的数字
- 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模型:
M:模型(Model) :对应 data 中的数据
V:视图(View) :模板
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>
九、事件修饰符
小技巧:修饰符可以连续写
功能性修饰符
- stop:停止事件冒泡
- prevent:阻止标签的默认行为
- once:事件只触发一次
- self:当前事件只有自己触发时才执行回调
- capture:事件捕获阶段执行
- passive:事件默认行为立即执行,无需等待回调
- native修饰符,给组件绑定事件时需要加native修饰符
针对于鼠标的
.left .right .middle
针对于键盘的修饰符
.enter .tab(必须配合keydown使用) .delete (捕获“删除”和“退格”键) .esc .space .up .down .left .right
针对于系统的修饰符(用法特殊)
- 配合keyup使用:按下修饰符键的同时,再按下其他键。随后释放其他键,事件才会触发。
- 配合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样式三种写法
- 字符串写法: 适用于动态的只设置一个class样式
- 数组写法:适用于动态的一次设置多个class样式
- 对象写法:适用用于不确定用不用要设置的样式
<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两种写法
- 内联样式对象写法
- 内联样式数组写法
<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原理流程
- 编译:Vue 模板被编译为了渲染函数:即用来返回虚拟 DOM 树的函数。这一步骤可以通过构建步骤提前完成,也可以通过使用运行时编译器即时完成。
- 挂载:运行时渲染器调用渲染函数,遍历返回的虚拟 DOM 树,并基于它创建实际的 DOM 节点。这一步会作为,响应式副作用执行、,因此它会追踪其中所用到的所有响应式依赖。
- 更新:当一个依赖发生变化后,副作用会重新运行,这时候会创建一个更新后的虚拟 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?
- 做好使用每条数据的唯一标识作为key,比如id、手机号、身份证号,学号等唯一值。
- 如果不存在对数据的头部添加、头部删除等造成数组索引重排的操作,仅用于渲染列表展示,将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
作用:向其所在的节点中渲染文本内容。
与插值语法的区别: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>
十七、生命周期
- beforeCreate(创建前):数据监测(getter和setter)和初始化事件还未开始,此时 data 的响应式追踪、event/watcher 都还没有被设置,也就是说不能访问到data、computed、watch、methods上的方法和数据。
- created(创建后):实例创建完成,实例上配置的 options 包括 data、computed、watch、methods 等都配置完成,但是此时渲染得节点还未挂载到 DOM,所以不能访问到 $el属性。
- beforeMount(挂载前):在挂载开始之前被调用,相关的render函数首次被调用。此阶段Vue开始解析模板,生成虚拟DOM存在内存中,还没有把虚拟DOM转换成真实DOM,插入页面中。所以网页不能显示解析好的内容。
- mounted(挂载后):在el被新创建的 vm.$el(就是真实DOM的拷贝)替换,并挂载到实例上去之后调用(将内存中的虚拟DOM转为真实DOM,真实DOM插入页面)。此时页面中呈现的是经过Vue编译的DOM,这时在这个钩子函数中对DOM的操作可以有效,但要尽量避免。一般在这个阶段进行:开启定时器,发送网络请求,订阅消息,绑定自定义事件等等
- beforeUpdate(更新前):响应式数据更新时调用,此时虽然响应式数据更新了,但是对应的真实 DOM 还没有被渲染(数据是新的,但页面是旧的,页面和数据没保持同步呢)。
- updated(更新后) :在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。此时 DOM 已经根据响应式数据的变化更新了。调用时,组件 DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。
- beforeDestroy(销毁前):实例销毁之前调用。这一步,实例仍然完全可用,this 仍能获取到实例。在这个阶段一般进行关闭定时器,取消订阅消息,解绑自定义事件。
- 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中使用组件的三大步骤:
- 定义组件(创建组件)
使用(options)创建(const school = (options) 可简写为:const school = options),其中options和new Vue(options)时传入的那个options几乎一样,但也有点区别;
区别如下:
- el不要写,为什么? ——— 最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。
- data必须写成函数,为什么? ———— 避免组件被复用时,数据存在引用关系。
- 注册组件
- 局部注册:靠new Vue的时候传入components选项,键是组件名,值是创建组件时的引用名字
- 全局注册:靠(‘组件名’,组件的引用)
- 使用组件(写组件标签)
在容器中使用组件,写组件标签
<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
- 组件本质是一个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、子组件接收数据:
- 第一种方式(只接收):
props:['name']
//1、简单接收 props: ['name', 'age'],
- 第二种方式(限制类型):
props:{name:String}
// 2、接收参数:对类型限制 props: { name: String, age: Number }
- 第三种方式(限制类型、限制必要性、指定默认值)
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数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先。
值为对象的选项,例如
methods
、components
和directives
,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。声明周期函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。
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
:指令绑定的前一个值,仅在update
和componentUpdated
钩子中可用。无论值是否改变都可用。expression
:字符串形式的指令表达式。例如v-my-directive="1 + 1"
中,表达式为"1 + 1"
。arg
:传给指令的参数,可选。例如v-my-directive:foo
中,参数为"foo"
。modifiers
:一个包含修饰符的对象。例如:中,修饰符对象为
{ foo: true, bar: true }
。vnode
:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。oldVnode
:上一个虚拟节点,仅在update
和componentUpdated
钩子中可用。
案例
局部指令
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代理服务器的配置:通过
中的
选项来配置
方式一:
说明:
- 优点:配置简单,请求资源时直接发给前端(8080)即可。
- 缺点:不能配置多个代理,不能灵活的控制请求是否走代理。
- 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器 (优先匹配前端资源)
const { defineConfig } = require('@vue/cli-service') // 使用的是CommonJS规范 module.exports = defineConfig({ transpileDependencies: true, publicPath: "./",//默认是网络地址,本地打不开,要想本地打开,设置为./或者为空 // 语法检查关闭 lintOnSave: false, // 开启代理服务器,端口号为资源服务器端口, devServer: { proxy: 'http://localhost:8889' } })
方式二:
说明:
- 优点:可以配置多个代理,且可以灵活的控制请求是否走代理。
- 缺点:配置略微繁琐,请求资源时必须加前缀。
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()
语法:
this.$nextTick(回调函数)
作用:在下一次 DOM 更新结束后执行其指定的回调。
它会判断当前的代码是否可以在本生命周期执行,如果不可以会顺延到下一个生命周期,如果还不行则继续顺延,直到可以执行为止
什么时候用:当改变数据后,要基于更新后的新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组件
特点:
- 在使用transition标签时标签内只能同时显示一个元素
- transition本身不会渲染出元素节点
- transition组件可以给元素在某些特定的时刻添加上特定的属性,从而实现动画效果(6个类)
- transition组件可以使用mode标签属性的过渡模式
- transition的name属性值默认的是v可以通过name修改属性名前缀
注意:
transition
组件不只可以写过渡,也可以写动画,- 不给transition 使用name属性取名字,六个类名默认以v开头
1、概念:Vue 在插入、更新或者移除 DOM 时,提供多种不同方式的应用过渡效果
2、原理:当插入或删除包含在
transition
组件中的元素时,Vue 将会做以下处理:
- 自动嗅探目标元素是否应用了 CSS 过渡或动画,如果是,在恰当的时机添加/删除 CSS 类名。
- 如果过渡组件提供了 JavaScript 钩子函数,这些钩子函数将在恰当的时机被调用。
- 如果没有找到 JavaScript 钩子并且也没有检测到 CSS 过渡/动画,DOM 操作 (插入/删除) 在下一帧中立即执行。(注意:此指浏览器逐帧动画机制,和 Vue 的
nextTick
概念不同)3、可以过渡条件:Vue 提供了
transition
的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡
- 条件渲染 (使用
v-if
)- 条件展示 (使用
v-show
)- 动态组件
- 组件根节点
4、过渡的类名:
- 元素进入的样式:
- v-enter:进入的起点
- v-enter-active:进入过程中
- v-enter-to:进入的终点
- 元素离开的样式:
- v-leave:离开的起点
- v-leave-active:离开过程中
- 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、列表过渡
使用组件
特点:
- transition-group可以给对个元素进行过渡或动画,可以显示多个元素
- transition-group默认渲染一个元素,可以通过
tag
属性 指定为其他元素。- 过渡模式不可用,因为我们不再相互切换特有的元素。
- 内部元素总是需要提供唯一的
key
attribute 值。- 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>
是一个内置组件,它的功能是在多个组件间动态切换时缓存被移除的组件实例。
- include 只缓存谁
- 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语法
缺点:
vue3将不再支持IE11,Vue 在 版本仍然支持 IE11,如果你想使用类似 Vue 3 的新特性,可以等等 Vue 2.7 版本。这次的 RFC 宣布,将会对 2.7 版本做向后兼容,移植 的部分新功能,以保证两个版本之间相似的开发体验。
对于习惯了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中
注意点:
- 尽量不要与配置混用
- 配置(data、methos、computed…)中可以访问到setup中的属性、方法。
- 但在setup中不能访问到配置(data、methos、computed…)。
- 如果有重名, setup优先。
- 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>
- 备注:
- 接收的数据可以是:基本类型、也可以是对象类型。
- 基本类型的数据:响应式依然是靠
()
的get
与set
完成的。- 对象类型的数据:内部 “ 求助 ” 了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来调用
具体写法:
祖组件中:
setup(){ ...... let car = reactive({name:'奔驰',price:'40万'}) provide('car',car) ...... }
后代组件中:
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一样)defineProps
和defineEmits
他们不需要导入,且会随着<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()
剩余参数和剩余插槽
useSlots
和useAttrs
是真实的运行时函数,它的返回与和
等价
<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路由
- 理解: 一个路由(route)就是一组映射关系(key - value),多个路由需要路由器(router)进行管理。
- 前端路由: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**和 router∗∗和route
<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对象传过来的参数,state:State对象,里面有共享数据,
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对象传过来的参数,state:State对象,里面有共享数据,
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']) }