🧊 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
  }
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- ⚠️ 注意:
- 尽量不要与 Vue 2.x 中的配置混用:
- Vue 2.x 的配置( data、methods、computed… )中可以访问到setup中的属性、方法。
- 但是在  setup配置中不能访问 Vue 2.x 的配置(data、methods、computed… )。
- 当  setup中的属性、方法与 Vue 2.x 的配置有重名时,以setup优先。
 
- Vue 2.x 的配置( 
- setup不能是一个- async函数,因为返回值不再是- return对象,而是一个被- async包裹的对象,而是- promise,此时模版无法使用- return中的属性(后期可以返回一个- Promise的实例,但是需要- Suspense和异步组件的配合 🔗 Suspense 组件)
 
- 尽量不要与 Vue 2.x 中的配置混用:
# ref  函数
 
- 在之前使用的
ref,作用是给原生的 DOM 标签添加标识,替代id的作用。- 在上述
setup的例子中定义的数据不是一个响应式的数据,发生变化 Vue 无法监测。
- ref函数作用:定义一个响应式的数据。
- 语法: - const xxx = ref(initValue)- 创建一个  ReflImpl:reference+implement包含响应式数据的引用ref对象(引用实现对象)。
- 在 JS 中操作数据:  xxx.value
- 在模版中读取数据:直接  (自动解析ReflImpl的value值)
 
- 创建一个  
- 注意: - 参数可以接收的数据有:基本数据类型、对象类型。
- 基本类型的数据:响应式依然是直接靠  Object.defineProperty()的get与set(数据劫持)完成的。
- 对象类型的数据:借助 Vue 3 中的  reactive函数。
 
# reactive  函数
 - reactive函数的作用:定义一个对象类型的响应式数据。(对于基本数据类型只能使用- ref,基本数据类型放在对象中再使用- reactive函数即可 )
- 语法: - const 代理对象 = reactive(源对象),参数接收一个对象(或数组),返回一个代理对象(- Proxy的实例对象,代理 Proxy 对象)
- 使用 - reactive函数定义的响应式数据是「深层次的」。
- reactive函数内部基于 ES6 的- Proxy函数实现,通过代理对象操作源对象的内部数据。
# 对比  ref  与  reactive  函数
 - 从定义数据类型角度对比: - ref用来定义基本数据类型。
- reactive用来定义对象(包括数组)类型。
 - reactive可以用来定义基本数据类型,封装在一个- data对象中即可。
- 从原理角度对比: - ref通过- Object.defineProperty()的- get与- set来实现响应式(数据劫持)。
- reactive通过使用- Proxy(数据劫持)与- Reflect(操作源对象内部的数据)结合实现响应式。
 
- 从实用角度对比: - ref定义的数据,操作数据需要使用- .value读取,在模版中可以直接读取。
- reactive定义的数据,操作和读取数据可以直接读取。
 
# setup  的注意点
 - setup执行的时机:- 在  beforeCreate之前执行一次,this是undefined。
 
- 在  
- 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.set(- this.$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)
    }
})
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,
  }
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]
  }
})
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})
2
3
4
- 监视普通的  ref定义的多个响应式数据:
watch([sum, msg], (newValue, oldValue)=>{
  console.log('newValue', newValue)
  console.log('oldValue', oldValue)
})
2
3
4
监视使用
ref定义的数据可以完整获取newValue、oldValue。
注意
对于  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})
2
3
4
注意
- 监视使用 - reactive定义的响应式数据不能获取- oldValue;
- 监视使用 - reactive定义的响应式数据,会强制开启深度监视(对于此事监视完整的一个对象而言)。
- 监视  reactive定义的响应式数据的某一个属性或者某些属性,第一个参数需要为一个函数的返回值或者对象,监测多个属性需要使用数组包裹。
watch(() => person.age, (newValue, oldValue) => {
  console.log('newValue', newValue)
}, {immediate: true, deep: false})
2
3
watch([() => person.age, () => person.name], (newValue, oldValue) => {
  console.log('newValue', newValue)
}, {deep: false})
2
3
注意
- 当监视的为  reactive定义的响应式数据的某一个属性为一个对象时,并且其中有需要深度监视的属性,此时需要开启深度监视。
watch(() => person.job, (newValue, oldValue) => {
  console.log('oldValue', oldValue)
  console.log('newValue', newValue)
}, {deep: true})
2
3
4
由于监视的是 reactive 定义的对象中的某个属性,所以 deep 配置有效
# watchEffect  函数
 - 在  watch函数中:既要指明监视的属性,也要指明监视的回调。
- 在  watchEffect函数中:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。
- 与  computed函数相似,在computed函数中注重计算出来的值(回调函数的返回值),所以必须要返回一个值;但是watchEffect注重的是过程(回调函数的函数体),所以不用写返回值。
🌰  watchEffect  函数的使用例子:
watchEffect(()=>{
  const x1 = sum.value
  const x2 = person.age
  console.log('watchEffect回调执行')
})
2
3
4
5
只要
watchEffect指定的回调函数中用到的数据发生变化,则直接重新执行回调。
# 生命周期
- 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
}
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";
setup() {
  let point = usePoint()
  return {
    point
  }
}
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)
	}
}
2
3
4
5
6
7
8
9
10
# 其他 API
# shallowReactive  原始  shallowRef  函数
 - 浅层响应式数据的定义: - shallowReactive:只处理对象最外层属性的响应式(浅响应式)。
- shallowRef:只处理基本数据类型的响应式,不进行对象的响应式处理(对于使用这个函数定义的对象,不会借用- reactive处理对象为- Proxy对象)。
 
- 应用场景: - 如果只有一个对象数据,由多层对象结构(结构较深层),但只需要实现外层属性的变化,使用  shallowReactive函数定义。
- 如果只有一个对象数据,后续功能不会修改该对象中的属性,而是生成新的对象来替换,使用  shallowRef函数定义。
 
- 如果只有一个对象数据,由多层对象结构(结构较深层),但只需要实现外层属性的变化,使用  
# readonly  与  shallowReadonly  函数
 - 只读属性相关的 API :(* 对于定义的响应式数据有效)
- readonly:让一个响应式数据变为只读的(深只读);
- shallowReadonly:让一个响应式数据变为只读(浅只读);
 
- 应用场景:不希望响应式数据被修改时。(当收到来自另一个组件定义的响应式数据,在现组件不希望引入的数据被修改时)
# toRaw  与  markRaw  函数
 生成与标记为原始普通数据:
- 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# provide  与  inject
  
 - 作用: 实现 **(祖与后代)跨级组件之间 ** 的通信。
- 套路:父组件有一个  provide选项来提供数据,后代组件有一个inject选项来开始使用这些数据。
🌰 例子:
- 在祖组件中的  setup中provide:
setup(){
	......
    let car = reactive({name:'奔驰',price:'40万'})
    provide('car',car)
    ......
}
2
3
4
5
6
- 在后代组件中使用  inject获取:
setup(props,context){
	......
    const car = inject('car')
    return {car}
	......
}
2
3
4
5
6
# 响应式数据判断
当数据逐渐增多或者获取从它处来的数据,未知该数据的响应式类型(是否为响应式),需要进行该数据的判断,使用下列函数判断:
- isRef:检查目标数据是否为- ref创建的引用对象;
- isReactive:检查目标数据是否由- reactive创建的响应式代理;
- isReadonly:检查目标数据是否为由- readonly创建的只读代理;
- isProxy:检查一个对象是否由- reactive或者- readonly方法创建的代理。
# Composition API 的优势
# Options API 存在的问题

提示
使用传统 Options API (配置式 API)中,新增或者修改一个需求,就需要分别在  data 、 methods 、 computed  里修改 。
# Composition API 的优势


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