前文指路:
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零基础教程》