Vue – Next/Packages/Run-time core/ SRC /component.ts This article only implements Composition API, does not implement Options API

With the front-end business more and more complex, it is natural to appear the needs of modernization, such as modularization, componentization, automation, standardization, the previous render and patch have left a hole, this paper mainly to achieve the rendering of components

To use the

According to the official documentation, define the following global components (omit some extraneous code)

app.component('my-comp', {
    props: ['className'].setup() {
        return { text: 'this is a text' };
    },
    template: ` 
      
{{ text }}
`
});Copy the code
<my-comp class-name="aaa" id="cid" />
Copy the code

Render to the structure of the page as shown below, the page effect will not put, just a sentence

In fact, I have been curious about these questions since the very beginning of vuE3 and even VUE

  • What is the essence of a component?
  • propsWhat is?
  • whytamplateCan getpropssetupData in?
  • Why can rendertemplateThe structure in?
  • Why is it thatid='cid'It’ll mount to the outer layerdivOn the?

The above questions will not be answered for the moment, after reading this will understand, first continue to look down

Use what I wrote

Using the official version, let’s take a look at my beggar version, below

const MyComp = {
    props: ['className'].setup() {
        return { text: 'this is a text' };
    },
    render(ctx) {
        return h('div'.null, [
            h('div', { class: ctx.className, id: ctx.id }, ctx.text), ]); }}; render( h( MyComp, {className: 'aaa'.id: 'cid',},null
    ),
    document.body
);
Copy the code

The rendering effect is the same as in the above example

So from these two comparisons, you can see something roughly

  • Here an object is used to hold some properties and methods of the component, i.eMyComp
  • Here userenderTo achieve thetemplateThe function of the
  • MyComp.renderThe function receives onectxIn order to getpropssetupThe data in the
  • This callhThe function generates “oneMyCompType element “, and rendering it to the page is the component we want

While not yet a complete picture, you can get an idea of what the MyComp object is like, which is a configuration object of the component that contains some information about the component, from which the component is rendered to generate a tree of nodes and render to the page

Before we start writing

If you think you’re ready to start typing code, you’re making a mistake. Here, props is a property. In fact, a property is a set of custom attributes that can be registered on a component

What is an attribute? My temporary understanding is that an attribute is the attribute of an element tag. There are a large number of attributes, such as class, ID and style, while property is some custom attributes. To dig a little deeper, this is how I understand it

  • attributeIs an elementThe labelProperty for the view layer
  • propertyIs an elementobjectFor the logical layer

It’s not exactly a definition here, but it’s pretty much what it means, and it makes the difference pretty clear. For example, if we need to define a class name for a node to specify a style, that’s an attribute, and that node needs an isShow to control whether it should be displayed, So this must be a property, but both of these are essentially attributes

Analyze it before you write it

How do we get data from the setup setup in render?

In fact, this is very simple, just need to store all the data in an object, and this object is the corresponding component instance object, called instance, this is not what I said, vue3 does it. Then it’s very clear what we need to do

  • Use an instance object to hold data about the component
  • Use the instance object when renderingrenderFunction exposes a tree of nodes

You must first have property, attribute, and context. What should context contain if it is used to store the context?

In this example, we can use props to declare the object to be received, and the setup function to expose the object to the object. This should be included in the context context.

As mentioned above, the component configuration object acts as type when the H function creates a VNode, so we can get props and setup from this component configuration object

Now is really the time to start writing code

Finally

Finally started writing after all that crap

Initialize the instance

First of all, we should write the function of the mount component, and also fill in the previous hole. After the long paving above, our first steps to do things are clear and clear

  1. Gets the component configuration object
  2. createinstance
  3. Initialize properties
  4. runsetup
  5. Initialization context
const mountComponent = (vnode, container) = > {
    const { type: Component } = vnode;

    const instance = {
        props: null.attrs: null.setupState: null.ctx: null,}// Initialize props
    // implement later
    initProps(instance, vnode);

    const { setup } = Component;
    if (setup) {
        SetupContext is also slots and emits
        const setupContext = { attrs: instance.attrs };
        const setupResult = setup(instance.props, setupContext);
        instance.setupState = setupResult;

        // The object returned by the proxyRefs proxy setup function in the source code
        // means that the.value method is not required to get the value of the reactive data in render
        // but I was lazy :)
        // The source code looks like this
        // handleSetupResult(instance, setupResult);} instance.ctx = { ... instance.props, ... instance.setupState, }// To be continued...
}
Copy the code

Initialize the props

To initialize the props function, the properties of the object are defined in vnode.props. If the object is vnode.props, put the object declared to be received in vnode.props. Attrs (props) ¶ If you want to update the props, you need to package the props in instance.attrs (props)

const initProps = (instance, vnode) = > {
    const { type: Component, props: vnodeProps } = vnode;

    / / initialization
    const props = (instance.props = {});
    const attrs = (instance.attrs = {});

    for (const key in vnodeProps) {
        if (Component.props && Component.props.includes(key)) {
            props[key] = vnodeProps[key];
        } else{ attrs[key] = vnodeProps[key]; }}// Proxy
    instance.props = reactive(instance.props);
}
Copy the code

Component mount & update

The component mount update operation is quite reasonable, because the update must go through the patch process, so where should N1 and N2 come from

The answer to this question is very simple, n1 and n2 are actually vNodes, and the component’s VNode is called subTree in the source code, which is returned by the render method inside the component configuration object, just need to exist in the instance

Reactive is used to represent props. To implement responsive updates of components, only effect can be used to listen on them

To start writing code, we need to extend instance first, as follows

// Other code omitted
const instance = {
    props: null.attrs: null.setupState: null.ctx: null.// Extend the following properties
    subTree: null.update: null.isMounted: false,}Copy the code

As mentioned above, the component’s mount update methods all need to be listened for with Effect, so it looks like this

instance.update = effect(() = > {
    if(! instance.isMounted) {// mount...
    } else {
        // update...}});Copy the code

Here are some more details

mount

Render (subTree); render (subTree); render (subTree)

if(! instance.isMounted) {const subTree = (instance.subTree = Component.render(instance.ctx));

    patch(null, subTree, container);

    vnode.el = subTree.el;

    instance.isMounted = true;
} else {
    // update...
}
Copy the code

update

For update operation, just get the original subTree, re-render the subTree, and take the two directly to patch

if(! instance.isMounted) {// mount...
} else {
    const prevSubTree = instance.subTree;

    const nextSubTree = (instance.subTree = Component.render(instance.ctx));

    patch(prevSubTree, nextSubTree, container);

    vnode.el = subTree.el;
}
Copy the code

process & update

After completing the logic above, the rest are all small things. The holes left before are filled in. The processComponent has the same logic as the other processxxx processes we wrote earlier, which determines whether to mount or update based on the presence of N1

const processComponent = (n1, n2, container) = > {
    if (n1) {
        // The source code has a shouldUpdateComponent to determine if the component should be updated
        // Update it every time
        updateComponent(n1, n2);
    } else{ mountComponent(n2, container); }}Copy the code

Update logic can reuse the component instance of N1 and then call update on instance

Save the mountComponent to updateComponent in vNode.component.vnode.component.vnode.component.vnode.component.vnode.component.vnode.component.vnode.component.vnode.component.vnode.component.vnode.component.vnode.component.updatecomponent We also need to save instance in mountComponent

const mountComponent = (vnode, container) = > {
    // ...
    const instance = (vnode.component = {
        // ...
    });
    // ...
}
Copy the code

After that, we can do this directly in our updateComponent

const updateComponent = (n1, n2) = > {
    n2.component = n1.component;
    n2.component.update();
}
Copy the code

unmountComponent

Component uninstallation is also very taken for granted, directly to the component of the VNode, that is, subTree uninstallation can be

const unmountComponent = vnode= > {
    // The source code is not so simple
    // Because you have to deal with life cycles and so on
    // but I was lazy :)
    unmount(vnode.component.subTree);
}
Copy the code

One more thing

Why id=’cid’ is attached to the outer div

app.component('my-comp', {
    props: ['className'].setup() {
        return { text: 'this is a text' };
    },
    template: ` 
      
{{ text }}
`
});Copy the code
<my-comp class-name="aaa" id="cid" />
Copy the code

I am an attribute that is not accepted by prop. I am an attribute that is not accepted by prop

If you bind an attribute to the component and it is not accepted by props, the attribute will be applied to the root node of the component by default. In the example above, I bound an ID attribute to the my-comp component, but I did not declare the object to be accepted by props. So I’ll mount the ID attribute to the root of my my-comp component, assuming that my my-comp component has only one root node

To mount an internal div, I just need to declare the accept ID in props

app.component('my-comp', {
    props: ['className'.'id'].// Declare the id received here
    setup() {
        return { text: 'this is a text' };
    },
    template: ` 
      
{{ text }}
`
});Copy the code
<my-comp class-name="aaa" id="cid" />
Copy the code

The results are as follows. You don’t cheat me

I am an attribute that is not accepted by prop.

InheritAttrs implementation

The implementation here is a bit convoluted, but logically it is simple to distinguish between vnode. props and instance.props

  • First of all, it’s a father-and-son relationship,VNode.propsthroughinitPropsIt’s filtered outinstance.props
  • VNode.propsAre some properties that are mounted on the node and are passedpatchPropsMounted, for exampleonClick,style,class
  • instance.propsSaves a portion of the context inside the component, although the source isVNode.propsBut my personal understanding is that by the time I get here,instance.propsIt exists as an execution context, not just node attributes, because those attributes that are mounted on element tags go toinstance.attrsIn the

Just add instance.attrs to vnode. props and let it mount

const inheritAttrs = (instance, subTree) = > {
    const { attrs } = instance;
    const { props } = subTree;

    if(attrs) { subTree.props = { ... props, ... attrs, }; }}Copy the code

The time to do attributes inheritance, of course, is when the component is mounted and updated, as follows

if(! instance.isMounted) {const subTree = (instance.subTree = Component.render(instance.ctx));

    / / inheritance
    inheritAttrs(instance, subTree);

    patch(null, subTree, container);

    vnode.el = subTree.el;

    instance.isMounted = true;

} else {
    const prevSubTree = instance.subTree;

    const nextSubTree = (instance.subTree = Component.render(instance.ctx));

    / / inheritance
    inheritAttrs(instance, nextSubTree);

    patch(prevSubTree, nextSubTree, container);

    vnode.el = subTree.el;
}
Copy the code

Answer the previous question in detail

Q&A, by the way. Same rule, it’s all personal. If you’re wrong, shoot me

Q: What is the essence of a component? A: A component is actually A collection of elements, in all sorts of ways. From a page perspective, a component is a subTree of nodes, while from a logical perspective, a component is actually an instance object, instance. But having said that, I can render a bunch of tags and say is this a component? Yes, but it doesn’t make any sense. The essential difference between a component and a bunch of tags, I personally think, is the flow of data. Components have their own context, where the data can be used at any node in the tree of nodes. I can create a bunch of VNodes and say this is a component. Can I? Yes, but it doesn’t make sense because there’s no data flowing around, meaning it’s not in the same independent context. Based on this, the following is my conclusion that a component is a collection of complex elements in the same context with data flowing

Q: What is props? A: As documented, instance.props is A part of A component’s internal context that can be used to receive custom properties for use within the component. The biggest difference between props and attrs should not be “accepted or not”, but “custom or not”. The custom is props and the uncustom is attrs. Of course, it is also possible to receive a class, id and so on as the example in the article to use inside the component, but I just use the example diagram is convenient, normal development nobody writes this, I think there is no ten years cerebral blood clots really cannot write this code, there must be a better way to handle it

Q: Why can tamplate get props and setup data? A: Vue template syntax is actually written by a compiler to handle it, and will eventually compile into a lot of render and H, So the template can get props and setup data because the instance object is a bridge, and the template can get data directly from instance. CTX

Q: Why is it possible to render the structure in template? A: The “component configuration object” mentioned in the text (I really don’t know what to call it) acts as type in the h function, so you can deconstruct type as above and use it to get the render method. This is used to obtain the subTree of the node in template

Q: Why is the example id=’cid’ mounted on the outer div? A: I am an attribute that is not accepted by prop. The answer is as follows. In this example, id is not accepted by props, so by default it is attrs and is mounted directly on the root node of the component. As for why it was mounted on the root node rather than something else, would you go back and take a closer look at my Render implementation and patch implementation

Q: Can you explain from a source point of view why you can’t use this in setup functions? A: Setup doesn’t point to the current instance object, it doesn’t point to the component configuration object, it actually points to the window. You can look at the setup function calls for more details, but using this in setup is actually quite easy. All you need to do is make this in Setup refer to instance

const setupResult = setup.call(instance, instance.props, setupContext);
Copy the code

But to be honest, that doesn’t make any sense at all, or at the moment there’s no way to use this in setup. All the contexts that are used in setup are in two parameters: props and CTX. Other lifecycle hooks have setup versions as well. Back to the problem itself, the setup function is called after props is initialized, and the instance object already exists, but the other options apis in the source code have not yet been parsed. That is, the reference to this is different from that of other Options APIS, so we don’t do anything about it. Instead, we store the context for you to use in two parameters of setup, so we don’t need to use this anymore

Q: I can’t compositionAPI and still can’t use beggar-vue you wrote back?? A: My beggar-Vue is not made to produce. The above

conclusion

I feel that I need to understand the nature of components. In the implementation, I can see that almost all the code revolves around instance, and components are really just objects from a logical point of view. In addition, you need to think clearly about the functions and relationships of each attribute. This is really important. For example, what is props, what is attrs, and what is their relationship

The other steps are described in detail in the mountComponent section

const mountComponent = (vnode, container) = > {
    const { type: Component } = vnode;

    const instance = (vnode.component = {
        props: null.attrs: null.setupState: null.ctx: null.subTree: null.update: null.isMounted: false}); initProps(instance, vnode);const { setup } = Component;
    if (setup) {
        const setupContext = { attrs: instance.attrs };
        constsetupResult = setup(instance.props, setupContext); instance.setupState = setupResult; } instance.ctx = { ... instance.props, ... instance.setupState, } instance.update = effect(() = > {
        if(! instance.isMounted) {const subTree = (instance.subTree = Component.render(instance.ctx));

            inheritAttrs(instance, subTree);

            patch(null, subTree, container);

            vnode.el = subTree.el;

            instance.isMounted = true;

        } else {
            const prevSubTree = instance.subTree;
            constnextSubTree = (instance.subTree = Component.render(instance.ctx)); inheritAttrs(instance, nextSubTree); patch(prevSubTree, nextSubTree, container); vnode.el = subTree.el; }}); }const initProps = (instance, vnode) = > {
    const { type: Component, props: vnodeProps } = vnode;

    const props = (instance.props = {});
    const attrs = (instance.attrs = {});

    for (const key in vnodeProps) {
        if (Component.props && Component.props.includes(key)) {
            props[key] = vnodeProps[key];
        } else {
            attrs[key] = vnodeProps[key];
        }
    }

    instance.props = reactive(instance.props);
}

const inheritAttrs = (instance, subTree) = > {
    const { attrs } = instance;
    const { props } = subTree;

    if(attrs) { subTree.props = { ... props, ... attrs, }; }}Copy the code