Vue3 new features

  1. Composition API.

    Unlike Vue2’s optional API, the composite API initializes the component using the setup method, which contains all data and methods and returns them uniformly.

    Here’s an example:

    Vue3 composition API:
    
    <script>
    export default{
    	setup(){
    		const count =ref (0);
            const add=()=>{
            	count.value++;
            }
    		return {
    			count,
    			add
    		}
    	}
    }
    </script>
    Copy the code
  2. Proxy-based response.

    Responsive data in Vue2 is implemented using ES5’s data hijacking setter/getter, using object.defineProperty, but can only hijack existing object properties, for the properties on the object. Even a few overrides (such as dynamically adding keys to an object, or subscripting an array) can’t completely cover the changes.

    In Vue3, Proxy is used for responsive data, aiming at the whole object, directly Proxy the object, and intercepts data changes when modifying the Proxy object.

    Const data={} const dataProxy=new Proxy(data,{set(target,key,val){// Reflact.set()} get(target,key,val){// Reflact.get() } })Copy the code

    The proxy.revocable () method can be used to create a revocable Proxy object.

    const target = { name: 'vuejs'} const {proxy, revoke} = proxy.revocable (target, handler) proxy.name Revocation proxy.name // TypeError: RevokedCopy the code

    However, Proxy is only used in ES6 environment and cannot compile and degrade yet.

  3. Optimization for Tree-shaking.

    When using Vue3, you can choose to import the appropriate modules on demand, rather than importing all the code at once, so that when packaging, Vue3 removes unreferenced source code, thereby reducing the volume.

  4. Optimization of rendering performance.

    Vue3 moves the rendering code such as static nodes, subtrees, and so on out of the rendering function to avoid recreating these unchanged objects every time you render them. Subdivide the update type of an element. For example, if only class is involved in a dynamic binding, only the class is compared, not its content. In addition, Vue3 also has a number of changes, such as the support for multi-root components in the template, and so on, I will not cover them here.

The code structure

Vue3 code is mainly divided into packages and scripts two directories, script is mainly used for code inspection, packaging and other engineering operations, the real source code is located in the packages directory, a total of 13 packages:

  • compiler-coreThe template parsing core, independent of the specific environment, mainly generates the AST and is generated based on the ASTrender()methods
  • compiler-domTemplate parsing logic in the browser environment, such as handling HTML escapes, handling V-Model instructions, and so on
  • compiler-sfcResponsible for parsing Vue single file components, as explained in previous VUe-Loader parsing
  • compiler-ssrTemplate parsing logic in the server rendering environment
  • reactivityReactive data correlation logic
  • runtime-corePlatform-independent runtime cores, including Render
  • runtime-domThe runtime core in the browser environment
  • runtime-testRelated support for automated testing
  • server-rendererLogic for SSR server rendering
  • sharedSome common tools shared between packages
  • size-checkA sample library to test the size of tree code after shaking
  • template-explorerIt is used to check the compiled output of a template and is mainly used for development debugging
  • vueThe main entry point for Vue 3, including the runtime and compiler, includes several different entries (development version, Runtime version, Full version)

reactivity

The whole process

ReactiveFlags

export const enum ReactiveFlags {
  skip = '__v_skip',
  isReactive = '__v_isReactive',
  isReadonly = '__v_isReadonly',
  raw = '__v_raw',
  reactive = '__v_reactive',
  readonly = '__v_readonly'
}
Copy the code
  • The proxy object will passReactiveFlags.rawReference to the original object
  • The original object will passReactiveFlags.reactiveReactiveFlags.readonlyReference proxy object
  • Proxy object according to which it isreactivereadonlyAnd willReactiveFlags.isReactiveReactiveFlags.isReadonlyThe property value is set totrue

Track and the Trigger

Track () and trigger() are the core of dependency collection. Track () is used to track dependencies (collect effect) and trigger() is used to trigger responses (perform effect). They need to be used in conjunction with the effect() function

const obj = { foo: 1 }
effect(() => {
  console.log(obj.foo)
  track(obj, TrackOpTypes.GET, 'foo')
})

obj.foo = 2
trigger(obj, TriggerOpTypes.SET, 'foo')
Copy the code

The track and trigger functions take three arguments:

  • Target: The target object to track, here it isobj
  • Type of trace operation:obj.fooIs to read the value of the object, so is'get'
  • Key: To track the target objectkey, we’re readingfoo, sokeyfoo

Essentially create a data structure:

/ / map for pseudo code: {/ target: {[key] : [effect1, effect2...]. }}Copy the code

The simple understanding is that effect is associated with the key of the object and the specific operation by this mapping:

[target]`---->`key1`---->`[effect1, effect2...]  [target]`---->`key2`---->`[effect1, effect3...]  [target2]`---->`key1`---->`[effect5, effect6...]Copy the code

Since effect is already associated with target, you can of course fetch effect via target —-> key and execute them. This is what trigger() does. So when we call the trigger function we specify the target object and the corresponding key value.

toRef

Sometimes there are secondary references to the object, and the response is lost. (When the object is destructed and returned to the render environment, the response is lost.)

Const obj2 = {foo; reactive({foo: 1}); Obj.foo} effect(() => {console.log(obj2.foo) // here read obj2.foo}) obj.foo = 2 // Setting obj.foo is obviously invalidCopy the code

You can solve the problem by using the toRef() function to convert one of the Key values of the responsive object toRef. Its implementation simply returns set and GET, and since the target itself is responsive, no track or trigger is required.

function toRef(target, key) {
    return {
        isRef: true,
        get value() {
            return target[key]
        },
        set value(newVal){
            target[key] = newVal
        }
    }
}
Copy the code

But the value of ref requires the dot value to access the value. And if we don’t want to use dot value, we can just make another layer of reactive.

const obj = reactive({ foo: 1 }) // const obj2 = { foo: toRef(obj, 'foo') } const obj2 = reactive({ ... ToRefs (obj)}) // Make obj2 also reactive (() => {console.log(obj2.foo) // even if obj2.foo is ref, }) obj.foo = 2 // ValidCopy the code

Its implementation, finds the value if it’s ref, returns.value. But for an array of refs, you still need dot value to access it.

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

Usually when we use the ref() function, we are referring to the original data type, but it is also possible to refer to non-basic types. For example:

const refObj=ref({foo:1})
Copy the code

Refobj.value is an object that is responsive. Modifying refobj.value.foo triggers a response. While shallowRef is a shallow proxy, only the ref object itself is represented, i.e..value is represented, while the object referenced by.value is not. Modifying refj.value.foo does not trigger a response. What if you want to trigger the response as well? Vue3 provides a triggerRef function that lets us force the response to be triggered.

triggerRef(refObj) 
Copy the code

Vue3 Diff

General process:

  1. Compare from the beginning to find the same node patch, find the difference, immediately jump out.

  2. If the first step does not complete the patch, immediately, start the patch from the back to the front, and immediately break out of the loop if the difference is found.

  3. If the number of new nodes is greater than the number of old nodes, the remaining nodes are treated as new VNodes (in this case, the same Vnodes have been patched).

  4. If the number of old nodes is larger than the number of new nodes, uninstall all the excess nodes (in this case, the same Vnodes have been patched).

  5. Uncertain elements (which means that no vNode has the same patch) are opposed to 3 and 4. As follows:

         // [i ... e1 + 1]: a b [c d e] f g
    
         // [i ... e2 + 1]: a b [e d c h] f g
    
         // i = 2, e1 = 4, e2 = 5
    
    Copy the code
  • Use map to save new VNode nodes that have not been compared and record the number of new nodes that have been patched and the number of new nodes that have not been mapped toBePatched

  • Create an array newIndexToOldIndexMap where each child element is a number in [0, 0, 0, 0, 0, 0, 0,] that records the index of the old node, and the array index is the index of the new node

  • Traversing the old node:

    • If the number of new nodes is 0 for toBePatched, uninstall the old nodes uniformly
    • If the key of the old node exists, the corresponding index is found by the key
    • If the key of the old node does not exist: iterate over all the remaining new nodes. If the new node corresponding to the current old node is found, then assign the index of the new node to newIndex
    • No new node corresponding to the old node was found. Uninstall the current old node
    • If a new node is found corresponding to the old one, the index of the old node is recorded in the array of the new nodes
  • If movement occurs:

  1. Find the longest stable sequence from the newIndexToOldIndexMap list of old and new node indexes
  2. NewIndexToOldIndexMap [I] =0 proves that there is no old node and a new vnode is formed
  3. The moving node is processed.

Diff algorithm comparison:

  • React: Walks the position of the new node sequence in the old node sequence. If the position increases, the new node does not need to move, otherwise the node moves back.
  • Vue 2: double-ended comparison. The start and end of the new and old node sequences are compared to each other, looking for reuse, until the pointer coincides, exit the loop. If no reuse node is found after four comparisons, only the nodes in the new sequence can be compared with the old sequence.
  • Vue 3: longest increasing subsequence. If List[j]==index indicates that the node does not need to be moved; otherwise, the node is inserted at the end of the queue.