目录 § ⇧
- 1. 进阶属性概览 §
- 2. 复习响应式原理 §
- 3.
Computed计算属性(数据) § - 4.
Watch监听属性(数据) § - 5.
Directive指令(资源) § - 6.
Mixin混入(组合) § - 7.
Extends继承、扩展(组合) § - 8.
Provide和Inject(组合) § - 9.
Vue3.x响应式系统 APIwatchEffect§ - 10. $3 §
- 11. $3 §
1. 进阶属性概览 §
computed计算属性- 不需要加括号
- 会根据依赖是否变化来缓存
watch监听- 一旦
data变化,就执行的函数 options.watch用法this.$watch用法deep,immediate含义
- 一旦
directives指令- 内置指令
v-if / v-for / v-bind /v-on - 自定义指令,如
v-focus - 指令是为了减少重复的
DOM操作
- 内置指令
mixin混入- 重复三次之后的出路
- 混入
V.S.全局混入 - 选项自动合并
- 混入就是为了减少重复的构造选项
extends继承Vue.extend- 觉得用了
mixin还是重复,自己写了View,继承Vue - 还可以预先定义其他构造选项
- 继承就是为了减少重复的构造选项
- 不用
ES6的extends的原因
provide/inject- 祖孙组件通信
- 后代组件通信
- 全局变量,局部的全局变量
2. 复习响应式原理 § ⇧
- 当传入
options.data给Vue之后data会被Vue监听,对象被Vue篡改settergetter- 会被
Vue实例vm代理 - 每次对
data的读写会被Vue监控 Vue会在data变化时更新UI
data变化时除了更新UI外,其他的操作
3. Computed 计算属性(数据) § ⇧
用途 ⇧
- 根据其他属性计算出来的属性就是计算属性
- 语法是函数,当做属性使用,默认读取方法返回值
- 计算属性文档
computed文档- 示例代码1:用户名展示
main.js
|
|
- 用户可能没有填昵称,优先展示昵称,没有昵称其次展示邮箱,再次展示手机
- 页面中多处展示昵称、邮箱和手机
DRY原则,灵活面对需求变化:先展示手机,最后是邮箱- 将所有的数据的可能变化,写在一个函数中(
displayName() {return ...}必须有返回值),按需调用(当做属性,不用写括号){{displayName}} - 需要多处展示,多处写入计算属性即可
- 好处就是可以让一些事根据其他属性计算而来的属性变成一个属性,形式是一个方法,但必须当做属性来用,默认去读取方法的返回值
可读可写
main.js
|
|
- 示例代码2:列表展示
- 需求 点击性别展示符合的内容;点击全部,展示全部
如何给三个按钮添加事件处理函数
- 思路一:点击后改
userList,不可改变原始数据 - 复制一份数据用来展示
displayUsers: [] - 思路二:使用
computed
思路一:点击后改userList ⇧
-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55let id = 0 const createUser = (name, gender) => { id += 1 return {id: id, name: name, gender: gender} } new Vue({ data() { return { userList: [ createUser("方方", "男"), createUser("圆圆", "女"), createUser("小新", "女"), createUser("小葵", "女"), ], // DRY 重复代码 使用computed重构displayUsers displayUsers: [], } }, // created 实例出现在内存中,而未出现在页面中 // 复制一份原始数据用来展示 created() { this.displayUsers = this.userList }, template: ` <div> <button @click="showAll">全部</button> <button @click="showMale">男</button> <button @click="showFemale">女</button> <ul> <!-- <li v-for="u in userList" :key="u.id"> --> <li v-for="u in displayUsers" :key="u.id"> {{u.name}} - {{u.gender}} </li> </ul> </div> </div> `, methods: { set() { console.log('set') this.displayName = '圆圆' }, // DRY 重复代码 使用computed重构 showMale() { this.displayUsers = this.userList.filter(user => user.gender === '男') }, showFemale() { this.displayUsers = this.userList.filter(user => user.gender === '女') }, showAll() { this.displayUsers = this.userList }, } }).$mount('#app')
思路二:使用computed ⇧
- 根据
user和gender筛选结果result - 封装函数
result = f(users, gender){...},通过计算得到结果 - 在
methods的方法中判断this.gender - 在
template点击事件中,直接赋值gender,自动计算显示内容 -
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111let id = 0 const createUser = (name, gender) => { id += 1 return {id: id, name: name, gender: gender} } new Vue({ data() { return { userList: [ createUser("方方", "男"), createUser("圆圆", "女"), createUser("小新", "女"), createUser("小葵", "女"), ], // DRY 重复代码 使用computed重构displayUsers /* displayUsers: [],*/ // 初始化gender,根据gender筛选结果result gender: '', } }, // created 实例出现在内存中,而未出现在页面中 // 创建时 复制一份原始数据用来展示 // DRY 重复代码 使用computed重构displayUsers时不需要 /* created() { this.displayUsers = this.userList }, */ computed: { displayUsers() { // 哈希映射 const hash = { male: '男', female: '女' } const {userList, gender} = this if(gender === '') { return userList } /* else if(gender === 'male') { return userList.filter(user => user.gender === '男') }else if(gender === 'female') { return userList.filter(user => user.gender === '女') } */ /* else if(gender === 'male' || gender === 'female') { return userList.filter(user => user.gender === hash[gender]) } */ else if(typeof gender === 'string') { return userList.filter(user => user.gender === hash[gender]) }else{ throw new Error('gender的值是意外的值') } }, }, template: ` <div> <!-- <button @click="showAll">全部</button> <button @click="showMale">男</button> <button @click="showFemale">女</button> --> <!-- 不需要加this 可省略methods中的三个方法 直接改变this.gender --> <!-- <button @click="gender =''">全部</button> <button @click="gender ='male'">男</button> <button @click="gender ='female'">女</button> --> <button @click="setGender('')">全部</button> <button @click="setGender('male')">男</button> <button @click="setGender('female')">女</button> <ul> <!-- <li v-for="u in userList" :key="u.id"> --> <li v-for="(u, index) in displayUsers" :key="index"> {{u.name}} - {{u.gender}} </li> </ul> </div> </div> `, methods: { // 在template中改变属性的选择条件,自动计算 改变内容 /* // DRY 重复代码 使用computed重构 showMale() { // 用 computed 计算属性 根据不同的gender显示userList内容 来代替 // this.displayUsers = this.userList.filter(user => user.gender === '男') this.gender = 'male' }, showFemale() { // 用 computed 计算属性 根据不同的gender显示userList内容 来代替 // this.displayUsers = this.userList.filter(user => user.gender === '女') this.gender = 'female' }, showAll() { // 用 computed 计算属性 根据不同的gender显示userList内容 来代替 // this.displayUsers = this.userList this.gender = '' }, */ // setGender 代替以上三个函数 setGender(string) { this.gender = string } } }).$mount('#app') 根据
gender返回userList的不同部分computed使得button的逻辑更加清晰,按钮直接改变gender,而不用通过绑定函数,计算对应的值,全部在computed中计算
Computed 缓存原理 ⇧
- 如果依赖的属性没有变化,计算属性就不会重新计算
getter/setter默认不会做缓存,Vue做了特殊处理示例代码3 非
Vue源码1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51// Computed 缓存原理 let obj1 = { 姓: "高", 名: "圆圆", get 姓名() { console.log('计算了一次,将姓与名相加') return this.姓 + this.名; }, set 姓名(xxx){ this.姓 = xxx[0] this.名 = xxx.slice(1) }, age: 18 }; console.log(obj1.姓名) console.log(obj1.姓名) console.log(obj1.姓名) console.log('----------------') // 精髓 // 杠精说:为什么每次都要重新计算?如果计算很复杂,不就很浪费时间吗? // 那就缓存一下吧 // 缓存是啥?就是哈希表 const cache = {} // {'高':{'圆圆': '高圆圆'}} let obj2 = { 姓: "高", 名: "圆圆", get 姓名() { // 由于对象不支持 ['高','圆圆'] 数组作为 key,只能变通一下 if(this.姓 in cache && this.名 in cache[this.姓]){ console.log('有缓存,不再计算') return cache[this.姓][this.名] } // 如果 cache[this.姓] 不存在,就赋值为 {} // 如果 cache[this.姓] 存在,就赋值为它自己(相当于什么都不做) cache[this.姓] = cache[this.姓] || {} // 保底值 cache[this.姓][this.名] = this.姓 + this.名 console.log('计算了一次,将姓与名相加') return cache[this.姓][this.名]; }, set 姓名(xxx){ this.姓 = xxx[0] this.名 = xxx.slice(1) }, age: 18 }; console.log(obj2.姓名) console.log(obj2.姓名) console.log(obj2.姓名)勿重复在
data中声明数据,否则报错The computed property "xxx" is already defined in data
4. Watch 监听属性(数据)§ ⇧
用途 ⇧
- 当数据变化时,执行一个函数
- 又称“侦听器”
- 当需要在数据变化时执行异步或开销较大的操作时,来响应数据的变化
示例代码4:撤销
|
|
- 官方描述:每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把 “接触”过的数据 property 记录为依赖
- 之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染
- 即所有
methods中操作数据的改变都会被watch监听到
异步
watch.n的函数是异步的,异步更新队列- 搜索
vue.js set data without watch:just do a conditional check inside the watcher callback - 异步接口
this.$nextTick(() => {}, 0)相当于ES2017 async/await语法糖
示例代码5:模拟
computed不建议使用,优先使用computed
|
|
immediate:是否在第一次渲染时运行watch函数监听数据初始值- 注意
watch监听data第二层数据user.email的写法 watch一般不监听初始的值:从无到有,即第一次数据是空值- 除非设置
watch内部对象的属性immediate: true使得第一次渲染也触发watch - 都是在数据变化的时候执行一个函数
computed着重依赖之间的变化和缓存watch着重变化后执行函数,而不是得到的结果
数据变化 ⇧
- 示例代码6
obj原本是{a: 'a'},变为obj = {a: 'a'},地址变了,而obj.a的值没变- 简单类型看值,复杂类型(对象看地址)
- 对象的属性变了,但对象的地址没变,
Vue认为对象没变 - 和
===的规则相同
watch 类型语法
watch: { [key: string]: string | Function | Object | Array }- 常用
watch: {fn(new, old) {...}}
语法1 文档
|
|
Vue实例将会在实例化时调用$watch(),遍历watch对象的每一个property1 2 3vm.$watch('xxx', fn, {deep: ..., immediate: ...}) // 'xxx'可以改为一个返回字符串的函数 // 在组件中可写到钩子函数里
语法3 钩子函数中写
this.$watch(...)
|
|
深入监听deep: true ⇧
- 如果
object.a变了,deep: true控制object变了 - 如果
object.a变了,deep: false控制object不变 deep即监听object深入监听- 不仅比较地址,任何一层的值变了,都判定数据变化,都受到监听
- 即不需要再监听对象里面的属性变化,只要监听外层,同时监听了内部数据变化
deep默认值是false1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53const vmWatchDeep = new Vue({ template: ` <div> <hr> <button @click="n += 1">n + 1</button> <button @click="m += 1">m + 1</button> <button @click="obj.a += 'hi'">obj.a + 'hi'</button> <button @click="obj1.a += 'ho'">obj1.a + 'ho'</button> <button @click="obj = {a: 'a'}">obj = 新对象</button> </div> `, name: 'WatchDeep', data() { return { n: 0, obj: { a: "a" }, obj1: { a: "a" }, } }, created(){ this.$watch('m', function() { console.log("m 也变了") },{immediate: true}) }, watch: { n() { console.log("n 变了") }, obj() { console.log("obj 变了") }, "obj.a": function() { console.log("obj.a 变了") }, obj1: { handler() { console.log('obj1变了') }, deep: true, }, "obj1.a": function() { console.log("obj1.a 变了") }, } }).$mount('#watchDeep') vmWatchDeep.$watch('n', function() { console.log("n 也变222了") },{immediate: true})写在外面太丑,可写在生命周期的钩子中
和
DOM无关,放在created中,创建完立即监听数据
简述 computed 和 watch 的区别 ⇧
- 翻译成中文
- 各自描述(可用代码例子)
标准答案
- 把
computed翻译为计算属性 - 把
watch翻译为监听/观察/侦听 - 描述
computed的功能:用于计算一个新的属性,并提及「依赖」和「缓存」 - 描述了
watch的功能:在数据变化时执行一个函数 - 其他正确的描述:
computed计算出的值不需要加括号,在template中,直接当做属性来用computed根据「依赖」会自动「缓存」,依赖不变,不会重新计算值watch监听某个属性变化了,就执行一个函数watch中的immediate表示是否在第一次渲染中监听属性watch中的deep是否监听对象内部属性的变化
详细描述 计算属性
computed:
- 用来计算一个值,这个值使用时不需要加函数执行的括号,直接当属性来用;值会根据所依赖的数据动态显示新的计算结果,并自动缓存,即依赖不变,不会重新计算
- 支持缓存,只有依赖数据发生改变,才会重新进行计算
- 不支持异步,当
computed内有异步操作时无效,无法监听数据的变化 - 可用
Vue提供的异步函数nextTick computed属性值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的- 即基于
data中声明过的数据或者父组件传递的props中的数据通过计算得到的值 - 如果一个属性是由其他属性计算而来的,即这个属性(数据)依赖其他属性(数据),是多对一或者一对一,可用
computed - 如果
computed属性值是函数,那么默认会走get方法;函数的返回值就是属性的属性值;在computed中的,属性都有一个get和一个set方法,当数据变化时,调用set方法。
详细描述 监听属性
watch:
- 对
data的监听回调,即当依赖的data数据变化,执行回调函数 - 监听某个属性变化了,就执行一个函数
- 不支持缓存,数据被监,数据不变就不触发回调;
watch支持异步;- 监听的函数接收两个参数,第一个参数是最新的值;第二个参数是输入之前的值;
- 当一个属性发生变化时,需要执行对应的操作;一对多;
- 监听数据必须是
data中声明过的数据或者父组件传递过来的props中的数据,当数据变化时,触发其他操作 immediate组件加载立即触发回调函数执行,即第一次渲染时就会执行这个函数deep监听器会一层层的往下遍历,给对象的所有属性都加上这个监听器- 监听某个数据的变化时用
watch - 不可使用箭头函数来定义 watcher 回调函数,箭头函数绑定了父级作用域的上下文,this 将不会按照期望指向 Vue 实例
小结 ⇧
| 区别 | computed | watch | watchEffect |
|---|---|---|---|
| 作用简述 | 动态显示计算值 | 监听数据变化,执行回调函数 | — |
| 初始数据 | 属性值 | immediate属性可监听初始值,否则为空 |
— |
| 缓存 | 支持 | 不支持 | — |
| 异步 | 不支持 | 支持 | — |
| 特点1 | 不需要加括号,可以当一个属性来用,依赖于其他数据的变化 | 仅当数据变化才执行回调函数 | — |
| 特点2 | 多对一、一对一 | 一对多 | — |
| 目的 | 动态显示计算值 | 响应数据的变化,执行相应操作 | — |
5. Directive 指令 § ⇧
通过学习自定义指令来掌握
Vue的指令,再使用内置指令除了核心功能默认内置的指令 (v-model 和 v-show),Vue 也允许注册自定义指令
在
Vue2.0中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通DOM元素进行底层操作,这时候就会用到自定义指令
Vue2.x自定义指令 文档Vue3.x自定义指令 文档- 自定义指令
v-x,点击出现一个x
两种写法
- 全局声明一个指令
Vue.directive('directiveName', directiveOptions) - 局部声明多个指令,在
options中声明directives: {'directiveName': directiveOptions}对象
main.js
|
|
目标:造出
v-y,点击即出现一个y
main.js
|
|
- 声明的局部指令只能用在该实例中
directiveOptions ⇧
directiveOptions的五个属性(钩子)Vue2.x
bind(el, info, vnode, oldVnode)类似createdinserted(el, info, vnode, oldVnode)类似mountedupdated(el, info, vnode, oldVnode)类似updatedcomponentUpdated(el, info, vnode, oldVnode),文档unbind(el, info, vnode, oldVnode)类似destroyed
自定义指令的生命周期钩子更名了
directiveOptions的六个属性(钩子)Vue3.x
仿
v-on指令 ⇧
v-on2(省略事件委托)
main.js
|
|
inserted时添加事件监听unbind时解除事件监听- 简化了在
created和beforeDestroy时原本的DOM操作
缩写
directiveOptions在某些条件下可以缩写为函数,文档
指令的作用 ⇧
用于
DOM操作
Vue的实例/组件用于数据绑定、事件监听、DOM更新Vue的指令主要目的就是原生DOM操作
减少重复
- 封装为自定义指令,复用
DOM操作 - 封装为自定义指令,简化
DOM操作
6. Mixin 混入 § ⇧
Mixin减少重复
混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能
就是复制
Directive指令的作用是减少DOM操作的重复Mixin混入的作用是减少data、methods、钩子的重复
场景描述
Mixin.vue
|
|
- 假设需要在多个组件上添加
name和time - 在
created、时,打出提示,并报出存活时间destroyed - 一共有五个组件
- 给每个组件添加
data和钩子,共五次? - 使用
mixin减少重复
- 给每个组件添加
- 示例链接
./mixins/log.js
|
|
|
|
mixins用法
mixins选项接收一个混入对象的数组mixins: [obj1, ...]- 这些混入对象可以像正常的实例对象一样包含实例选项
options - 当组件使用混入对象时,这些选项将会被合并到该组件本的选项形成身最终的选项,使用的是和
Vue.extend()一样的选项合并逻辑 - 比如混入包含一个
created钩子,而创建组件本身也有一个,那么两个函数都会被调用 Mixin钩子按照传入顺序依次调用,并在调用组件自身的钩子之前被调用1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28const mixin = { created: function () { console.log(1) } } const vm = new Vue({ created: function () { console.log(2) }, mixins: [mixin] }) // => 1 // => 2 // 定义一个混入对象 const myMixin = { created: function () { this.hello() }, methods: { hello() { console.log('hello from mixin!') } } } // 定义一个使用混入对象的组件 const Component = Vue.extend({ mixins: [myMixin] }) const component = new Component() // => "hello from mixin!"
mixin技巧
选项智能合并
- 文档 选项合并
- 当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”
可以添加相应操作,同名覆盖
- 数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先
- 同名钩子函数将合并为一个数组,因此都将被调用
- 混入对象的钩子将在组件自身钩子之前调用
值为对象的选项,例如
methods、components和directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37/* 混入对象 */ const mixin = { created: function () { console.log('混入对象的钩子被调用') } } /* 组件 */ new Vue({ mixins: [mixin], created: function () { console.log('组件钩子被调用') } }) // => "混入对象的钩子被调用" // => "组件钩子被调用" /* 同名覆盖 */ var mixin = { methods: { conflicting: function () { console.log('from mixin') } } } var vm = new Vue({ mixins: [mixin], methods: { conflicting: function () { console.log('from self') } } }) vm.conflicting() // => "from self"
Vue.extend()也使用同样的策略进行合并
全局混入
Vue.mixin
- 全局注册一个混入,影响注册之后所有创建的每个 Vue 实例
- 文档 不推荐使用
- 注意所有组件的
data中需要相应的数据是否声明完整 - 范围过大,插件作者可以使用混入,向组件注入自定义的行为,以避免重复应用混入。不推荐在应用代码中使用
Vue3.x混入(mixin) 将不再作为推荐使用,CompositionAPI可以实现更灵活且无副作用的复用代码
Vue3.x中Mixin合并行为变更Vue3.x中,冲突合并的结果是组件中键值对完全替代mixins的,浅层次执行合并,并不深入比对每一项- 对于依赖对象声明的用户,建议
- 将共享数据提取到外部对象并将其用作
data中的每个property - 重写对共享数据的引用以指向新的共享对象
- 对于依赖
mixin的深度合并行为的用户,建议重构代码以完全避免这种依赖,因为mixin的深度合并非常隐式,让代码逻辑更难理解和调试 - 或使用
Composition代替
- 将共享数据提取到外部对象并将其用作
7. Extend 继承、扩展 § ⇧
Extend减少Mixin的重复
- 不每次都写一个
mixins - 使用
Vue.extend或options.extends
MyVue.js
|
|
在组件
Child1中使用或者new MyVue(options)
|
|
extend是比mixin更抽象的封装
- 写一次
extend代替写多次mixin,较少用到
8. Provide 和 Inject § ⇧
提供和注入,需求描述
- 一键换肤功能:默认蓝色,切换为红色
- 文字大小:默认正常,切换大小
- 示例链接
Provide-Inject.vue- Provide
|
|
Child1.vue
|
|
HTML
```
.app.fontSize-normal是同一个选择器,同时满足两种类["normal", "big", "small"].indexOf(name) >= 0代替短路判断fontSize默认不继承
Provide和Inject小结
- 在提供数据的地方写
Provide,Provide为函数形式,返回提供的数据的拷贝(比如字符串)- 如果在注入的地方修改,就需要再提供一个修改的函数到
Provide,让注入拿到函数的引用 - 或者用复杂类型的引用
- 如果在注入的地方修改,就需要再提供一个修改的函数到
- 在需要数据的地方写
Inject,Inject为数组['InjectData1', 'InjectData2', ...] Provide和Inject作用是大范围的 数据data和 方法methods共用,Provide把东西提供给所有人去引用,让Inject去调用- 注意点:示例中不可只传
themeName而不传changeTheme,因为themeName的值是被复制给provide - 不推荐传引用,变量容易失控,示例
总结 ⇧
computed计算属性(数据)- 全局用
Vue. - 局部用
options. - 作用是``
- 全局用
watch监听属性(数据)- 全局用
Vue. - 局部用
$watch() - 作用是``
- 全局用
directive指令(资源)- 全局用
Vue.directive({...}) - 局部用
options.directives - 作用是减少
DOM操作相关的代码重复
- 全局用
mixin混入(组合)- 全局用
Vue.mixin({...}) - 局部用
options.mixins: [mixin1, mixin2] - 作用是减少
options里的代码重复
- 全局用
extend继承、扩展(组合)- 全局用
Vue.extend({...}) - 局部用
options.extends: {...} - 作用是类似
mixin,只是形式不同
- 全局用
provide和inject(组合)- 祖先组件不需要知道哪些后代组件使用它提供的 property
- 后代组件不需要知道被注入的 property 来自哪里
- 祖代提供东西,后代注入使用
- 作用是祖代提供,隔
N代共享信息,反过来不行
参考文章
相关文章
- 无