Before reading this article, I hope you still have a certain foundation, can understand some basic principles of Vue2, understand the basic application of Proxy code and the characteristics of WeakMap, Map, Set data structure. If you don’t know about these students suggest first to fill in the basic knowledge.

If you are not already familiar with vue3, it is recommended to learn vue3 first.

Vue3 Basic features

Vue3 + Ts of actual combat

Declaration: in this article, the method name is used in the source code method name, a lot of code structure according to the structure of the source code to write, the purpose is to hope to be able to see the source code of students do some guidance.

If there’s something in the code that you can’t figure out at once, try to accept it first, and then go back and understand it.

The most important: for the learning method of this article, I hope you must be more to write code, do some more thinking, after reading, I believe you will surely gain.

If there is anything wrong, you are welcome to leave a message to correct.

responsive

As we all know, VUE2 implements data interception and listening through defineProperty. Compared with defineProperty, Proxy adopted in VUE3 is more convenient to listen on arrays without nesting or traversing each attribute, which is more convenient for the whole object.

Vue3 creates a reactive object by working with reactive objects. When the object is modified, the view is updated and the corresponding side effect function is executed (we’ll analyze this later when we implement the watchEffect). There are two main processes:

  • Listen for data and collect dependencies
  • Update views (later)

Inside reactive, data interception and listening are realized through Proxy.

// v3.js

// Reactive
function reactive(target){
    return createReactiveObject(
        target,
        mutableHandlers
    );
}

/** * The function that actually creates the responsive object *@param {*} The target object *@param {*} Handler handler
function createReactiveObject(target,handler){
    const proxy = new Proxy(target,handler);
    return proxy;
}
Copy the code

Proxy’s monitoring of data is mainly handled in handler, which is an object that defines one or more traps. And then we implement the mutableHandlers object

// v3.js

const get = createGetter();
function createGetter(){
    return function get(target,key,receiver){
        const res = Reflect.get(target, key, receiver);
        console.log('Get executed',key);
        returnres; }}const set = createSetter();
function createSetter(){
    return function set(target,key,value,receiver){
        const res = Reflect.set(target,key,value,receiver);
        console.log('Set executed',key);
        returnres; }}// The proxy handler is an object that defines one or more traps
// Only handle simple get and set
const mutableHandlers = {
    get,
    set
}

// Reactive
function reactive(target){
  return createReactiveObject(
    target,
    mutableHandlers
  );
}

/** * The function that actually creates the responsive object *@param {*} The target object *@param {*} Handler handler
function createReactiveObject(target,handler){
  const proxy = new Proxy(target,handler);
  return proxy;
}
Copy the code

So we’ve implemented a simple ReactVie that listens for data. You can test this with the following code:

// v3.js

const testObj = reactive({ count:10 }})
testObj.count
testObj.count = 20

// Execute the current code through node v3.js
// Get executes count
// Set executes count
Copy the code

This only implements listening for simple objects. If the objects are more complex, there will be problems. For example:

// v3.js

const testObj = reactive({ count:10.info: { name:'lucas' }})
testObj.info.name
testObj.info.name = 'viky'

// The execution result is as follows
// Get executes count
// Set executes count
Copy the code

The listener is not listening to the name attribute. Optimize the get method. If the value is an object, we should listen recursively:

// v3.js

const isObject = (target) = >{ returntarget ! = =null && typeof target === 'object'}
function createGetter(){
    return function get(target,key,receiver){
        const res = Reflect.get(target, key, receiver);
        console.log('Get executed',key);
        if(isObject(res)){
            return reactive(res);
        }
        returnres; }}Copy the code

So far, our reactive has been able to listen to data.

To subscribe to

Next, let’s start collecting dependencies, again in GET, through the track function, and before we do that, let’s take a look at the data structures used to store dependencies in VUe3.

Keep this data structure in mind (time one minute…)

We start the code to implement the track function:

// v3.js

let targetMap = new WeakMap(a);// Store the object
let activeEffect; // The current effect to be added

/** * Collect dependencies *@param {*} Target Target object *@param {*} Key the key value * /
function track(target,key){
    if(activeEffect === undefined) {return;
    }
    let desMap = targetMap.get(target);
    if(! desMap){ targetMap.set(target,(desMap=new Map()))}let deps = desMap.get(key);
    if(! deps){ desMap.set(key,(deps =new Set()))}if(! deps.has(activeEffect)){ deps.add(activeEffect)// Used to clear dependencies (analyzed later)
        activeEffect.deps.push(deps)
    }
}
Copy the code

We make the call in the get trap:

// v3.js

function createGetter(){
    return function get(target,key,receiver){
        const res = Reflect.get(target, key, receiver);
        // Dependency collection
        track(target, key)
        if(isObject(res)){
            return reactive(res);
        }
        returnres; }}Copy the code

At this point you might wonder, what is an activeEffect and where is it assigned?

ActiveEffect is a encapsulated side effect function that executes either a function we pass in or a function that updates the view. As for the specific when the value was assigned, we left a question one here, and then one by one to answer, first go on to see.

release

Now that we’re pretty much done with the collection, let’s think about how we can perform the collection side effect function when the listening object is modified. In V3, this is done by trigger.

//v3.js

/** * triggers the corresponding side effect function *@param {*} Target Target object *@param {*} Key the key value * /
function trigger(target,key){
    // Get the Map of our stored target
    const depsMap = targetMap.get(target);
    if(! depsMap){return;
    }
    const effects = new Set(a);// Side effect function queue
    // Define the add method to be added to the queue
    const add = (effectsToAdd) = >{
        effectsToAdd.forEach(effect= > {
            effects.add(effect)
        });
    }
    // Add the side effects of the corresponding key to the queue
    add(depsMap.get(key));
    // Define the execution function
    const run = (effect) = >{
        effect();
    }
    / / execution
    effects.forEach(run);
}
Copy the code

The author simplified the trigger function on the premise of keeping the trace of the source code, and removed some other cases. Let’s continue to optimize the set trap:

// v3.js

function createSetter(){
    return function set(target,key,value,receiver){
        const res = Reflect.set(target,key,value,receiver);
        // Execute the corresponding side effect function
        trigger(target,key)
        returnres; }}Copy the code

So, basically, we’re going to end by creating a reactive object by implementing it, subscribes to it, and publishes it in the get and set traps. However, we left the activeEffect question open, and in order to better answer this question and understand the above code, we implemented an official watchEffect function to make the above code run to verify that our publish subscription was in effect. For those of you who don’t know watchEffect well, I suggest you take a look at its basic usage first, and then think about how to implement it in the context of our code above.

The Master said: Why did Firth get it

The realization of the watchEffect

WatchEffect is a function that automatically applies and reapplies side effects based on reactive objects, receiving a side effect function and executing it immediately. Returns a function that can stop listening. So let’s start implementing this function step by step,

// v3.js

/** * Monitors the reactive object and executes the corresponding side effect function *@param {*} Effect Side effect function */
function watchEffect(effect){
    return doWatch(effect);
}

function doWatch(fn){
    // Package processing
    const getter = () = > {
        // Never trust a programmer to write code that doesn't report errors
        return callWithErrorHandling(fn);
    }
    / / actuator
    const runner = effect(getter);
    // Do it once immediately to collect dependencies
    runner();
    // Return a function to clear side effects
    return () = >{
        stop(runner)
    }
}

// Execute the function and handle the error
function callWithErrorHandling(fn){
    var res;
    try {
        res = fn();
    } catch (error) {
        // Error handling
        throw new Error(error);
    }
    return res;
}

Copy the code

We first wrap the side effect function we passed in with the callWithErrorHandling method to handle the error (which is simple here), get our getter, use the getter to create an effect function, and then the function executes immediately. Finally, return a function that can stop listening. That’s the general structure.

Now let’s see what effect looks like.

// v3.js

/** * Create effect *@param {*} Fn functions * /
function effect(fn){
    const effect = createReactiveEffect(fn);
    return effect;
}

// effect Indicates the id
let uid = 0;

function createReactiveEffect(fn){
    const effect = function reactiveEffect(){
        try {
            activeEffect = effect;
            return fn();
        } finally {
            // Release in time to avoid pollution
            activeEffect = undefined; }}/ / logo
    effect.id = uid++;
    // The array that was collected in the set for cleanup
    effect.deps = []
    return effect;
}
Copy the code

Effect is a function that will be assigned to activeEffect. When this function is executed, it will execute the side effect function that we first passed in to watchEffect. If any of our reactive objects are read from the side effect function, track will be triggered for dependency collection. It is the effect function (activeEffect) that is collected, which is why runner executes it immediately, to collect dependencies. When the reactive object changes, the trigger executes the effect(activeEffect) function. I also added a unique id to the effect function and a deps to hold the sequence that was collected (I got the name myself).

This may be a little confusing, but if you don’t understand it a little bit, I suggest you read it twice, “Read the book a hundred times, the meaning will come out,” and the code is the same. The relationship between these data will be analyzed in general later.

Let’s move on to the stop method to stop listening.

// v3.js

function stop(effect){
    cleanup(effect)
}

// Remove side effects
function cleanup(effect){
    const { deps } = effect;
    if(deps.length){
        for(var i =0; i<deps.length; i++){
            deps[i].delete(effect)
        }
    }
}
Copy the code

The effect function has a collection of sets that collect the effect. Delete the collection of this effect from these collections.

Let me use a graph to sort out one of the relationships between these data structures.

Combine this with the diagram and look at the code twice, and the relationship will come to mind.

So that’s the watchEffect implementation, so let’s see it in action and let’s see, let’s create a new HTML page.

// html <! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, Word-break: break-word! Important; "> < span style =" box-sizing: border-box; color: RGB (50, 50, 50) < img SRC ="./v3.js"></ img >< img SRC ="./v3.js"></ img >< img SRC =" = () => { obj.count++; } const clearFn = watchEffect(()=>{console.log('watchEffect execute ',obj.count); }) </script> </body> </html>Copy the code

The effect is as follows:

Let’s take a look at the initialization process.

Initialization process

Let’s first look at how Vue3 creates an application:

createApp({
    ...
}).mount("#app");
Copy the code

Just think out loud, how does this invocation pattern work?

It returns an object through the createApp method, and then it has a mount method inside it, right?

Let’s take a look at the general structure:

// v3.js

// Create the application entry
const createApp = (. args) = > {
    constapp = ensureRenderer().createApp(... args);return app;
}

const ensureRenderer = () = > {
    / / source return renderder | | (the renderer = createRenderder ())
    // Save the renderer
    // Initialize the renderer to be null, so create it directly, omitting intermediate functions
    return baseCreateRenderer();
}

// Functions that actually create the renderer
const baseCreateRenderer = () = > {
    // There are many rendering functions defined
    // ...
    const render = () = >{};return {
        render,
        createApp:createAppAPI(render)
    }
}

function createAppAPI(render){
    /** createApp instance method * rootComponent:createApp passed in initialize Oject */
    return function createApp(rootComponent){
        const app = {
            // Here are the familiar methods: _uid, _props, _context, and so on
            _component:rootComponent,
            // use(){ 
            // return app
            // },
            // component(){
            // return app
            // },
            // directive(){
            // return app
            // },

            // Load the node
            mount(rootContainer){
                // return app for chained calls
                returnapp; }}returnapp; }}Copy the code

The createApp method returns an app instance, and then renders the data into a page by calling the mount method on the instance (this is a bit of a stretch). This is the skeleton of it, which we’ll fill in little by little.

We start with the mount, where we find the DOM to load by passing in the node ID. In the source code, the mount method is extended in the createApp method, in which the DOM node and template information are obtained. Then render it through the render function.

// v3.js

const createApp = (. args) = > {
    constapp = ensureRenderer().createApp(... args);const { mount } = app ; 
    app.mount = (containerOrSelector) = > {
        // Get the real DOM node
        const container = document.querySelector(containerOrSelector);
        if(! container){return;
        }

        // Retrieve and save the template
        const component = app._component
        component.template = container.innerHTML;

        const proxy = mount(container);
        return proxy;
    }
    return app;
}

function createAppAPI(render){
    /** createApp instance method * rootComponent:createApp passed in initialize Oject */
    return function createApp(rootComponent){
        const app = {
            // Here are the familiar methods: _uid, _props, _context, and so on
            _component:rootComponent,
            mount(rootContainer){
                // Create a vNode for the component
                const vnode = createVNode(rootComponent);
                render(vnode, rootContainer);
                // return app for chained calls
                returnapp; }}returnapp; }}// Create the virtual DOM
function createVNode(type){
    return {
        type,
        props:null.children:null}}Copy the code

Note: When we create the app instance, we assign the rootComponent parameter directly to app._component. Since rootComponent is an Object, we copy the memory address, and then set the template field in the extended mount method. The template field is added to the rootComponent. (Basics, just a reminder)

The virtual DOM (Vnode) is an Object that literally describes the information about a DOM element. Here the author writes three fields that we’re going to use :type, props, and children.

The vnode in the source code looks like this:

{
    __v_isVNode: true,
    [ReactiveFlags.SKIP]: true,
    type,
    props,
    key: props && normalizeKey(props),
    ref: props && normalizeRef(props),
    scopeId: currentScopeId,
    children: null.component: null.suspense: null.ssContent: null.ssFallback: null.dirs: null.transition: null.el: null.anchor: null.target: null.targetAnchor: null.staticCount: 0,
    shapeFlag,
    patchFlag,
    dynamicProps,
    dynamicChildren: null.appContext: null
}
Copy the code

Let’s update the baseCreateRenderer method to the mountComponent method in render:

// v3.js

const baseCreateRenderer = () = > {
    // ...
    // There are many rendering functions defined
    
    const mountComponent = (vnode,container) = >{
        // Initialize a real example of a component
        // const instance = createComponentInstance(vnode);
        
        const instance = { type: vnode.type }; 	

        // Initialize setup
        setupComponent(instance)

        // Initialize the render side effect function
        setupRenderEffect(instance,container)
    }

    // ...
    const render = (vnode,container) = > {
        mountComponent(vnode,container);
    };
   
    return {
        render,
        createApp:createAppAPI(render)
    }
}
Copy the code

The mountComponent first creates an instance of the component that executes the two main functions setupComponent and setupRenderEffect.

The setupComponent method does a few things.

  • Initialize executionsetup, the execution result isresult.
  • willresultthroughreactiveDo responsive listening and bind to the component instanceinstanceOn,
  • Convert the template to an AST.
  • willastConverted torenderFunction and bind to the component instanceinstanceOn.
  • Options compatible with vue2.0. (Later)

Since compiling modules is relatively complex, we implement the first two points first and analyze the modules separately.

The result of setup is listened on responsively in order to trigger dependency collection when read.

The setupRenderEffect method sets the side effects (mentioned above) for page initialization rendering and updates and executes the side effects once.

Start the code, remind everyone to look at the location of the code:

// v3.js

const baseCreateRenderer = () = > {
    // ...
    // There are many rendering functions defined
    
    // Render side effects
    const setupRenderEffect = (instance,container) = > {
        instance.update = effect(function componentEffect(){
            // Render directly
            if(! instance.isMounted){// Get the rendered tree vnode
                const subTree = (instance.subTree = renderComponentRoot(instance))
                patch(
                    null,
                    subTree,
                    container
                )
                instance.isMounted = true;
            }else{
                // To obtain the old and new VNodes for comparison

                // Obtain the previous vNode
                const prevTree = instance.subTree;
                // The next tree to be updated
                const nextTree = renderComponentRoot(instance);
                patch(
                    prevTree,
                    nextTree,
                    container
                )
            }
        })

        // Manually initialize the execution once
        instance.update();
    }
    
    // ...
    
    return {
        render,
        createApp:createAppAPI(render)
    }
}

// Initialize setup
function setupComponent(instance){
    const { setup } = instance.type;
    if(! setup){return;
    }
    // Never trust a programmer to write code that doesn't report errors
    const setupResult = callWithErrorHandling(setup);
    instance.setupState = reactive(setupResult);
}

// Construct the render tree
function renderComponentRoot(instance){
    const { setupState }  = instance;
    // Since there is no template compilation, let's create renderNode directly
    return {
        type:'div'.props: {id:'demo'
        },
        children:[
            {
                type:'h1'.props: {onclick:() = >{
                        console.log('Click event triggers ~'); }},children: setupState.count
            }
        ]
    }
}

Copy the code

ComponentEffect in the source code is through the effect function of the lazy field to control the execution of a. In the first execution of the function, we will generate our rendering node tree vnode, and save, marked isMounted as true, the next trigger will create a new rendering node tree vnode, and then the two will be passed to patch for comparison and then rendered as the real DOM.

Note: The vnode we created is stored in instance when the program is first run, and will be used for later updates to retrieve the old node.

We simply return a VNode in the renderComponentRoot function.

Note: The vnode tree is not the same as the vnode that was originally created on mount. The vnode is the actual tree structure to render.

Another core function patch appears, which I believe you should be familiar with.

// v3.js

const baseCreateRenderer = () = > {
    // ...
    // There are many rendering functions defined
    
    // vnode => dom
    const mountElement = () = > {} 
    
    / / update the dom
    const patchElement = () = > {}
    
    const patch = (n1,n2, container) = > {
        // Initialize the render
        if(! n1){ mountElement(n2,container) }else{
            patchElement(n1,n2,container)
        }
    }
    
   
    return {
        render,
        createApp:createAppAPI(render)
    }
}
Copy the code

Compared with the source code, the patch function is simplified. When N1 is empty, the patch function is directly rendered; otherwise, the patch function is rendered after comparison.

Rendering Vnode

MountElement is the real way to convert vnode into a real DOM. We encapsulate the DOM operations and comparison procedures involved in it into a renderOptions object and pass it to the baseCreateRenderer. We implement mountElement first:

// v3.js

// Render
let rendererOptions = {
    // Insert the node
    insert: (child, parent) = > {
        parent.insertBefore(child,  null)},// Create a node
    createElement: (tag) = > document.createElement(tag),
    // Create text
    createText: text= > document.createTextNode(text),
    // Compare attributes
    patchProp: (container, key, value) = > {
        if (value) { 
            // When the value is a method, do the package processing
            if(typeof value === 'function'){
                container.setAttribute(key,` (${value}`) ())}else{
                container.setAttribute(key,value)
            }
        } else {
            container.removeAttribute(key)
        }
    },
    // Set the text of the node
    setElementText: (el, text) = > {
        el.textContent = text
    },
    // Get the parent node
    parentNode: node= > node.parentNode,
}

const ensureRenderer = () = > {
    return baseCreateRenderer(rendererOptions);
}

const baseCreateRenderer = (options) = > {
    const {
        insert: hostInsert,
        patchProp: hostPatchProp,
        createElement: hostCreateElement,
        setElementText:hostSetElementText,
        parentNode:hostGetParentNode
    } = options
    
    // ...
        
    const patch = (n1,n2, container) = > {
        // Initialize the render
        if(! n1){ mountElement(n2,container) }else{
            patchElement(n1,n2,container)
        }
    }

    // vnode => dom
    const mountElement = (n2,container) = > {
        const { type , props, children } = n2;
        // type Creates the element
        const el = n2.el = hostCreateElement(type)
        / / props attribute
        if(props){
            Object.keys(props).forEach((key) = >{
                // Set it directly
                hostPatchProp(el,key,props[key])
            })
        }
        / / the children
        if(typeof children === 'string' || typeof children === number){
            hostSetElementText(el,children)
        }else if(Array.isArray(children)){
            children.forEach((vnode) = >mountElement(vnode,el))
        }
        
        // Add a node
        hostInsert(el,container);
    }

    // Compare update
    const patchElement = (n1,n2) = >{}// ...
    const render = (vnode,container) = > {
        mountComponent(vnode,container);
    };

    return {
        render,
        createApp:createAppAPI(render)
    }
}

Copy the code

The mountElement method does a lot of things:

Create an element based on the type. We will save the element to vNode for the next update. Then set the element’s props and determine the type of children. Otherwise, recursively call mountElement to render the child node.

Note: When we create the node, we mount the node to the EL field of the vnode for later update operations.

Update the view

The patchElement function is used to compare the old vNode with the new vnode, and update where it needs to be updated. Note that our old Vnode has a real DOM stored in it, so we don’t need to recreate it according to the new Vnode. Instead, we need to make a difference update on the old EL.

Implement the patchElement function:

// v3.js

// Compare update
const patchElement = (n1, n2) = > {
    const { type: oldType, props: oldProps, children: oldChildren,el } = n1;
    const { type: nextType, props: nextProps, children: nextChildren } = n2;
    // type
    if(oldType ! == nextType) {// Get the parent node
        const container = hostGetParentNode(n1.el);
        // Re-render directly
        mountElement(n2, container);
        return;
    } else { 
        if (nextProps) { 
            Object.keys(nextProps).forEach(key= > { 
                // Set the element of the new element when the old element's attribute is not equal to the new element's attribute
                if(oldProps[key] ! == nextProps[key]) { hostPatchProp(el,key,nextProps[key]) } }) }if (oldProps) { 
            Object.keys(oldProps).forEach(key= > {
                // If the new element does not contain this attribute, remove it
                if(! (keyin nextProps)) { 
                    hostPatchProp(el,key,null)}}); }const isStringOrNumber = (str) = > {
            return str === ' ' || typeof str === 'string' || typeof str === 'number';
        }

        // children
        if (isStringOrNumber(nextChildren)) {
            if (isStringOrNumber(oldChildren)) {
                // Both are strings and have different values
                if(nextChildren ! == oldChildren) { hostSetElementText(el, nextChildren) } }else if (Array.isArray(oldChildren)) {
                // The new value is a string and the old element is an array
                hostSetElementText(el, nextChildren)
            }
        } else { 
            // When the children of both old and new elements are arrayspatchChildren(n1, n2); }}}/ / the children
const patchChildren = (n1, n2) = > {
    const { children: oldChildren, el } = n1;
    const { children: nextChildren } = n2;
    for (var i = 0; i < nextChildren.length; i++) { 
        // If the old element has a value old to compare
        if (oldChildren[i]) {
            patchElement(oldChildren[i], nextChildren[i]);
        } else { 
            // Not created directly
            mountElement(nextChildren[i],el)
        }
    }
}
	
Copy the code

Determine the node type, if not, directly as the new vnode re-render.

To determine the attributes, iterate over the attributes in the new Vnode. If there are different attributes, replace them with the new ones directly, recycle the old attributes, and retain the original attributes of the old node.

To identify the child node, consider the type of the node. It could be an array, it could be a string:

  • Both the old and new child nodes are of string type

If the type is different, just replace it.

  • The old child node is an array and the new child node is a string

Direct replacement

  • Both old and new child nodes are arrays

The new children are called N2 and the old children are called N1. Since it is updating, n2 must be the main one, so loop N2. If the corresponding N1 also has data, node comparison will be carried out through patchElement recursion. If N1 does not exist, it is created by mountElement.

The above is the general idea of patch. In fact, the patch process here is not rigorous, and many cases are not taken into account, such as the order of elements. Just get the general idea here.

The exciting time is finally here. Let’s see the code in action. Create a new HTML file:

<! DOCTYPEhtml>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <title>Vue3 source code parsing</title>
</head>
<body>
    <div id="app"></div>
    <script src="./v3.js"></script>
    <script>
        createApp({
            setup(){
                const obj = reactive({
                    count:0
                })
                return {
                    obj
                }
            }
        }).mount("#app")
    </script>
</body>
</html>
Copy the code

RenderComponentRoot returns the render node:

{
    type:'div'.props: {id:'demo'
    },
    children:[
        {
            type:'h1'.props: {onclick:() = >{
                    console.log('Click event triggers ~'); }},children: setupState.obj.count
        }
    ]
}
Copy the code

Based on vNode, we infer that the final rendered page structure should look like this:

<div id="app">
    <div id="demo">
        <h1 onclick="(()=>{console.log(' click event triggered ~'); }) ()">0</h1>
    </div>
</div>
Copy the code

The execution result is as follows:

Let’s look at responsive updates again:

// html


<script>
    createApp({
        setup(){
            const obj = reactive({
                count:0
            })
            const changeCount = () = >{
                obj.count++;
            }
            return {
                obj,
                changeCount
            }
        }
    }).mount("#app")
</script>

Copy the code

Since we simply set the element attribute when we set the element attribute, the object in the click event is still the window object called. We need to change the setupComponent code a little bit. Bind the result of instance.setupState to window. By the way, modify the vnode click event:

// v3.js

// Initialize setup
function setupComponent(instance){...Object.keys(instance.setupState).forEach(key= >{
        // Mount to window
        window[key] = instance.setupState[key]
    })
}

// Construct the render tree
function renderComponentRoot(instance){
    const { setupState }  = instance;
    return{...type:'h1'.props: {onclick: setupState.changeCount
                },
                children: setupState.obj.count ... }}Copy the code

The effect is as follows:

At the end

We implemented the responsive principle, learned the Vue3 initialization process, and implemented the responsive update page. The watchEffect is implemented as a side effect.

Learning without thought is labor lost; thought without learning is perilous.

Migrant worker, have you harvested today?