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?
props
What is?- why
tamplate
Can getprops
和setup
Data in? - Why can render
template
The structure in? - Why is it that
id='cid'
It’ll mount to the outer layerdiv
On 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.e
MyComp
- Here use
render
To achieve thetemplate
The function of the MyComp.render
The function receives onectx
In order to getprops
和setup
The data in the- This call
h
The function generates “oneMyComp
Type 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
attribute
Is an elementThe labelProperty for the view layerproperty
Is 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 rendering
render
Function 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
- Gets the component configuration object
- create
instance
- Initialize properties
- run
setup
- 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.props
throughinitProps
It’s filtered outinstance.props
VNode.props
Are some properties that are mounted on the node and are passedpatchProps
Mounted, for exampleonClick
,style
,class
等instance.props
Saves a portion of the context inside the component, although the source isVNode.props
But my personal understanding is that by the time I get here,instance.props
It exists as an execution context, not just node attributes, because those attributes that are mounted on element tags go toinstance.attrs
In 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