Vue 中的响应性是如何工作的 
可以追踪对象属性的读写的。
在 JavaScript 中有两种劫持 property 访问的方式:getter / setters 和 Proxies。Vue 2 使用 getter /setters 完全是出于支持旧版本浏览器的限制。而在 Vue 3 中则使用了 Proxy 来创建响应式对象,仅将 getter /setter 用于 ref。下面的伪代码将会说明它们是如何工作的:
function reactive(obj) {
  return new Proxy(obj, {
    get(target, key) {
      track(target, key)
      return target[key]
    },
    set(target, key, value) {
      target[key] = value
      trigger(target, key)
    }
  })
}
function ref(value) {
  const refObject = {
    get value() {
      track(refObject, 'value')
      return value
    },
    set value(newValue) {
      value = newValue
      trigger(refObject, 'value')
    }
  }
  return refObject
}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
Object.defineProperty (按属性劫持) 无法自动检测新增属性
Proxy (代理整个对象)支持动态添加响应式属性
在 Vue 3 中,Vue 使用 Proxy 实现响应式系统,这比 Object.defineProperty() 更强大,因为 Proxy 可以直接监听整个对象的变化,包括新增或删除属性。
状态机 
状态机是一种数据模型,用于描述应用可能处于的所有可能状态,以及从一种状态转换到另一种状态的所有可能方式。虽然对于简单的组件来说,这可能有些小题大做了,但它的确可以使得复杂的状态流更加健壮和易于管理。
XState 是 JavaScript 中一个比较常用的状态机实现方案。这里是集成它的一个例子:
import { createMachine, interpret } from 'xstate'
import { shallowRef } from 'vue'
export function useMachine(options) {
  const machine = createMachine(options)
  const state = shallowRef(machine.initialState)
  const service = interpret(machine)
    .onTransition((newState) => (state.value = newState))
    .start()
  const send = (event) => service.send(event)
  return [state, send]
}2
3
4
5
6
7
8
9
10
11
12
13
14
ref()  
在组合式 API 中,推荐使用 ref() 函数来声明响应式状态:
import { ref } from 'vue'
const count = ref(0)2
ref()  接收参数,并将其包裹在一个带有  .value  属性的 ref 对象中返回:
const count = ref(0)
console.log(count) // { value: 0 }
console.log(count.value) // 0
count.value++
console.log(count.value) // 12
3
4
5
6
7
<div>{{ count }}</div>注意,在模板中使用 ref 时,我们不需要附加  .value 。为了方便起见,当在模板中使用时,ref 会自动解包 (有一些注意事项)。 在模板渲染上下文中,只有顶级的 ref 属性才会被解包。
为  ref()  标注类型 
ref 会根据初始化时的值推导其类型:
import { ref } from 'vue'
// 推导出的类型:Ref<number>
const year = ref(2020)
// => TS Error: Type 'string' is not assignable to type 'number'.
year.value = '2020'2
3
4
5
6
7
有时我们可能想为 ref 内的值指定一个更复杂的类型,可以通过使用  Ref  这个类型:
import { ref } from 'vue'
import type { Ref } from 'vue'
const year: Ref<string | number> = ref('2020')
year.value = 2020 // 成功!2
3
4
5
6
或者,在调用  ref()  时传入一个泛型参数,来覆盖默认的推导行为:
// 得到的类型:Ref<string | number>
const year = ref<string | number>('2020')
year.value = 2020 // 成功!2
3
4
如果你指定了一个泛型参数但没有给出初始值,那么最后得到的就将是一个包含  undefined  的联合类型:
ts
// 推导得到的类型:Ref<number | undefined>
const n = ref<number>()2
<script setup>  
在  setup()  函数中手动暴露大量的状态和方法非常繁琐。幸运的是,我们可以通过使用单文件组件 (SFC) 来避免这种情况。我们可以使用  <script setup>  来大幅度地简化代码:
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
  count.value++
}
</script>
<template>
  <button @click="increment">
    {{ count }}
  </button>
</template>2
3
4
5
6
7
8
9
10
11
12
13
14
15
深层响应性 
Ref 可以持有任何类型的值,包括深层嵌套的对象、数组或者 JavaScript 内置的数据结构,比如  Map 。
Ref 会使它的值具有深层响应性。这意味着即使改变嵌套对象或数组时,变化也会被检测到:
import { ref } from 'vue'
const obj = ref({
  nested: { count: 0 },
  arr: ['foo', 'bar']
})
function mutateDeeply() {
  // 以下都会按照期望工作
  obj.value.nested.count++
  obj.value.arr.push('baz')
}2
3
4
5
6
7
8
9
10
11
12
DOM 更新时机 
当你修改了响应式状态时,DOM 会被自动更新。但是需要注意的是,DOM 更新不是同步的。Vue 会在 “next tick” 更新周期中缓冲所有状态的修改,以确保不管你进行了多少次状态修改,每个组件都只会被更新一次。
要等待 DOM 更新完成后再执行额外的代码,可以使用 nextTick() 全局 API:
import { nextTick } from 'vue'
async function increment() {
  count.value++
  await nextTick()
  // 现在 DOM 已经更新了
}2
3
4
5
6
7
reactive()  
还有另一种声明响应式状态的方式,即使用  reactive()  API。与将内部值包装在特殊对象中的 ref 不同, reactive()  将使对象本身具有响应性:
import { reactive } from 'vue'
const state = reactive({ count: 0 })2
3
为  reactive()  标注类型 
reactive()  也会隐式地从它的参数中推导类型:
import { reactive } from 'vue'
// 推导得到的类型:{ title: string }
const book = reactive({ title: 'Vue 3 指引' })2
3
4
要显式地标注一个  reactive  变量的类型,我们可以使用接口:
import { reactive } from 'vue'
interface Book {
  title: string
  year?: number
}
const book: Book = reactive({ title: 'Vue 3 指引' })2
3
4
5
6
7
8
TIP
不推荐使用  reactive()  的泛型参数,因为处理了深层次 ref 解包的返回值与泛型参数的类型不同。
reactive()  的局限性 
reactive()  API 有一些局限性:
- 有限的值类型:它只能用于对象类型 (对象、数组和如 - Map、- Set这样的集合类型)。它不能持有如- string、- number或- boolean这样的原始类型。
- 不能替换整个对象:由于 Vue 的响应式跟踪是通过属性访问实现的,因此我们必须始终保持对响应式对象的相同引用。这意味着我们不能轻易地 “替换” 响应式对象,因为这样的话与第一个引用的响应性连接将丢失: js- let state = reactive({ count: 0 }) // 上面的 ({ count: 0 }) 引用将不再被追踪 // (响应性连接已丢失!) state = reactive({ count: 1 })1
 2
 3
 4
 5
- 对解构操作不友好:当我们将响应式对象的原始类型属性解构为本地变量时,或者将该属性传递给函数时,我们将丢失响应性连接: js- const state = reactive({ count: 0 }) // 当解构时,count 已经与 state.count 断开连接 let { count } = state // 不会影响原始的 state count++ // 该函数接收到的是一个普通的数字 // 并且无法追踪 state.count 的变化 // 我们必须传入整个对象以保持响应性 callSomeFunction(state.count)1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12