Vue's past and present life | core principle analysis

Posted by Dan06 on Wed, 08 Dec 2021 23:49:37 +0100

Vue's past and present life

  • 2013 youyuxi personal project
  • Release of version 0.1 in February 2014
  • Release of version 1.0 on October 2015
    • Template syntax improvement
  • 2016.9 version 2.0 release
    • Span end
    • New rendering mechanism
  • 2019.10 3.0 alpha release
    • performance
    • framework
    • On demand import
    • Composition API
    • Proxy observer
    • AOT optimization

Vue 1 responsive principle

Building responsive object processes

  • The walk function traverses the attributes in the data object and calls defineReactive to turn it into a responsive object
    • Recursively call walk for object attributes to ensure that the attributes in the whole data object tree are responsive objects.
  • In defineReactive, the watchers array is used to store the watcher, the get function of Object.defineProperty is used to collect the watcher and return value, and the set function is used to set the value and update the view of the watcher in the watchers.

Implementation of Walk function

function walk(data){

    Object.keys(data).foreach(key => {
        defineReactive(data, key, data[key])
        //Object recursively calls walk
        walk(data[key])
    })
}

Implementation of defineReactive function

function defineReactive(obj, key, value){
    let oldValue = value;
    const watchers = []
    Object.defineProperty(obj, key, {
        get(){
            //Collect watcher
            watchers.push(currentWatcher)
            return oldValue
        },

        set(){
            if(newValue === oldValue) return;
            oldValue = newValue;
            watchers.forEach(watcher => wathcer.update())//update the view
        }
    })

}

What the hell is Watcher after all this time?

image-20210320163559768

  • Watcher is used to obtain data and update views, and implement vue instructions
    • The watcher get s the data render view from data, and the responsive object in data hijacks the current watcher and "stores" it
    • Data updating data will trigger the set function of the responsive object, take out and traverse the "stored" watchers when get ting data, and "notify" them to update the view.
    • The watcher "receives the data update notification in data" and rerender the render view.
    • When the view changes, the set function of the responsive object in the data will be triggered to form a data flow loop.
  • Example:
// vm points to the current component, el points to the current dom node, the third parameter is the label type, and the fourth is the callback function
// currentWatcher is a pointer to a global variable


// Normal rendered watcher
Watcher(vm, el, 'text', () =>{
    // Point the currentWatcher object to the current watcher (vdom node) for the get function of the responsive object
    currentWatcher = this;
    // Read the displayed content
    el.textContext = eval('vm.data.text')
    // Unbind currentWatcher to prevent errors.
    currentWatcher = null
})


//watcher with v-if instruction
Watcher(vm, el, 'text', () =>{
    // Point the currentWatcher object to the current watcher (vdom node) for the get function of the responsive object
    currentWatcher = this;
    // Implement the v-if instruction to determine whether to display the element by judging the variable value. The principle of v-show is similar
    el.style.display = eval('Boolean(vm.data.text)') ? 'block' : 'none'
    // Unbind currentWatcher to prevent errors.
    currentWatcher = null
})

Several obvious problems in Vue 1

  1. Intercept the state of all components at startup, and perform recursive responsive proxy, which affects the first rendering speed
  2. The memory usage is high. A watcher needs to be created for an instruction, calculated attribute, handlebar expression, etc. too many watchers lead to high memory usage.
  3. After the template is compiled, it can directly operate the dom and cannot cross end.

Optimization in Vue

  1. New rendering engine - vdom
  2. Watcher relies on strength adjustment
  3. other
    1. Redesign and definition of API and syntax
    2. Life cycle adjustment
    3. Bidirectional data flow - > unidirectional data flow
    4. jsx syntax is supported
    5. wait...

New rendering engine - vdom

//template
<template>
    <div v-if="text">
        {{text}}
    </div>
</template>

// compile render after Vue loader compilation
// The h function is used to generate the Vdom node. The first parameter is the current component, the second parameter is the attribute, and the third attribute is the child node
render(){
 return this.text
     ? h(
         'div',
         null,
         h(this.text, null,[])
     )
     : vm.createEmptyVNode()
}

Watcher relies on strength adjustment

A watcher is no longer associated with a single dom node or instruction, and a component corresponds to a watcher, which greatly reduces the memory problem caused by too many watchers in vue 1. At the same time, vdom diff can update dom with minimal cost during rendering.

Watch(compoent, vm, 'text', () =>{
    const newVnode = component.render()
    const oldVnode = component.render()
    //The diff algorithm returns the difference between the old and new nodes
    const patches = vm.diff(newVnode, oldVnode)
    // The difference is applied to the component through the patch function
    vm.patch(component, patches);
})

Advantages of vdom

  1. The framework shields the specific rendering details, abstracts the rendering layer, improves the abstraction ability of the organization, no longer relies on the browser to run, and can span segments, such as SSR, isomorphic rendering applet, weex, uni app and other frameworks.
  2. Conduct more AOT (Ahead Of Time) compilation optimization through static analysis.
  3. Additional capabilities: when a large number of components are updated, the dom is updated at the least cost.
  4. vdom is slower than direct dom operation. In most cases, the efficiency is worse than vue 1. Although a little performance is sacrificed, it makes vue obtain more features and optimization space.

AOT compilation optimization

Cache static element

  • Cache static nodes and attributes to avoid duplicate creation
// Before Compilation
<div>
    <span class="foo">
        Static
    </span>
    <span>
        {{dynmic}}
    </span>
</div>

// After compilation
const __static1=h('span',{
    class:'foo'
}, 'static')

render(){
    return h('div', [
        __static1,
        h('span', this.dynamic)
    ])
}

Component fast path

  • After compilation, directly judge whether it is a component, a native tag or a text node to avoid unnecessary branch judgment and improve performance.
  • Improve the efficiency of vdom diff

Vue2 before optimization

  • Call the h function every time to make branch judgment
// Before Compilation
<Comp></Comp>
<div></div>
<span></span>


// Vue2
render(){
    return createFragment([
        h(Comp, null, null),
        h('div', null, [
            h('span', null, null)
        ])
    ])
}

function h(type, attrs, children){
    if(isComponent(type)){
        //Create component vnode
        return createComponentVNode(type, attrs, children)
    }
    if(isDomElement(type)){
        //Create native dom vnode
        return createElementVNode(type, attrs, children)
    }
    //Create a pure string node
    return createStringVNode(type, attrs, children)
}

Vue3 optimized

  • After compilation, call different createVNode methods directly
// Vue3
render(){
    return createFragment([
        createComponentVNode(Comp, null, null),
        createElmentVNode('div',null, [
           createElmentVNode('span', null, null)
        ])
    ])
} 

SSR optimize

  • String splicing is used in SSR, and vnode is not created.
//Before Compilation
<template>
    <div>
        <p class="foo">
            {{msg}}
        </p>
        <comp/
    </div>
</template>

//After compilation
render(){
    return h('div', [
        this.ssrString(
            `<p class="foo">`
            + this.msg
            + '</p>'
        ),
        h(comp)
    ])
}

Inline handler

  • Cache the event handler on dom to avoid duplicate creation.
// Before Compilation
<div @click="count++"></div>



// After compilation
import {getBoundMethod} from 'vue'

function __fn1(){
    this.count++
}

render(){
    return h('div',{
        onClick:getBoundMethod(__fn1,this)
    })
}

Vue3 changes

Proxy Reactive State

  • Vue3 uses Proxy to generate responsive objects
  • Vue1/2 traverses and recurses all attributes in data to generate responsive objects
  • In Vue3, it is changed to generate responsive objects only when get gets this attribute, which delays the generation of responsive objects and speeds up the first screen rendering.
// Vue1/2 Practice
function walk(data){
    Object.keys(data).foreach(key => {
        defineReactive(data, key, data[key])
        //Object recursively calls walk
        walk(data[key])
    })
}



// Practices in Vue3
function reactive(target){
    let observerd = new Proxy(target, {
        get(target, key, receiver){
            let result = Reflect.get(target, key, receiver)
             //Recursion occurs only when taking sub attributes of an object
            reactive(result)
            return result
        },
        set(target, key, value, receiver) {
            let oldValue = target[key]
            if (value == oldValue)
                return
            let result = Reflect.set(target, key, value, receiver)
            return result;
        }
    })
    return observerd
}

Composition API

  • In Vue2, the code is divided into blocks according to data, methods, calculation attributes, etc., so that the code of the same business function may need to jump up and down repeatedly.
    • Although there is Mixin, there will be some problems in the relationship between businesses, including namespaces.
  • The introduction of Composition API in Vue3 enables developers to block code according to business, and introduce various attributes such as responsive objects, watch es and life cycle hooks as needed. The use method is similar to React Hooks, making developers develop more flexibly.
    • See Vue3 Chinese document - vuejs (vue3js.cn) for details

Vue2

export default {   
    data() {     
        return {       
            counter: 0     
        }   
   },   
   watch: {     
       counter(newValue, oldValue) {       
           console.log('The new counter value is: ' + this.counter)     
       }   
   } 
}

Vue3

import { ref, watch } from 'vue' 


const counter = ref(0) 
watch(counter, (newValue, oldValue) => {   
    console.log('The new counter value is: ' + counter.value) 
})

Why use Composition API

  • Mixin, hoc and composition APIs are all designed to solve the problem of code reuse. However, mixin and hoc are too flexible and not standardized, which makes it easy for developers to write scattered and difficult to maintain logic.
  • Composition API avoids the defects of mixin and hoc, provides a fixed programming mode - > function combination, decouples each module, and makes it more elegant and easier to combine and reuse.
  • Taking the component state as an example, in the traditional way, all States are mixed together in one component, which is not semantically strong. The composition API separates the states according to different logic and abstracts the state layer components.
const Foo = {
    template:'#modal',
    mixins:[Mixin1, Mixin2],
    methods:{
        click(){
            this.sendLog()
        }
    },
    components:{
        appChild:Child
    }
}
  • After reading the above code, you will find the following problems
    • Which Mixin does sendLog come from
    • Is there a logical relationship between mixin1 and mixin2
    • If mixin1 and mixin2 are injected into sendLog, which one should be used
    • If you use the method of hoc, the cost of two component instances is increased and the diff is increased twice.
    • With a few more mixin s, this component is more difficult to maintain.
  • This clearly reflects the benefits of the Composition API

Time Slicing

  • Vue3 initially implemented this feature, but later removed it
  • The reasons are summarized as follows:
    • Based on the principle of responsiveness and AOT compilation optimization, vue vdom diff has high efficiency compared with react
    • Time Slicing only plays a significant role in some extreme cases. The introduction of Time Slicing will reduce the efficiency of vdom diff, block UI rendering, and gain little.

Introduce and support treeshaking on demand

  • The decoupling of Vue modules (reactive, SSR, runtime, etc.) can be introduced as needed.

Vue vs React

Same point

  1. Based on MVVM idea: responsive data-driven trying to update
  2. Provide component solutions
  3. Cross end: vdom based rendering engine

Core differences

  1. location
    1. React is a Library that only focuses on the mapping from state to view. Solutions such as state, routing and animation come from the community.
    2. Vue is a progressive Framework. At the beginning of design, it considers the problems that developers may face. It officially provides complete solutions such as routing, state management, animation and plug-ins, which are not mandatory. For example, module mechanism and dependency injection can be well integrated with community solutions through plug-in mechanism.
    3. Library, with small scope of responsibility and low development efficiency, needs external force, but it is easy to expand. For the maintenance team, the cost of maintaining compatibility between versions is low. It's easier to focus on core changes.
    4. Framework, wide scope of responsibilities, high development efficiency, built-in solution and low degree of expansion. For the maintenance team, the cost of maintaining compatibility between versions is high.
  2. rendering engine
    1. Vue performs data interception / proxy, which is more accurate in detecting data changes. As much data is changed, it triggers as many updates as possible.
    2. React setState triggers local overall refresh without tracking data changes to achieve accurate update, so it provides developers with shouldComponentUpdate to remove some unnecessary updates.
    3. Based on this responsive design, it indirectly affects the implementation of Composition API and React Hooks of the core architecture.
  3. Template DSL
    1. Vue template syntax is closer to html, with strong static expression ability. Based on the declarative ability, it is more convenient for AOT compilation and optimization.
    2. JSX syntax can be considered as the addition of html support on the basis of JS, and its essence is imperative programming. Weak static expression ability leads to insufficient optimization information and can not do static compilation well.