The profile

Vue3’s ReActivity is a separate package, which is a big change, with all the response-related implementations in it, and that’s what I’m talking about.

Knowledge to prepare

1. Proxy: es6 proxy implementation method 2. Reflect: Put some object objects that are obviously language internal methods on Reflect, 3. WeakMap: weakMap key can only be object type. 4. WeakSet: weakSet object is a collection of some object values, and each object value can only appear once. Reactive brief implementation We used to write reactive data like this

data () {
    return {
        count: 0
    }
}Copy the code

Then vu3’s new responsive writing style (compatible with old ones)

 setup() {
     const state = {
         count: 0,
         double: computed(() => state.count * 2)
     }
     function increment() {
         state.count++
     }
onMounted(() => {
    console.log(state.count)
})

watch(() => {
    document.title = `count ${state.count}`
Copy the code

Copy the code

})
return {
state,
increment
}
}

Unlike the React hooks, the setup() function is called only once. The setup() function is called only once. So the new response data can be declared in two ways: 1.Ref Prerequisite: declare a type Ref

export interface Ref<T> {
  [refSymbol]: true
  value: UnwrapNestedRefs<T>
}Copy the code

Ref () function

function ref(raw: Unknown) {if (isRef(raw)) {return raw} Raw = convert(raw) const r = {_isRef: Track (r, operationtypes.get, ") return raw}, Set value(newVal) {raw = convert(newVal) // trigger Trigger (r, operationtypes.set, '')}} return r as Ref}Copy the code

Take a look at convert

const convert = val => isObject(val) ? reactive(val) : valCopy the code

As you can see, the ref type wraps only the outermost layer, and the internal object is ultimately called Reactive to generate the Proxy object for the reactive Proxy. Why not use proxy for all internal objects and use Ref for the outermost object? The reason may be simple: proxies are objects, and for primitive data types, function passes, or object structures, references to the original data are lost. Official explanation:

However, the problem with going reactive-only is that the consumer of a composition function must keep the reference to the returned object at all times in order to retain reactivity. The object cannot be destructured or spread:

2.Reactive premise: First understand weakMap

WeakMaps that store {raw <-> observed} pairs. Const rawToReactive = new WeakMap<any, any>() // Key: original object value: Proxy const reactiveToRaw = new WeakMap<any, any>() const rawToReadonly = new WeakMap<any, any>() const readonlyToRaw = new WeakMap<any, any>() reactive(target)Copy the code

Note: Target must be an object, otherwise a warning will be generated

Function reactive(target) {if (readonlytoraw. has(target)) {return target} If (readonlyvalues.has (target)) {return readonly(target)} // go ----> step2 return CreateReactiveObject (target, rawToReactive reactiveToRaw, mutableHandlers, / / attention transfer mutableCollectionHandlers)}Copy the code

createReactiveObject(target,toProxy,toRaw,baseHandlers,collectionHandlers)

function createReactiveObject( target: unknown, toProxy: WeakMap<any, any>, toRaw: WeakMap<any, any>, baseHandlers: <any>, collectionHandlers: ProxyHandler<any>) {// If (! isObject(target)) { if (__DEV__) { console.warn(`value cannot be made reactive: ${String(target)} ')} return target} let observed = toProxy (target) if (observed! == void 0) {return observed} // If (toraw. has(target)) {return target} // If (toraw. has(target)) {return target} If (! CanObserve (target) {return target} const handlers = collectionTypes.has(target.constructor) ? collectionHandlers : ----> Step3 Observed = new Proxy(target, 2 weakMap store target Observed toproxy. set(target, observed) Toraw. set(target, observed) if (! targetMap.has(target)) { targetMap.set(target, new Map()) } return observed }Copy the code

Handlers are as follows, and the handles of a new Proxy(target, handles) are this object

export const mutableHandlers = {
  get: createGetter(false),
  set,
  deleteProperty,
  has,
  ownKeys
}Copy the code

CreateGetter (false) {reactive(res);} createGetter(false) {reactive(res);

reactive(target) -> createReactiveObject(target,handlers) -> new Proxy(target, handlers) -> createGetter(readonly) -> get() -> res -> isObject(res) ? reactive(res) : res

function createGetter(isReadonly: Boolean) {// isReadonly is used to distinguish whether the data is read-only and responsive // Receiver is a proxy object created. string | symbol, receiver: Const res = reflect. get(target, key, Receiver) if (isSymbol(key) && builtinsymbols.has (key)) {return res} if (isRef(res)) {return res.value} // Collect dependencies Track (target, operationtypes.get, key) // Call reactive and pass the res. Return isObject(res)? isReadonly ? // need to lazy access readonly and reactive here to avoid // circular dependency readonly(res) : reactive(res) : res } }Copy the code

One of the main functions of a set set is to trigger a listener to try to update, but notice that it controls when the view actually needs to be updated, right

function set( target: object, key: string | symbol, value: unknown, receiver: object ): OldValue = (target as any)[key] const oldValue = (target as any)[key] // oldValue = (target as any)[key] And returns if (isRef(oldValue) &&! isRef(value)) { oldValue.value = value return true } const hadKey = hasOwn(target, Key) const result = reflect. set(target, key, value, receiver) // If it is a data operation on the original prototype chain, do nothing to trigger the listener function. If (target === toRaw(receiver)) {// Update two conditions // 1. // 2. Old and new values are not equal if (! hadKey) { trigger(target, OperationTypes.ADD, key) } else if (hasChanged(value, oldValue)) { trigger(target, OperationTypes.SET, key) } } return result }Copy the code

(1) Let a = [1], a.paush (2); (2) Let a = [1], a.paush (2); (3) Set length 1; 2. Set value 2 (traps) If the set key is length, we can determine that the array already has this property, so we do not need to update it. If the new value is the same as the old value, we do not need to update it.

Problem 3: There is a target === toRaw(receiver) condition before continuing to operate on the trigger update view, which is exposed as a target! == toRaw(receiver) Receiver: the object that is called initially. This is usually the proxy itself, but the handler’s set method can also be called indirectly (and therefore not necessarily the proxy itself) on the prototype chain or in other ways

// don’t trigger if target is something up in the prototype chain of original

If our operation is a data operation on the raw data prototype chain, target is not equal to toRaw(Receiver). == toRaw(receiver) Example:

Const child = new Proxy({}, {// Other traps omit set(target, key, value, receiver) {reflect. set(target, key, value, receiver) receiver) console.log('child', receiver) return true } } )

const parent = new Proxy( { a: 10}, {// Other traps omit set(target, key, value, receiver) {reflect. set(target, key, value, receiver) {// Other traps omit set(target, key, value, receiver) {Reflect. receiver) console.log('parent', receiver) return true } } )

Object.setPrototypeOf(child, parent) // child.proto === parent true

child.a = 4

Copy the code

// parent Proxy {a: 4} // Proxy {a: 4}

In theory, the parent’s set should not fire, but it does

Target: {a: 10} receiver: Proxy {a: 4} in VUe3 toRaw(receiver): {a: 4}Copy the code

Why do we need a Ref when we have a proxy to do reactive? Because Proxy cannot hijack the basic data type, we designed such an object — Ref. In fact, there are still many design details, I will not go into details. The official website also gives them the differences, you can learn about them.