概要

本文通过抽取Vue3 reactive源码中的次要局部,来了解其响应式object的实现形式。本文源码基于这个入口文件:github reactive.ts

reactive()

源码地位:github reactive.ts

export function reactive(target) {  return createReactiveObject(    target,    false,    mutableHandlers,    mutableCollectionHandlers  )}

reactive外部调用了createReactiveObject函数,并传入mutableHandlers作为Proxy的handler

createReactiveObject()

源码地位:github reactive.ts

function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers) {    const proxy = new Proxy(    target,    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers  )    return proxy}

可见reactive的实质,是将一般的object转换成了Proxy。而Proxy只反对非空的object,所以reactive函数也只能用于object

const handler = {}new Proxy({}, handler) // 失常运行new Proxy(1, handler) // Cannot create proxy with a non-object as target or handler

如果用于原始类型的响应式,应该应用Vue3提供的ref函数。

mutableHandlers

源码地位:github baseHandlers.ts

mutableHandlersmutableCollectionHandlers用于new Proxy的第二个参数,是reactive函数最外围的逻辑,重点关注其getset属性。

export const mutableHandlers = {  get: createGetter(),  set: createSetter(),  deleteProperty,  has,  ownKeys,}

createGetter()

源码地位:github baseHandlers.ts

function createGetter(isReadonly = false, shallow = false) {  return function get(target: Target, key: string | symbol, receiver: object) {    // 外面代码比拟长,上面拆成几局部了解    // part 1 - 解决外部变量    // part 2 - 解决数组    // part 3 - 解决其余状况  }}

part 1 - 解决外部变量

// ts语法const enum ReactiveFlags {  SKIP = '__v_skip',  IS_REACTIVE = '__v_isReactive',  IS_READONLY = '__v_isReadonly',  IS_SHALLOW = '__v_isShallow',  RAW = '__v_raw'}if (key === ReactiveFlags.IS_REACTIVE) {  return !isReadonly} else if (key === ReactiveFlags.IS_READONLY) {  return isReadonly} else if (key === ReactiveFlags.IS_SHALLOW) {  return shallow} else if (  key === ReactiveFlags.RAW &&  receiver ===  (isReadonly   ? shallow   ? shallowReadonlyMap   : readonlyMap   : shallow   ? shallowReactiveMap   : reactiveMap  ).get(target)) {  return target}

应用reactive()生成的object,都蕴含ReactiveFlags对应的属性,能够输入看下成果:RunJS Demo。

应用shallowReactive()生成的object,只有根结点的属性是响应式的,这和Vue2中的响应式变量的个性是一样的。参见这个示例:Vue3 shallowReactive

part 2 - 解决数组

const targetIsArray = isArray(target)if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {  return Reflect.get(arrayInstrumentations, key, receiver)}

Proxy为数组,且调用数组原型办法时,其实是先get到办法名,再用这个办法去set相应的值,比方上面的代码:

const arr = [{count: 1}, {count: 2}]const proxyArray = new Proxy(arr, {  get(target, prop, receiver) {    console.log(`get ${prop}`)    return Reflect.get(target, prop, receiver)  },  set(target, prop, receiver) {    console.log(`set ${prop}`)    return Reflect.set(target, prop, receiver)  }})proxyArray.push({count: 3})

// 顺次输入为

get pushget lengthset 2set length

理论运行成果见:Javascript Proxy an array

part 3 - 解决其余状况

const res = Reflect.get(target, key, receiver)if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {  return res}if (!isReadonly) {  track(target, TrackOpTypes.GET, key)}if (shallow) {  return res}if (isRef(res)) {  // ref unwrapping - skip unwrap for Array + integer key.  return targetIsArray && isIntegerKey(key) ? res : res.value}if (isObject(res)) {  // Convert returned value into a proxy as well. we do the isObject check  // here to avoid invalid value warning. Also need to lazy access readonly  // and reactive here to avoid circular dependency.  return isReadonly ? readonly(res) : reactive(res)}return res

reactive初始化的变量中,如果蕴含ref类型的值,则取值时不须要带上.value。例如:

const count = ref(0)const state = reactive({count: count})// 以下两行,都能取到count值console.log(count.value)console.log(state.count)

理论运行成果见:Vue3 reactive object with ref

createSetter()

源码地位:github baseHandlers.ts

function createSetter(shallow = false) {  return function set(    target: object,    key: string | symbol,    value: unknown,    receiver: object  ): boolean {    // part 1 - 赋值    // part 2 - 触发事件  }}

part 1 - 赋值

let oldValue = (target as any)[key]if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {  return false}if (!shallow) {  if (!isShallow(value) && !isReadonly(value)) {    oldValue = toRaw(oldValue)    value = toRaw(value)  }  if (!isArray(target) && isRef(oldValue) && !isRef(value)) {    oldValue.value = value    return true  }} else {  // in shallow mode, objects are set as-is regardless of reactive or not}const hadKey =      isArray(target) && isIntegerKey(key)? Number(key) < target.length: hasOwn(target, key)const result = Reflect.set(target, key, value, receiver)

part 2 - 解发事件

// don't trigger if target is something up in the prototype chain of originalif (target === toRaw(receiver)) {  if (!hadKey) {    trigger(target, TriggerOpTypes.ADD, key, value)  } else if (hasChanged(value, oldValue)) {    trigger(target, TriggerOpTypes.SET, key, value, oldValue)  }}return result

只有新增(TriggerOpTypes.ADD)和设置新值(TriggerOpTypes.SET)的时候,才会触发(trigger)订阅过的事件

结语

原文链接:https://runjs.work/projects/a082a7c4e4b748a8

原文示例放弃更新。