“This is the 19th day of my participation in the First Challenge 2022, for more details: First Challenge 2022”.

preface

This article on Vue3.0 responsive source code for learning, Vue official document: in-depth responsive principle

One of the most unique features of Vue is its non-invasive and responsive system. Data models are proxied JavaScript objects. And when you modify them, the view is updated.

Responsive API experience

If you want to use reactive data in Vue3 setup, you can use the Reactive and Refs apis

<body>
  <div id="app">
    <h1>vue3 reactivity API</h1>
    <p>{{ state.counter }}</p>
    <p>{{ counter2 }}</p>
  </div>
  <script>
    const app = Vue.createApp({
      setup() {
        const state = Vue.reactive({
          counter: 1
        })
        setInterval(() = > {
          state.counter++
        }, 1000)
        Vue.watch(() = > state.counter, () = >{})const counter2 = Vue.ref(1)
        setInterval(() = > {
          counter2.value++
        }, 1000)
        Vue.watch(counter2, () = >{})return {
          state,
          counter2
        }
      }
    })

    app.mount('#app')
  </script>
</body>
Copy the code

Differences in use

  • Ref () takes a primitive data type and transforms it into a reactive feature, and Reactive () is a reactive feature for an Object

  • To use or modify ref data, add.value. To use or modify rective data, use it directly

  • The.value is not required if the ref is used directly in HTML after the export, and the object data wrapped in rective requires an outer key value, such as state.xxx

  • The data in rective can be used directly in HTML by toRef, which converts toRefs into ref exports

// Convert a single attribute
const state = reactive({
  foo: 1.bar: 2
})

const fooRef = toRef(state, 'foo')

fooRef.value++
console.log(state.foo) / / 2

state.foo++
console.log(fooRef.value) / / 3

Copy the code
// Convert the entire object
const state = reactive({
  foo: 1.bar: 2
})
const stateAsRefs = toRefs(state)

// the ref and the original property are "linked"
state.foo++
console.log(stateAsRefs.foo.value) / / 2

stateAsRefs.foo.value++
console.log(state.foo) / / 3
Copy the code

Reactive function source code parsing

  • Find the location defined by Reactive () packages\reactivity\ SRC \ reaction.ts

  • Reactive () returns the result of createReactiveObject(), which can be used to turn a normal object into a reactive object

export function reactive(target: object) {
  // if trying to observe a readonly proxy, return the readonly version.
  if (isReadonly(target)) {
    return target
  }
  // How do I convert an incoming plain object into a responsive object
  return createReactiveObject(
    target,
    false,
    mutableHandlers, /* Proxy handler for ordinary objects */
    mutableCollectionHandlers, 
    reactiveMap
  )
}
Copy the code
  • createReactiveObject()To the first parametertarget(i.e.reactiveWrapped object) made a layerProxyThe agentMDN Proxy document
  • Generally, the incoming objects are ordinary objects, so inProxyIn thehandlerWill usebaseHandlersIs in thereactive()In the incomingmutableHandlers
function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>
) {...// Create a proxy object, using the original object passed as the proxy target
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  proxyMap.set(target, proxy)
  return proxy
}
Copy the code
  • mutableHandlerslocationpackages\reactivity\src\baseHandlers.ts, of which thegettersetterAn interception operation was performed
export const mutableHandlers: ProxyHandler<object> = {
  get,
  set,
  deleteProperty,
  has,
  ownKeys
}
Copy the code
  • getterIf the proxy object is an object and not read-only, the proxy object type will be executedtrack()To collect dependencies
function createGetter(isReadonly = false, shallow = false) {
  return function get(target: Target, key: string | symbol, receiver: object) {...// Target is array processing
    const targetIsArray = isArray(target)

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

    // Target is an object
    const res = Reflect.get(target, key, receiver)

    if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
      return res
    }

    // If it is not read-only
    if(! isReadonly) {// Establish the relationship between target,key, and dependent functions
      track(target, TrackOpTypes.GET, key)
    }

    if (shallow) {
      return res
    }

    if (isRef(res)) {
      // ref unwrapping - does not apply for Array + integer key.
      constshouldUnwrap = ! targetIsArray || ! isIntegerKey(key)return shouldUnwrap ? res.value : res
    }

    // If the object is processed recursively
    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
  }
}
Copy the code
  • tracklocationpackages\reactivity\src\effect.ts, will put the collected dependencies intodepsMapObject, and then trace the dependenciestrackEffectsTrigger an update at some point in the future
// Rely on collection
export function track(target: object, type: TrackOpTypes, key: unknown) {...let depsMap = targetMap.get(target)
  if(! depsMap) { targetMap.set(target, (depsMap =new Map()))}let dep = depsMap.get(key)
  if(! dep) { depsMap.set(key, (dep = createDep())) } ...// Rely on trace
  trackEffects(dep, eventInfo)
}
Copy the code
  • After collecting dependencies, you can watch in a single step to see how the data changes trigger updates, which will be explained in the next article