目录

🧊 Vue 3 的组合 API 编程

# 常用 API

# setup

  • setup 是 Vue 3 中的新的一个配置项,值为一个函数

    是所有 Composition 组合 API 「表演的舞台」。

  • 组件中所有到的:数据、方法,都要配置在 setup 中。

  • setup 函数中的两种返回值:

    • ⭐️ 若最后返回一个对象,则对象中的属性、方法,在模版中可以直接使用
    • 若返回一个渲染函数,则可以返回自定义的渲染内容(了解)。

🌰 例子:在 setup 中返回一个对象:

setup() {
  // 数据
  let name = 'Simon'
  let age = '3'

  // 方法
  function sayHello() {
    alert(`hello, this is ${name}, age:${age}`)
  }

  return {
    name,
    age,
    sayHello
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  • ⚠️ 注意:
    1. 尽量不要与 Vue 2.x 中的配置混用:
      • Vue 2.x 的配置( datamethodscomputed … )中可以访问到 setup 中的属性、方法。
      • 但是在 setup 配置中不能访问 Vue 2.x 的配置( datamethodscomputed … )。
      • setup 中的属性、方法与 Vue 2.x 的配置有重名时,setup 优先。
    2. setup 不能是一个 async 函数,因为返回值不再是 return 对象,而是一个被 async 包裹的对象,而是 promise ,此时模版无法使用 return 中的属性(后期可以返回一个 Promise 的实例,但是需要 Suspense 和异步组件的配合 🔗 Suspense 组件

# ref 函数

  • 在之前使用的 ref ,作用是给原生的 DOM 标签添加标识,替代 id 的作用。
  • 在上述 setup 的例子中定义的数据不是一个响应式的数据,发生变化 Vue 无法监测。
  • ref 函数作用:定义一个响应式的数据

  • 语法: const xxx = ref(initValue)

    • 创建一个 ReflImplreference + implement 包含响应式数据的引用 ref 对象(引用实现对象)。
    • 在 JS 中操作数据: xxx.value
    • 在模版中读取数据:直接 (自动解析 ReflImplvalue 值)
  • 注意:

    • 参数可以接收的数据有:基本数据类型、对象类型。
    • 基本类型的数据:响应式依然是直接靠 Object.defineProperty()getset (数据劫持)完成的。
    • 对象类型的数据:借助 Vue 3 中的 reactive 函数。

# reactive 函数

  • reactive 函数的作用:定义一个对象类型的响应式数据。(对于基本数据类型只能使用 ref ,基本数据类型放在对象中再使用 reactive 函数即可 )

  • 语法: const 代理对象 = reactive(源对象) ,参数接收一个对象(或数组),返回一个代理对象Proxy 的实例对象,代理 Proxy 对象)

  • 使用 reactive 函数定义的响应式数据是「深层次的」。

  • reactive 函数内部基于 ES6 的 Proxy 函数实现,通过代理对象操作源对象的内部数据

# 对比 refreactive 函数

  • 从定义数据类型角度对比

    • ref 用来定义基本数据类型。
    • reactive 用来定义对象(包括数组)类型。

    reactive 可以用来定义基本数据类型,封装在一个 data 对象中即可。

  • 从原理角度对比

    • ref 通过 Object.defineProperty()getset 来实现响应式(数据劫持)。
    • reactive 通过使用 Proxy (数据劫持)与 Reflect (操作源对象内部的数据)结合实现响应式。
  • 从实用角度对比

    • ref 定义的数据,操作数据需要使用 .value 读取,在模版中可以直接读取。
    • reactive 定义的数据,操作和读取数据可以直接读取。

# setup 的注意点

  • setup 执行的时机

    • beforeCreate 之前执行一次, thisundefined
  • setup 的参数

    • props :值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性
    • context上下文对象,包含以下内容:
      • attrs : 值为对象,包含:组件外部传递过来,但没有在 props 配置中声明的属性,相当于 this.$attrs
      • slots收到的插槽内容,相当于 this.$slots
      • emit分发自定义事件的函数,相当于 this.$emit 。注意,自定义事件在要触发的组件中,与 props 相同,使用 emits 先声明,否则会警告。

# 响应式原理

# Vue 2 中的响应式

  • 实现原理(对于对象类型和数组类型的数据):

    • 对象类型:通过 Object.defineProperty() 对属性的读取、修改进行拦截(数据劫持)。

    • 数组类型通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。

      Object.defineProperty(data, 'count', {
          get () {}, 
          set () {}
      })
      
      1
      2
      3
      4
  • 存在问题(可以通过 Vue.setthis.$set ) 解决)

    • 新增属性、删除属性,界面不会更新
    • 直接通过下标修改数组,界面不会自动更新

# Vue3 的响应式

  • 实现原理:
    • 通过 Proxy(代理):拦截对象中任意属性的变化,包括:属性值的读写、属性的添加、属性的删除等。
    • 通过 Reflect(反射):映射对源对象的属性进行操作
new Proxy(data, {
	// 拦截读取属性值
    get (target, prop) {
    	return Reflect.get(target, prop)
    },
    // 拦截设置属性值或添加新属性
    set (target, prop, value) {
    	return Reflect.set(target, prop, value)
    },
    // 拦截删除属性
    deleteProperty (target, prop) {
    	return Reflect.deleteProperty(target, prop)
    }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14

🔗 相关链接 MDN:

  • Proxy:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy

  • Reflect:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect

# 计算属性 computed 函数

Vue 3 中的计算属性 computed 函数的使用方法与 Vue 2 中的 computed 一致,在 Vue 3 中计算属性放入了 setup 中。可以将定义在 setup 中的数据设置为计算属性。

🌰 计算属性的简写形式:

setup() {
  // 数据属性
  let person = reactive({
    firstName: 'Simon',
    lastName: 'Luo',
  })

  // 计算属性
  person.fullName = computed(() => {
    return person.firstName + '-' + person.lastName
  })

  return {
    person,
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  • 上述简写的形式若将计算后的属性修改会提示警告,因为简写的计算属性是只读的。

🌰 计算属性的完整形式(包括 get()set() ):

person.fullName = computed({
  get() {
    return person.firstName + '-' + person.lastName
  },
  set(value) {
    const nameArr = value.split('-')
    person.firstName = nameArr[0]
    person.lastName = nameArr[1]
  }
})
1
2
3
4
5
6
7
8
9
10

# 数据监视 watch 函数

computed 函数类似, watch 函数放在 setup 中使用,并且与 Vue 2 时的数据监视 watch 配置用法相同。

但是 Vue 3 中的数据监视,对于不同的使用情况有不同的结果

  • 监视普通的 ref 定义的一个响应式数据:
watch(sum, (newValue, oldValue)=>{
  console.log('newValue', newValue)
  console.log('oldValue', oldValue)
}, {immediate: true})
1
2
3
4
  • 监视普通的 ref 定义的多个响应式数据:
watch([sum, msg], (newValue, oldValue)=>{
  console.log('newValue', newValue)
  console.log('oldValue', oldValue)
})
1
2
3
4

监视使用 ref 定义的数据可以完整获取 newValueoldValue

注意

对于 ref 定义的响应式数据的一个注意点: ref 定义的普通数据的监视不能使用 .value ,不用使用 .value 是因为 ref 定义的数据生成引用对象, .value 直接获取的是数据。

而对于使用 ref 定义的响应式数据为一个对象时,此时 .value 的值为借用 reative 生成的 Proxy 对象,这时并不能监视到该层次的变化。可以使用加上 .value 或者开启深度监视解决。

  • 监视 reactive 定义的响应式数据(以对象为例):
watch(person, (newValue, oldValue) => {
  console.log('oldValue', oldValue) // 依旧是newValue
  console.log('newValue', newValue)
}, {deep: false})
1
2
3
4

注意

  • 监视使用 reactive 定义的响应式数据不能获取 oldValue

  • 监视使用 reactive 定义的响应式数据,会强制开启深度监视(对于此事监视完整的一个对象而言)。

  • 监视 reactive 定义的响应式数据的某一个属性或者某些属性,第一个参数需要为一个函数的返回值或者对象,监测多个属性需要使用数组包裹。
watch(() => person.age, (newValue, oldValue) => {
  console.log('newValue', newValue)
}, {immediate: true, deep: false})
1
2
3
watch([() => person.age, () => person.name], (newValue, oldValue) => {
  console.log('newValue', newValue)
}, {deep: false})
1
2
3

注意

  • 当监视的为 reactive 定义的响应式数据的某一个属性为一个对象时,并且其中有需要深度监视的属性,此时需要开启深度监视。
watch(() => person.job, (newValue, oldValue) => {
  console.log('oldValue', oldValue)
  console.log('newValue', newValue)
}, {deep: true})
1
2
3
4

由于监视的是 reactive 定义的对象中的某个属性,所以 deep 配置有效

# watchEffect 函数

  • watch 函数中:既要指明监视的属性,也要指明监视的回调。
  • watchEffect 函数中:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。
  • computed 函数相似,在 computed 函数中注重计算出来的值(回调函数的返回值),所以必须要返回一个值;但是 watchEffect 注重的是过程(回调函数的函数体),所以不用写返回值。

🌰 watchEffect 函数的使用例子:

watchEffect(()=>{
  const x1 = sum.value
  const x2 = person.age
  console.log('watchEffect回调执行')
})
1
2
3
4
5

只要 watchEffect 指定的回调函数中用到的数据发生变化,则直接重新执行回调。

# 生命周期

Vue 3生命周期

  • Vue 3 中可以继续使用 Vue 2 中的生命周期钩子,但有有两个被更名:
    • beforeDestroy 改名为 beforeUnmount
    • destroyed 改名为 unmounted
  • Vue 3 也提供了 Composition API 形式的生命周期钩子,与 Vue 2 中钩子对应关系如下:
    • beforeCreate ===> setup()
    • created =======> setup()
    • beforeMount ===> onBeforeMount
    • mounted =======> onMounted
    • beforeUpdate ===> onBeforeUpdate
    • updated =======> onUpdated
    • beforeUnmount ==> onBeforeUnmount
    • unmounted =====> onUnmounted

# 自定义 hook 函数

自定义 hook 函数本质是一个函数,setup 函数中使用的 Composition API 进行了封装。

  • 类似于 Vue2 中的 mixin。

  • 自定义 hook 的优势:复用代码,让 setup 中的逻辑更清楚易懂

🌰 例子:

  • src 目录下创建新的目录 hook 存储 hook 函数,创建文件 usePoint 为例(一般命名开头为 use ):
// 实现获取鼠标坐标
import {onBeforeUnmount, onMounted, reactive} from "vue";

export default function () {
    // 实现的数据
    let point = reactive({
        x: 0,
        y: 0
    })

    // 实现的方法
    function savePoint(event) {
        point.x = event.pageX
        point.y = event.pageY
        console.log(event.pageX, event.pageY)
    }

    // 实现的相关钩子
    onMounted(() => {
        window.addEventListener('click', savePoint)
    })

    onBeforeUnmount(() => {
        window.removeEventListener('click', savePoint)
    })

    return point
}
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
  • 在要使用这个 hook 函数的组件,引入当作函数并使用:
import usePoint from "../hooks/usePoint";
1
setup() {
  let point = usePoint()

  return {
    point
  }
}
1
2
3
4
5
6
7

# toRef 函数

  • toRef 函数创建一个 ref 对象,其 value 值指向另一个对象中的某个属性。
  • 语法: const name = toRef(person,'name') / toRefs(person)
  • 应用:要将响应式对象中的某个属性单独提供给外部使用时

ref 函数不同, ref 函数会重新创建新的引用 RefImpl 对象; toRef 创建的是引用原来的 RefImpl 对象。

🌰:使用 toRefs() 批量处理 setup 中的某个对象:

setup(){
	let person = reactive({
		name: 'Simon',
    age: 3
	})
  
  return {
 	 	...toRefs(person)
	}
}
1
2
3
4
5
6
7
8
9
10

# 其他 API

# shallowReactive 原始 shallowRef 函数

  • 浅层响应式数据的定义

    • shallowReactive :只处理对象最外层属性的响应式(浅响应式)。

    • shallowRef :只处理基本数据类型的响应式,不进行对象的响应式处理(对于使用这个函数定义的对象,不会借用 reactive 处理对象为 Proxy 对象)。

  • 应用场景

    • 如果只有一个对象数据,由多层对象结构(结构较深层),但只需要实现外层属性的变化,使用 shallowReactive 函数定义。
    • 如果只有一个对象数据后续功能不会修改该对象中的属性,而是生成新的对象来替换,使用 shallowRef 函数定义。

# readonlyshallowReadonly 函数

  • 只读属性相关的 API :(* 对于定义的响应式数据有效)
    • readonly :让一个响应式数据变为只读的(深只读);
    • shallowReadonly :让一个响应式数据变为只读(浅只读);
  • 应用场景不希望响应式数据被修改时。(当收到来自另一个组件定义的响应式数据,在现组件不希望引入的数据被修改时)

# toRawmarkRaw 函数

生成与标记为原始普通数据:

  • toRaw
    • 作用:将一个由 reactive (只能是)定义的响应式对象转为普通对象(原始)。
    • 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作不会引起页面的变化
  • markRaw
    • 用于某些数据 ** 不应该被赋值时自动设置为响应式数据的,** 例如复杂的第三方库数据。
    • 当渲染具有不可变数据源的大列表时,跳过响应式转换会提高渲染性能

# customRef

  • 作用:创建一个自定义 ref并且对其依赖项跟踪和更新触发进行显示控制

🌰 实现防抖效果(延迟显示):

点击查看
// 自定义一个ref
function myRef(value, delay) {
  let timer
  // console.log('myRef', value)
  return customRef((track, trigger) => {
    return {
      // 类似计算属性get/set
      get() {
        console.log(`read from myRef, value: ${value}`)
        track() // 通知Vue追踪value的变化(告诉Vue)
        return value
      },
      set(newValue) {
        console.log(`modify in myRef, after Modified Value: ${newValue}`)
        clearTimeout(timer)
        timer = setTimeout(() => {
          value = newValue
          trigger() // 通知Vue重新解析模版
        }, delay)
      },
    }
  })
}

let keyword = myRef('hello', 500) // 使用自定义ref
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

# provideinject

  • 作用: 实现 **(祖与后代)跨级组件之间 ** 的通信。
  • 套路:父组件有一个 provide 选项来提供数据,后代组件有一个 inject 选项来开始使用这些数据。

🌰 例子:

  • 在祖组件中的 setupprovide :
setup(){
	......
    let car = reactive({name:'奔驰',price:'40万'})
    provide('car',car)
    ......
}
1
2
3
4
5
6
  • 在后代组件中使用 inject 获取:
setup(props,context){
	......
    const car = inject('car')
    return {car}
	......
}
1
2
3
4
5
6

# 响应式数据判断

当数据逐渐增多或者获取从它处来的数据,未知该数据的响应式类型(是否为响应式),需要进行该数据的判断,使用下列函数判断:

  • isRef :检查目标数据是否为 ref 创建的引用对象;
  • isReactive :检查目标数据是否由 reactive 创建的响应式代理;
  • isReadonly :检查目标数据是否为由 readonly 创建的只读代理;
  • isProxy :检查一个对象是否由 reactive 或者 readonly 方法创建的代理。

# Composition API 的优势

# Options API 存在的问题

提示

使用传统 Options API (配置式 API)中,新增或者修改一个需求,就需要分别在 datamethodscomputed 里修改 。

# Composition API 的优势

提示

⭐️ 在 Composition API (基于函数组合 API)中,可以整合在一块组织代码、函数(配合自定义钩子函数使用),让相关功能的代码更加有序的组织在一起

📢 上次更新: 2022/09/02, 10:18:16