Vue. Detailed explanation of slot deep replication of js

  • 2021-08-03 08:42:15
  • OfStack

Preface

In Vue, slot is a useful feature that can be used to insert something inside a component. slot means "slot". In vernacular, it is: when defining components, leave a few holes, and the user will decide what to insert.

For example, we define a component MyComponent that contains an slot:


Vue.component('MyComponent', {
 template: `
 <div>
  <slot></slot>
 </div>
 `
})

When the call <MyComponent>123</MyComponent> Is rendered to the following DOM structure:


<div>
 123
</div>

Now that there are new requirements, we want to call <MyComponent>123</MyComponent> The DOM structure is rendered as follows:


<div>
 123
 123
</div>

It seems easy to implement, that is, add another slot to MyComponent:


Vue.component('MyComponent', {
 template: `
 <div>
  <slot></slot>
  <slot></slot>
 </div>
 `
})

The rendered structure is really as you want, but the only fly in the ointment is that the console has a small Warning:


Duplicate presence of slot "default" found in the same render tree

If you are not an obsessive-compulsive disorder patient, you can call it a day and go home to sleep. Until one day your colleagues complained to you, why can't you render a custom component inserted into MyComponent?

For example, there is 1 custom component MyComponent2:


Vue.component('MyComponent2', {
 template: `
 <div>456</div>
 `
})

When the call <MyComponent><MyComponent2></MyComponent2></MyComponent> The expected rendering is the following DOM structure:


<div>
 <div>456</div>
 <div>456</div>
</div>

Why can't it work properly? It is estimated that the previous Warning caused the ghost. Through query, it is found that slot with duplicate names is not allowed in Vue 2.0:

Slots removal of duplicate names

Duplicate names in the same 1 template have been discarded. When an slot has already been rendered, it cannot be rendered again elsewhere in the same template. If you want to render the same 1 content in different positions, you can pass it by prop.

The documentation hints can be implemented with props, but it is clearly not appropriate in my use case. After searching, the most reliable method is to hand-write render function and copy the contents of slot to other locations.

Change the previous MyComponent to the render function:


Vue.component('MyComponent', {
 render (createElement) {
 return createElement('div', [
  ...this.$slots.default,
  ...this.$slots.default
 ])
 }
})

In the above definition, we inserted two this. $slots. default to test whether it works properly. However, it is not useful. The Vue document has the following instructions in Chapter 1 of render Function:

VNodes must be only 1

VNodes must be only 1 in all component trees

This means that we can't simply reference this. $slots. default in different places, and we must deeply copy slot. The functions of deep replication are as follows:


function deepClone(vnodes, createElement) {
 function cloneVNode (vnode) {
 const clonedChildren = vnode.children && vnode.children.map(vnode => cloneVNode(vnode));
 const cloned = createElement(vnode.tag, vnode.data, clonedChildren);
 cloned.text = vnode.text;
 cloned.isComment = vnode.isComment;
 cloned.componentOptions = vnode.componentOptions;
 cloned.elm = vnode.elm;
 cloned.context = vnode.context;
 cloned.ns = vnode.ns;
 cloned.isStatic = vnode.isStatic;
 cloned.key = vnode.key;
 return cloned;
 }
 const clonedVNodes = vnodes.map(vnode => cloneVNode(vnode))
 return clonedVNodes;
}

The core function above is cloneVNode() Which recursively creates VNode for deep replication. VNode attributes are many, I do not know which are the key attributes, just refer to the source of Vue 1 and copy over.

Based on the above function, we changed the definition of MyComponent:


Vue.component('MyComponent', {
 render (createElement) {
 return createElement('div', [
  ...this.$slots.default,
  ...deepClone(this.$slots.default, createElement)
 ])
 }
})

After testing, 1 cut is normal.

Summarize

In Vue 1.0, the duplicate name of slots is not a problem, and I don't know why this feature was eliminated in 2.0. I heard that React provides a standard function to copy Element. I hope Vue can also provide this function, so as not to step on the pit. The above is the whole content of this article. I hope the content of this article can bring 1 certain help to everyone's study or work. If you have any questions, you can leave a message for communication.


Related articles: