头图

summary

This article extracts the main parts of the Vue3 reactive source code to understand the implementation of its reactive object . The source code of this article is based on this entry file: github reactive.ts

reactive()

Source code location: github reactive.ts

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

reactive The createReactiveObject function is called internally, and mutableHandlers as the proxy handler

createReactiveObject()

Source code location: github reactive.ts

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

It can be seen that the essence of ---c0ed896ef2e2dd601660e841e935db3d reactive is to convert ordinary object into Proxy . And Proxy only supports non-empty object , so the reactive function can only be used for object .

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

If used for reactive primitive types, the ref function provided by Vue3 should be used.

mutableHandlers

Source code location: github baseHandlers.ts

mutableHandlers and mutableCollectionHandlers new Proxy used for the second parameter of ---25416f23ba8ac4224c5996d6e2fbe8d7---, which is the core logic of get reactive the most important function get and set attributes.

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

createGetter()

Source code location: 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 - Handling internal variables

 // 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
}

The objects generated using reactive() all contain the attributes corresponding to ReactiveFlags , and you can output the effect: RunJS Demo .

Using the object generated by shallowReactive() , only the properties of the root node are responsive, which is the same as the characteristics of responsive variables in Vue2. See this example: Vue3 shallowReactive

part 2 - working with arrays

 const targetIsArray = isArray(target)

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

Proxy is an array, and when calling the array prototype method, it is actually first get to the method name, and then use this method to set the corresponding value, such as the following code:

 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})

// output sequentially as

 get push
get length
set 2
set length

See the actual operation effect: Javascript Proxy an array

part 3 - dealing with other cases

 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

If the variable initialized with reactive contains a value of type ref , it is not necessary to bring .value when taking the value. E.g:

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

See the actual operation effect: Vue3 reactive object with ref

createSetter()

Source code location: 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 - assignment

 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 - Detach the event

 // don't trigger if target is something up in the prototype chain of original
if (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

Only when adding ( TriggerOpTypes.ADD ) and setting a new value ( TriggerOpTypes.SET ) will trigger (trigger) subscribed events

Epilogue

Original link: https://runjs.work/projects/a082a7c4e4b748a8

The original example is kept updated.


jinling
455 声望25 粉丝

RunJS在线编辑器开发者。[链接]