前文指路:
Vue零基础教程|从前端框架到GIS开发系列课程(五)组件式开发
Vue零基础教程|从前端框架到GIS开发系列课程(四)计算属性与侦听器
Vue零基础教程|从前端框架到GIS开发系列课程(三)模板语法
0帧起手《Vue零基础教程》,从前端框架到GIS开发系列课程
什么是组合式API(了解)
从整体上划分, Vue分为如下几个核心模块
-
编译器.
-
渲染器.
-
响应式系统.
将模块里的每个功能解耦成一个一个函数,
每个函数实现特定的功能, 这些函数可以任意组合使用, 就叫做组合式API
比如:
-
reactive: 将普通对象转换成响应式对象
-
computed: 定义一个计算属性
-
watch: 定义一个侦听器
小结
使用一个函数实现一个特定的小功能, 再加这些函数任意组合, 实现更复杂的功能.
这些函数就叫做组合式API
为什么提出组合式API(了解)
1) Options API下代码的组织形式
使用Options API实现一个功能, 需要在不同的地方编写代码
-
状态(数据)在data中定义
-
方法在methods中定义
-
计算属性
-
...
当新添加一个功能时, 代码的组织会比较零散
2) Composition API下代码的组织形式
Party3
Vue响应式原理(理解)
1) 什么是响应式
当数据改变时, 引用数据的函数会自动重新执行
2) 手动完成响应过程
首先, 明确一个概念: 响应式是一个过程, 这个过程存在两个参与者: 一方触发, 另一方响应
比如说, 我们家小胖有时候不乖, 我会打他, 他会哭. 这里我就是触发者, 小胖就是响应者
同样, 所谓数据响应式的两个参与者
-
触发者: 数据
-
响应者: 引用数据的函数
当数据改变时, 引用数据的函数响应数据的改变, 重新执行
我们先手动完成响应过程
示例
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <div id="app"></div> <script> // 定义一个全局对象: `触发者` const obj = { name: 'hello' } // effect函数引用了obj.name, 这个函数就是 `响应者` function effect() { // 这里可以通过app拿到DOM对象 app.innerHTML = obj.name } effect() // 当obj.name改变时, 手动执行effect函数, 完成响应过程 setTimeout(() => { obj.name = 'brojie' effect() }, 1000)</script> </body></html>
为了方便, 我们把引用了数据的函数 叫做 副作用函数
3) 副作用函数
如果一个函数引用了外部的资源, 这个函数会受到外部资源改变的影响
我们就说这个函数存在副作用. 因此, 也把该函数叫做副作用函数
这里, 大家不要被这个陌生的名字吓唬住
所谓副作用函数就是引用了数据的函数或者说数据关联的函数
4) reactive()函数
在Vue3的响应式模块中, 给我们提供了一个API: reactive
reactive(): 将普通对象转换成响应式对象
如何理解响应式对象
如果一个对象的get和set过程被拦截, 并且经过自定义后与某个副作用函数建立了依赖关系.
这样的对象就被认为是具有响应式特性的. 即: 当数据改变时, 所依赖的函数会自动重新执行
示例
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <script src="../node_modules/vue/dist/vue.global.js"></script> </head> <body> <script> const { reactive } = Vue // 将普通对象转换成响应式对象 const pState = reactive({ name: 'xiaoming', age: 20 }) console.log(pState)</script> </body></html>
5) effect()函数
响应式对象要跟副作用函数配合使用. 在Vue3中提供了一个API: effect
effect(): 注册副作用函数, 建立响应式对象和副作用函数之间的关系
如何建立依赖
-
定义一个函数, 这个函数中引用了响应式对象的属性
-
通过effect注册副作用函数
💡注意
effect()函数还有很多高级的用法, 这里我们先了解最基础的用法
- 接受一个函数作为参数
示例
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <script src="../node_modules/vue/dist/vue.global.js"></script> </head> <body> <script> const { reactive, effect } = Vue // 将普通对象转换成响应式对象 const pState = reactive({ name: 'xiaoming', age: 20 }) // 定义一个函数, 引用pState.name, 这个函数就是副作用函数 function e1() { console.log('e1被执行...', pState.name) } // 通过effect注册 effect(e1) // 当pState.name的值改变时, e1会自动重新执行 setTimeout(() => { pState.name = 'xiaomei' }, 1000)</script> </body></html>
说明
上述注册后, 只建立了pState.name => e1的对应关系
-
如果改变pState.age, e1不会重新执行
-
可以为pState.name注册多个副作用函数
示例1
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <script src="../node_modules/vue/dist/vue.global.js"></script> </head> <body> <script> const { reactive, effect } = Vue // 将普通对象转换成响应式对象 const pState = reactive({ name: 'xiaoming', age: 20 }) // 定义一个函数, 引用pState.name, 这个函数就是副作用函数 function e1() { console.log('e1被执行...', pState.name) } // 通过effect注册 effect(e1) setTimeout(() => { // 当pState.age的值改变时, e1不会自动重新执行 pState.age = 30 }, 1000)</script> </body></html>
示例2
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <script src="../node_modules/vue/dist/vue.global.js"></script> </head> <body> <script> const { reactive, effect } = Vue // 将普通对象转换成响应式对象 const pState = reactive({ name: 'xiaoming', age: 20 }) // 定义一个函数, 引用pState.name, 这个函数就是副作用函数 function e1() { console.log('e1被执行...', pState.name) } // 通过effect注册e1 effect(e1) // 通过effect注册e2 effect(function e2() { console.log('e2被执行...', pState.name) }) setTimeout(() => { // 当pState.name的值改变时, e1, e2都会自动重新执行 pState.name = 'xiaomei' }, 1000)</script> </body></html>
6) 实现响应式流程
借助reactive和effect我们就可以实现vue最核心的响应式流程:
当数据改变时, 页面重新渲染
示例
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <script src="../node_modules/vue/dist/vue.global.js"></script> </head> <body> <div id="app"></div> <script> const { reactive, effect } = Vue // 定义状态 const pMsg = reactive({ msg: 'hello' }) // 注册副作用函数 effect(() => { app.innerHTML = pMsg.msg }) // 改变状态 setTimeout(() => { pMsg.msg = 'world' }, 1000)</script> </body></html>
看到这里, 恭喜你, 已经掌握了最核心的原理🤝
💡 小结
-
响应式是一个过程, 存在触发者和响应者
-
数据的改变, 触发关联的副作用函数响应(重新执行)
-
分为两步
-
-
将普通对象转换成响应式对象
-
注册副作用函数, 建立依赖关系
-
Party4
reactive()函数详解(掌握)
通过前面的学习, 我们了解到reactive可以将一个普通对象转换成响应式对象.
那么, 接下来我们就详细研究一下这个函数.
研究函数主要从这样三个方面
-
输入, 也就是参数
-
作用, 做了什么
-
输出, 也就是返回值
1、参数: 只能是引用类型数据, 不能是值类型数据
2、作用: 创建传入对象的深层代理, 并返回代理后的对象
3、返回值: 一个Proxy代理对象
1) 深层代理
不管传入的对象存在多少层嵌套(对象套对象的情况), 每一层都具有响应性
示例
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <script src="../node_modules/vue/dist/vue.global.js"></script> </head> <body> <script> const { reactive, effect } = Vue const pState = reactive({ name: 'xiaoming', age: 20, gf: { name: 'xiaomei', city: { name: 'wuhan', }, }, }) effect(() => { console.log( `${pState.name}的女朋友叫${pState.gf.name}, 在${pState.gf.city.name}` ) }) setTimeout(() => { console.log('过了一段时间, 她去了beijing') // 不管嵌套多少层, 都具有响应性 pState.gf.city.name = 'beijing' }, 1000)</script> </body></html>
2) 重复代理
-
对同一个普通对象, 多次代理, 返回的结果唯一
-
对代理后的对象再次代理, 返回的结果唯一
以上, 可以理解为单例模式, reactive创建的代理对象只会存在一个
单例模式
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <script src="../node_modules/vue/dist/vue.global.js"></script> </head> <body> <script> const { reactive, effect } = Vue const state = { name: 'xiaoming' } const p1 = reactive(state) const p2 = reactive(state) // 对同一个对象多次代理, 返回的结果唯一 console.log(p1 === p2) // true const p3 = reactive(p1) // 对代理后的对象, 再次代理, 返回的结果唯一 console.log(p3 === p1) // true</script> </body></html>
3) 局限性
-
传入参数只能是对象
-
解构或者赋值操作会丢失响应性
示例1
解构赋值后的变量没有响应性
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <script src="../node_modules/vue/dist/vue.global.js"></script> </head> <body> <div id="app"></div> <script> const { reactive, effect } = Vue const pState = reactive({ name: 'xiaoming' }) // 对代理对象进行解构 let { name } = pState effect(() => { app.innerHTML = pState.name }) setTimeout(() => { name = 'xiaomei' console.log('对解构后的name操作, 不会触发响应式') }, 1000)</script> </body></html>
示例2
赋值操作丢失响应性
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <script src="../node_modules/vue/dist/vue.global.js"></script> </head> <body> <div id="app"></div> <script> const { reactive, effect } = Vue let todos = reactive([]) effect(() => { app.innerHTML = JSON.stringify(todos) }) // 模拟向接口请求 setTimeout(() => { // 将接口返回的数据赋值给todos, 导致todos丢失了响应性 todos = [ { id: 1, content: 'todo-1' }, { id: 2, content: 'todo-2' }, ] }, 1000)</script> </body></html>
Party5
ref()函数详解(重点)
1) ref的基本使用
由于reactive()存在一些局限性, 因此, Vue官方提供了另一个API(ref)定义响应式对象
-
可以传入任意类型数据
-
返回RefImpl
-
通过.value进行get和set操作
示例
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <script src="../node_modules/vue/dist/vue.global.js"></script> </head> <body> <div id="app"></div> <script> const { ref, effect } = Vue // 创建值类型数据的响应式对象 const count = ref(0) // 返回值, 是一个RefImpl console.log(count) effect(() => { // 引用时, 需要使用.value app.innerHTML = count.value }) setInterval(() => { // 赋值操作也需要使用.value count.value++ }, 1000)</script> </body></html>
示例
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <script src="../node_modules/vue/dist/vue.global.js"></script> </head> <body> <div id="app"></div> <script> const { ref, effect } = Vue const todos = ref([]) console.log(todos) effect(() => { // 引用时, 需要使用.value app.innerHTML = JSON.stringify(todos.value) }) setTimeout(() => { todos.value = [ { id: 1, content: 'todo-1' }, { id: 2, content: 'todo-2' }, ] })</script> </body></html>
2) 响应性丢失问题
将reactive定义的代理对象赋值给其它变量时, 会出现响应性丢失问题
赋值主要有如下三种情况:
-
如果将reactive定义的代理对象的属性赋值给新的变量, 新变量会失去响应性
-
如果对reactive定义的代理对象进行展开操作. 展开后的变量会失去响应性
-
如果对reactive定义的代理对象进行解构操作. 解构后的变量会失去响应性
赋值操作
示例
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <script src="../node_modules/vue/dist/vue.global.js"></script> </head> <body> <script> const { reactive } = Vue const obj = reactive({ foo: 1, bar: 2 }) // 将reactive创建的代理对象的属性赋值给一个新的变量foo let foo = obj.foo // foo此时就是一个普通变量, 不具备响应性 effect(() => { console.log('foo不具备响应性...', foo) }) foo = 2</script> </body></html>
-
obj.foo表达式的返回值是1
-
相当于定义了一个普通变量foo, 而普通变量是不具备响应性的
展开操作
示例
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <script src="../node_modules/vue/dist/vue.global.js"></script> </head> <body> <script> const { reactive } = Vue const obj = reactive({ foo: 1, bar: 2 }) // 展开运算符应用在proxy对象上, 会从语法层面遍历源对象的所有属性 // ...obj ===> foo:1, bar:2 const newObj = { ...obj, } // 此时的newObj是一个新的普通对象, 和obj之间不存在引用关系 console.log(newObj) // {foo:1, bar:2} effect(() => { console.log('newObj没有响应性...', newObj.foo) }) // 改变newObj的属性值, 不会触发副作用函数的重新执行 // 此时, 我们就说newObj失去了响应性 newObj.foo = 2</script> </body></html>
解构操作
示例
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <script src="../node_modules/vue/dist/vue.global.js"></script> </head> <body> <script> const { reactive } = Vue const obj = reactive({ foo: 1, bar: 2 }) // 对proxy对象进行解构操作后, foo和bar就是普通变量, 也失去了响应性 let { foo, bar } = obj effect(() => { console.log('foo不具备响应性', foo) }) // 给变量foo赋值, 不会触发副作用函数重新执行 foo = 2</script> </body></html>
对proxy对象解构后, foo就是一个普通变量, 也失去了跟obj的引用关系.
因此, 对foo的修改不会触发副作用函数重新执行
3) toRef与toRefs
为了解决在赋值过程中响应丢失问题, Vue3提供了两个API
-
toRef: 解决赋值时响应丢失问题
-
toRefs: 解决展开, 解构时响应丢失问题
示例1
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <script src="../node_modules/vue/dist/vue.global.js"></script> </head> <body> <script> const { reactive, effect, toRef, toRefs } = Vue // obj是reactive创建的响应式数据(proxy代理对象) const obj = reactive({ foo: 1, bar: 2 }) effect(() => { console.log('obj.foo具有响应性:', obj.foo) }) // 使用toRef定义, 取代基本赋值操作 foo = obj.foo // 这里得到的foo是一个Ref对象 const foo = toRef(obj, 'foo') effect(() => { console.log('foo.value具有响应性:', foo.value) })</script> </body></html>
示例2
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <script src="../node_modules/vue/dist/vue.global.js"></script> </head> <body> <script> const { reactive, effect, toRef, toRefs } = Vue // obj是reactive创建的响应式数据(proxy代理对象) const obj = reactive({ foo: 1, bar: 2 }) // 使用toRefs解构赋值 取代 {foo, bar} = obj const { foo, bar } = toRefs(obj) effect(() => { console.log('bar.value具有响应性', bar.value) })</script> </body></html>
这里toRefs返回的结构如下, 这样解构出来的对象依然是Ref对象, 通过.value访问还是保持了响应性
{ foo: ref(1), bar: ref(2),}
4) reactive VS ref
✏️ 最佳实践
-
大部分(80%以上)情况推荐使用ref
-
处理本地关联数据时, 可以使用reactive
Party6
computed()函数详解(掌握)
1) 基本使用
📝计算属性computed()函数
-
参数: 函数/对象
-
作用: 创建一个计算属性
-
返回: 计算属性对象
示例1
const state = reactive({firstname: 'xiao', lastname: 'ming'})// 接收一个副作用函数做为参数, 返回一个ref类型对象const fullname = computed(() => { return state.firstname + state.lastname})// 通过.value操作console.log(fullname.value)
示例2
接收一个对象作为参数, 但是这种方式用的不多.
const state = reactive({firstname: 'xiao', lastname: 'ming'})// 接收一个副作用函数做为参数, 返回一个ref类型对象const fullname = computed({ get() { return state.firstname + ' ' + state.lastname }, set(newValue) { [state.firstname, state.lastname] = newValue.split(' ') }})// 通过.value操作console.log(fullname.value)
2) 计算属性的特点
懒执行
示例
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <script src="../node_modules/vue/dist/vue.global.js"></script> </head> <body> <script> const { reactive, computed } = Vue const state = reactive({ firstname: 'xiao', lastname: 'ming' }) const fullname = computed(() => { console.log('默认不执行, 只有当访问fullName.value时执行') return state.firstname + state.lastname }) setTimeout(() => { fullname.value }, 1000)</script> </body></html>
缓存
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <script src="../node_modules/vue/dist/vue.global.js"></script> <script> const { reactive, computed } = Vue const state = reactive({ firstname: 'xiao', lastname: 'ming' }) const fullname = computed(() => { console.log('computed') return state.firstname + state.lastname }) console.log(fullname.value) // 初次访问时, 执行1次, 保存到缓存 console.log(fullname.value) // 再次访问, 直接返回缓存中的数据</script> </body></html>
3) effect的高级用法
effect函数的高级用法
-
lazy: 懒执行
-
scheduler: 自定义更新
lazy选项
示例
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <script src="../node_modules/vue/dist/vue.global.js"></script> </head> <body> <script> const { ref, effect } = Vue const count = ref(0) // effect 返回 run() 函数, // 1. 加入lazy:true选项后, 不会自动调用副作用函数 // 2. 手动执行run()函数, 才会调用副作用函数, 建立依赖关系 const run = effect( () => { console.log('一开始不执行, 调用run才会执行', count.value) }, { lazy: true } ) console.log(run)</script> </body></html>
scheduler选项
示例
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <script src="../node_modules/vue/dist/vue.global.js"></script> </head> <body> <script> const { ref, effect } = Vue const count = ref(0) effect( () => { console.log('第一次执行这里', count.value) }, { scheduler: () => { console.log('更新时, 执行这里...') }, } )</script> </body></html>
4) 基于effect实现computed
基本实现
从computed()函数的使用上分析
computed()函数的参数是一个副作用函数, 依赖响应式对象
computed()函数跟注册副作用函数effect()类似. 接收一个副作用函数做为参数
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <script src="../node_modules/vue/dist/vue.global.js"></script> </head> <body> <script> const { ref, effect } = Vue function computed(fn) { // 只考虑参数是fn的情况 const run = effect(fn) return { get value() { return run() }, } } const firstname = ref('xiao') const lastname = ref('ming') const fullname = computed(() => { console.log('副作用函数被执行了...') return firstname.value + lastname.value })</script> </body></html>
实现懒执行
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <script src="../node_modules/vue/dist/vue.global.js"></script> </head> <body> <script> const { ref, effect } = Vue function computed(fn) { // 只考虑参数是fn的情况 const run = effect(fn, { lazy: true }) return { get value() { return run() }, } } const firstname = ref('xiao') const lastname = ref('ming') const fullname = computed(() => { console.log( '副作用函数被执行了, 值是:', firstname.value + lastname.value ) return firstname.value + lastname.value })</script> </body></html>
实现缓存
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <script src="../node_modules/vue/dist/vue.global.js"></script> </head> <body> <script> const { ref, effect } = Vue function computed(fn) { let cache let dirty = true // 只考虑参数是fn的情况 const run = effect(fn, { lazy: true, shecduler: () => { dirty = true }, }) return { get value() { if (dirty) { cache = run() dirty = false } return cache }, } } const firstname = ref('xiao') const lastname = ref('ming') const fullname = computed(() => { console.log( '副作用函数被执行了, 值是:', firstname.value + lastname.value ) return firstname.value + lastname.value })</script> </body></html>
更多详细实现, 详见: (设计与实现篇-响应式原理四.computed的实现)
Party7
watch()函数详解(掌握)
1) 基本使用
📝侦听器watch()函数
- 参数:
-
-
侦听的数据源:
-
可以是引用了响应式对象的副作用函数
-
响应式对象(ref, reactive, computed)
-
以上类型组成的数组
-
对应的回调: 当数据改变时, 执行的回调函数
-
选项:
-
immediate: 创建时立即执行回调
-
deep: 当数据是对象, 开启深度侦听
-
flush: 设置回调执行的时机
-
-
作用: 侦听数据源的改变, 当数据源改变时, 重新执行回调
-
返回: unwatch方法
示例1
// 1. 数据源是ref(响应式对象)const count = ref(0)watch(count, () => { console.log('count的值改变了:', count.value)})
示例2
// 2. 数据源是reactive对象(默认是深度侦听)const stu = reactive({ name: 'xiaoming', age: 20, gf: { name: 'xiaomei', city: { name: 'wuhan', }, },})watch(stu, () => { // 由于这种方式会递归遍历每一层属性, 效率不高, 一般不用 console.log('侦听整个reactive对象, 是深度侦听的')})
由于侦听对象时会递归遍历每一层属性, 效率不高, 一般不用
一般情况下, 需要侦听的往往是某个具体的属性
此时, 我们需要包装一个函数, 在函数中引用该属性. 这个函数也是副作用函数
示例3
// 不能直接这样写. stu.age相当于读取出了具体的值watch(stu.age, () => { console.log('不会生效')})
-
对于值类型, 不能直接侦听
-
对于对象类型, 依然可用
示例4
// 通过一个副作用函数包装, 在副作用函数中引用需要侦听的属性watch( () => stu.age, () => { console.log('会生效, 常用') })
示例5
// 几乎不用watch([count, () => stu.age], () => { console.log('侦听的数据源是数组的情况')})
2) 高级使用
- 只有当数据源改变时, 才会执行回调.
-
-
如果侦听值类型数据, 可以在回调中拿到新旧值
-
如果侦听对象类型数据, 在回调中新旧值相同, 都是新值
-
- 当通过getter返回一个对象时
-
-
只有当对象整体被替换时, 才会触发回调
-
需要设置deep: true, 强制开启深度监听
-
-
通过设置immediate: true 可以立即执行回调. 旧值为undefined
-
通过设置flush: post可以控制回调执行的时机, 在DOM重新渲染后执行, 可以在回调中拿到更新后的DOM对象
-
停止侦听
示例1
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <script src="../node_modules/vue/dist/vue.global.js"></script> </head> <body> <script> const { ref, reactive, watch } = Vue const count = ref(0) watch(count, (newValue, oldValue) => { console.log(oldValue, newValue) }) // 2. 数据源是reactive对象(默认是深度侦听) const stu = reactive({ name: 'xiaoming', age: 20, gf: { name: 'xiaomei', city: { name: 'wuhan', }, }, }) // 通过一个副作用函数包装, 在副作用函数中引用需要侦听的属性 watch( () => stu.age, (newValue, oldValue) => { console.log(oldValue, newValue) } ) // 如果侦听对象类型, 新旧值相同, 都是新值 watch(stu, (newValue, oldValue) => { console.log(oldValue, newValue) })</script> </body></html>
示例2
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <script src="../node_modules/vue/dist/vue.global.js"></script> </head> <body> <script> const { ref, reactive, watch } = Vue const stu = reactive({ name: 'xiaoming', age: 20, gf: { name: 'xiaomei', city: { name: 'wuhan', }, }, }) // 如果侦听的属性是一个对象, 只有当对象被替换时, 才触发回调 // 当改变stu.gf.name, 不会触发回调. 需要加deep: true watch( () => stu.gf, (newValue, oldValue) => { console.log(oldValue, newValue) } )</script> </body></html>
示例3
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <script src="../node_modules/vue/dist/vue.global.js"></script> </head> <body> <script> const { ref, reactive, watch } = Vue const count = ref(0) watch( count, (newValue, oldValue) => { console.log(oldValue, newValue) // undefined 0 }, { immediate: true, } )</script> </body></html>
示例4
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <script src="../node_modules/vue/dist/vue.global.js"></script> </head> <body> <div id="app">{{msg}}</div> <script> const { ref, reactive, watch, createApp } = Vue createApp({ setup() { const msg = ref('hello') // watch(msg, () => { // // 回调先执行, 渲染后执行 // console.log('此时的DOM是旧的', app.innerHTML) // }) watch( msg, () => { // 渲染先执行, 回调后执行 console.log('此时的DOM是新的', app.innerHTML) }, { flush: 'post', } ) setTimeout(() => { msg.value = 'world' }, 1000) return { msg, } }, }).mount('#app') // watch()</script> </body></html>
示例5
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <script src="../node_modules/vue/dist/vue.global.js"></script> </head> <body> <script> const { ref, reactive, watch } = Vue const count = ref(0) const unwatch = watch( count, (newValue, oldValue) => { console.log(oldValue, newValue) // undefined 0 }, { immediate: true, } ) // 在调用unwatch之后, 停止侦听数据的改变</script> </body></html>
3) 基于effect实现watch
实际上, watch也是由effect实现的. 这里我给出最基本用法的实现.
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <script src="../node_modules/vue/dist/vue.global.js"></script> </head> <body> <script> const { reactive, effect } = Vue const stu = reactive({ name: 'xiaoming', age: 20, gf: { name: 'xiaomei', city: { name: 'wuhan', }, }, }) function watch(source, callback) { // 只考虑source是getter的情况 effect(source, { scheduler: callback, }) } watch( () => stu.age, () => { console.log('stu.age的值改变了...', stu.age) } )</script> </body></html>
4) watchEffect
watchEffect会自动收集参数函数中的依赖, 可以认为是对effect的直接封装.
当然会加入一些额外的参数选项
function watchEffect(fn) { effect(fn)}
3) watch VS watchEffect
-
watch可以更精确的控制侦听的属性, watchEffect是自动收集
-
watch可以拿到新旧值, watchEffect不行
-
watch的回调默认不执行, watchEffect的回调默认执行
Party8
其它函数(了解)
1) readonly()函数
readonly()
-
参数: 普通对象/reactive对象/ref对象
-
作用: 创建只读的响应式对象
-
返回: proxy对象
示例
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <script src="../node_modules/vue/dist/vue.global.js"></script> </head> <body> <script> const { reactive, ref, readonly } = Vue const count = ref(0) const stu = reactive({ name: 'xiaoming', age: 20 }) const r_obj = readonly({ msg: 'hello' }) const r_count = readonly(count) const r_stu = readonly(stu)</script> </body></html>
应用场景
父组件向子组件传对象时, 为了保证单向数据流(子组件不能修改父组件中的数据). 可以考虑在父组件中传递只读对象给子组件
2) shallow系列函数
创建浅层的代理, 即第一层具有响应性. 更深层不具有响应性
shallowReactive()
const sh_stu = shallowReactive({ name: 'xiaoming', age: 20, gf: { // 替换gf对象触发响应性 name: 'xiaomei', // 不具有响应性 city: { name: 'wuhan' // 不具有响应性 } }})
3) 工具函数
|------------|------------------|---------------------------------------------------------------------------|-----------------------------|--------------------------------------------------------|
| 函数名 | 参数 | 作用 | 返回值 | 说明 |
| isRef | (数据) | 判断传入的参数是否为Ref类型 | true: 是Ref类型 false: 不是Ref类型 | 根据__v_isRef标识判断 * computed的返回值是Ref类型 * toRef的返回值是Ref类型 |
| isReactive | (数据) | 判断传入的参数是否由 reactive()或者shallowReactive()创建 | true: 是 false: 否 | |
| isProxy | (数据) | 判断传入的参数是否由 reactive()或者shallowReactive() readonly()或者shallowReadonly() 创建 | true: 是 false: 否 | |
| toRef | (对象, '属性', 默认值?) | 将reactive对象的某个属性转换为 ref类型对象 | ObjectRefImpl对象 | 属性是字符串 默认值可选 |
| toRefs | (对象) | 将reactive对象转换成普通对象 但是每个属性值都是ref | 普通对象 | 传入参数必须是 reactive类型 |
需要vue教程资料,请加GIS小巫师,备注:《Vue零基础教程》