Vue零基础教程|从前端框架到GIS开发系列课程(六)组合式API

前文指路:

Vue零基础教程|从前端框架到GIS开发系列课程(五)组件式开发

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(): 注册副作用函数, 建立响应式对象和副作用函数之间的关系

如何建立依赖

  1. 定义一个函数, 这个函数中引用了响应式对象的属性

  2. 通过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的对应关系

  1. 如果改变pState.age, e1不会重新执行

  2. 可以为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>​

看到这里, 恭喜你, 已经掌握了最核心的原理🤝

💡 小结

  1. 响应式是一个过程, 存在触发者和响应者

  2. 数据的改变, 触发关联的副作用函数响应(重新执行)

  3. 分为两步

    1. 将普通对象转换成响应式对象

    2. 注册副作用函数, 建立依赖关系

Party4

reactive()函数详解(掌握)

通过前面的学习, 我们了解到reactive可以将一个普通对象转换成响应式对象.

那么, 接下来我们就详细研究一下这个函数.

研究函数主要从这样三个方面

  1. 输入, 也就是参数

  2. 作用, 做了什么

  3. 输出, 也就是返回值

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) 重复代理

  1. 对同一个普通对象, 多次代理, 返回的结果唯一

  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. 传入参数只能是对象

  2. 解构或者赋值操作会丢失响应性

示例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)定义响应式对象

  1. 可以传入任意类型数据

  2. 返回RefImpl

  3. 通过.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定义的代理对象赋值给其它变量时, 会出现响应性丢失问题

赋值主要有如下三种情况:

  1. 如果将reactive定义的代理对象的属性赋值给新的变量, 新变量会失去响应性

  2. 如果对reactive定义的代理对象进行展开操作. 展开后的变量会失去响应性

  3. 如果对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

✏️ 最佳实践

  1. 大部分(80%以上)情况推荐使用ref

  2. 处理本地关联数据时, 可以使用reactive

Party6

computed()函数详解(掌握)

1) 基本使用

📝计算属性computed()函数

  1. 参数: 函数/对象

  2. 作用: 创建一个计算属性

  3. 返回: 计算属性对象

示例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函数的高级用法

  1. lazy: 懒执行

  2. 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()函数

  1. 参数:
    1. 侦听的数据源:

    2. 可以是引用了响应式对象的副作用函数

    3. 响应式对象(ref, reactive, computed)

    4. 以上类型组成的数组

    5. 对应的回调: 当数据改变时, 执行的回调函数

    6. 选项:

    7. immediate: 创建时立即执行回调

    8. deep: 当数据是对象, 开启深度侦听

    9. flush: 设置回调执行的时机

  1. 作用: 侦听数据源的改变, 当数据源改变时, 重新执行回调

  2. 返回: 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) 高级使用

  1. 只有当数据源改变时, 才会执行回调.
    1. 如果侦听值类型数据, 可以在回调中拿到新旧值

    2. 如果侦听对象类型数据, 在回调中新旧值相同, 都是新值

  1. 当通过getter返回一个对象时
    1. 只有当对象整体被替换时, 才会触发回调

    2. 需要设置deep: true, 强制开启深度监听

  1. 通过设置immediate: true 可以立即执行回调. 旧值为undefined

  2. 通过设置flush: post可以控制回调执行的时机, 在DOM重新渲染后执行, 可以在回调中拿到更新后的DOM对象

  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>    <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

  1. watch可以更精确的控制侦听的属性, watchEffect是自动收集

  2. watch可以拿到新旧值, watchEffect不行

  3. watch的回调默认不执行, watchEffect的回调默认执行

Party8

其它函数(了解)

1) readonly()函数

readonly()

  1. 参数: 普通对象/reactive对象/ref对象

  2. 作用: 创建只读的响应式对象

  3. 返回: 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零基础教程》

相关推荐
树上有只程序猿31 分钟前
年底了公司要裁员,大家还好吗?
前端·后端
问道飞鱼1 小时前
【前端知识】Javascript进阶-类和继承
开发语言·前端·javascript·继承·
Grocery store owner1 小时前
el-time-picker选择时分秒并且根据总秒数禁用不可选
前端·javascript·vue.js
川石教育1 小时前
Vue前端开发-axios默认配置和响应结构
前端·javascript·vue.js
半吊子全栈工匠1 小时前
WEB语义化的新探索:浅析LLMs.txt
前端·搜索引擎
JackieDYH2 小时前
Vue3中页面滑到最下面,然后跳转新页面后新页面的位置还是在之前浏览的位置
前端·javascript·html
正小安2 小时前
vue3实现ai聊天对话框
前端·javascript·vue.js·elementui
长风清留扬2 小时前
小程序开发中的插件生态与应用-上
前端·javascript·css·微信小程序·小程序·apache
JSCON简时空2 小时前
还没下雪嘛?等不及了,自己整个 3D 雪地写写字!
前端·react.js·three.js