前言
上一篇《Vue 3 Pre-Alpha / vue-next 源码学习之 vue/reactivity/reactive.ts》我们学习了使用 Proxy
实现的 reactive
构造出来的响应式的对象,但 reactive
接受的值的参数限于 Object
, Array
, Map
, Set
, WeakMap
, WeakSet
这些类型,而本篇介绍的 ref
与 reactive
也类似,但可以接收任何类型的值的参数。而且对值的监听的实现方式也不一样,使用 get
set
的方式对值实现拦截监听,然后在 get
操作时添加 track
,在 set
操作时调用 trigger
,从而达到响应式的效果。
源码分析
注意带【】符号的为关键代码,关键代码能体现作者的最关键最基础的实现思路
import { track, trigger } from './effect' // track 了之后就会把 effect add 到 dep 中,然后再把 dep push 到 effect.deps里,trigger 顾名思义就是触发相关的所有 effects 事件的执行 import { OperationTypes } from './operations' // set, add, delete, clear, get, has, iterate 操作类型 import { isObject } from '@vue/shared' import { reactive } from './reactive' // 上篇介绍的 reactive 构造函数,用于生成一个被 proxy 的代理对象 import { ComputedRef } from './computed' // 顾名思义,应该就是 与 Vue 2.x computed 类似的 Ref export interface Ref<T = any> { // 把所有值转化成一个 Object 类形,其值藏于解套 UnwrapRef 后的 value 中 _isRef: true value: UnwrapRef<T> } const convert = (val: any): any => (isObject(val) ? reactive(val) : val) // 如果值为 Object,就直接转成 Reactive 对象 export function ref<T extends Ref>(raw: T): T export function ref<T>(raw: T): Ref<T> export function ref(raw: any) { // ref 构造函数 if (isRef(raw)) { return raw } raw = convert(raw) const v = { // 【Ref 的关键代码】:把 raw 值使用 get set 转化成一个 watchable 的 Object _isRef: true, get value() { // get 操作需要 track track(v, OperationTypes.GET, '') return raw }, set value(newVal) { // set 操作需要 trigger raw = convert(newVal) trigger(v, OperationTypes.SET, '') } } return v as Ref } export function isRef(v: any): v is Ref { // 是否为 Ref 类型呢?v._isRef === true 就是 return v ? v._isRef === true : false } export function toRefs<T extends object>( // 把 object (K, T[K]) 对像转化成另外一个 key 相同为 K 但值为 Ref<T[K]> 的 Object object: T ): { [K in keyof T]: Ref<T[K]> } { const ret: any = {} for (const key in object) { ret[key] = toProxyRef(object, key) } return ret } function toProxyRef<T extends object, K extends keyof T>( // 把 T[K] 变成 Ref<T[K]> object: T, key: K ): Ref<T[K]> { return { _isRef: true, get value(): any { return object[key] }, set value(newVal) { object[key] = newVal } } } type BailTypes = | Function | Map<any, any> | Set<any> | WeakMap<any, any> | WeakSet<any> // Recursively unwraps nested value bindings. export type UnwrapRef<T> = { // 神奇的递归解套,UnwrapRef({a: {b: {c: 1}}}}) => {1删object: {2删a : {3删object: {4删b: {5删object: {6删c: {7删ref: 1}}}}}}} => 1 cRef: T extends ComputedRef<infer V> ? UnwrapRef<V> : T ref: T extends Ref<infer V> ? UnwrapRef<V> : T array: T extends Array<infer V> ? Array<UnwrapRef<V>> : T object: { [K in keyof T]: UnwrapRef<T[K]> } }[T extends ComputedRef<any> ? 'cRef' : T extends Ref ? 'ref' : T extends Array<any> ? 'array' : T extends BailTypes ? 'ref' // bail out on types that shouldn't be unwrapped : T extends object ? 'object' : 'ref']
总结
要实现数据的响应式的绑定,实现的思路有很多,如 Vue 2.x 使用的是 Object.definedProperty 的方式。而到了 Vue 3 则大胆使用 ES6 的 Proxy
(reactive
的实现) 及 get
set
(Ref
的实现)。还有不得不说,本篇源码中的 typescript 的神奇的递归解套真是非常精彩。真是应了那句:Talk is cheap, show me the code!