Preface
vue.js
is the component, and the soul of the component is the slot. With the help of slots, we can achieve component reuse to the greatest extent. This article is mainly a detailed summary of the implementation mechanism of the slot, which is useful in some scenarios. vue.js
it is, knowing why, and mastering the implementation principle of 060d063355d5b1 can not only improve one's own problem-solving ability, but also learn the programming ideas and development paradigms of the great gods.
Sample code
<!-- 子组件comA -->
<template>
<div class='demo'>
<slot><slot>
<slot name='test'></slot>
<slot name='scopedSlots' test='demo'></slot>
</div>
</template>
<!-- 父组件 -->
<comA>
<span>这是默认插槽</span>
<template slot='test'>这是具名插槽</template>
<template slot='scopedSlots' slot-scope='scope'>这是作用域插槽(老版){{scope.test}}</template>
<template v-slot:scopedSlots='scopeProps' slot-scope='scope'>这是作用域插槽(新版){{scopeProps.test}}</template>
</comA>
Look beyond the surface
The role of the slot is achieve content distribution . To achieve content distribution, two conditions are required:
- Placeholder
- Distribute content
Components internally defined slot
label, we can understand placeholder , parent component content in the slot is to content distribution . The essence of slot processing is to place the specified content in the specified location. Not much nonsense, from this article, you will be able to understand:
- The realization principle of the slot
- How to use slots in
render
Realization principle
vue
instantiation sequence of the 060d063355d7ce component is: parent component state initialization ( data
, computed
, watch
...) --> Template compilation --> Generate render
method --> Instantiate rendering watcher
--> Call render
--> Call 060d063355d7d8 method to generate VNode
> patch VNode
, converted to true DOM
-> instantiating subunit assembly -> ...... repeats the same process -> sub-assembly to generate real DOM
mount real parent component generated DOM
on the mount In the page --> remove the old node
From the above process, it can be inferred:
- The parent component template is resolved before the child component, so the parent component will first get the slot template content
- The subcomponent template is parsed later, so when the subcomponent calls the
render
method to generateVNode
, you can use some means to get theVNode
node of the slot - The scope slot can get the variables in the sub-component, so the
VNode
scope
sub-component needs to be passed in in real time
The processing stage of the entire slot is roughly divided into three steps:
- Compile
- Generate rendering template
- Generate VNode
Take the following code as an example to briefly summarize the process of slot operation.
<div id='app'>
<test>
<template slot="hello">
123
</template>
</test>
</div>
<script>
new Vue({
el: '#app',
components: {
test: {
template: '<h1>' +
'<slot name="hello"></slot>' +
'</h1>'
}
}
})
</script>
Parent component compilation stage
Compilation is to parse the template file into a AST
syntax tree, and template
into the following data structure:
{
tag: 'test',
scopedSlots: { // 作用域插槽
// slotName: ASTNode,
// ...
}
children: [
{
tag: 'template',
// ...
parent: parentASTNode,
children: [ childASTNode ], // 插槽内容子节点,即文本节点123
slotScope: undefined, // 作用域插槽绑定值
slotTarget: "\"hello\"", // 具名插槽名称
slotTargetDynamic: false // 是否是动态绑定插槽
// ...
}
]
}
Parent component generation rendering method
According to the AST
syntax tree, the rendering method string is parsed and generated. The final result generated by the parent component is shown below. This structure render
. The essence is to generate VNode
, but _c
or h
is the abbreviation this.$createElement
with(this){
return _c('div',{attrs:{"id":"app"}},
[_c('test',
[
_c('template',{slot:"hello"},[_v("\n 123\n ")])],2)
],
1)
}
Parent component generates VNode
Call the render
method to generate VNode
, VNode
specific format is as follows:
{
tag: 'div',
parent: undefined,
data: { // 存储VNode配置项
attrs: { id: '#app' }
},
context: componentContext, // 组件作用域
elm: undefined, // 真实DOM元素
children: [
{
tag: 'vue-component-1-test',
children: undefined, // 组件为页面最小组成单元,插槽内容放放到子组件中解析
parent: undefined,
componentOptions: { // 组件配置项
Ctor: VueComponentCtor, // 组件构造方法
data: {
hook: {
init: fn, // 实例化组件调用方法
insert: fn,
prepatch: fn,
destroy: fn
},
scopedSlots: { // 作用域插槽配置项,用于生成作用域插槽VNode
slotName: slotFn
}
},
children: [ // 组件插槽节点
tag: 'template',
propsData: undefined, // props参数
listeners: undefined,
data: {
slot: 'hello'
},
children: [ VNode ],
parent: undefined,
context: componentContext // 父组件作用域
// ...
]
}
}
],
// ...
}
In vue
, the component is the basic unit of the page structure. From the above VNode
, we can also see that the VNode
page hierarchy ends in the test
component, and the test
component children
processed during the sub-component initialization process. The sub-component construction method assembly and attributes are merged in vue-dev\core\vdom\create-component.js createComponent
method, the component instantiation call entry is vue-dev\src\core\vdom\patch. js createComponent
method.
Sub-component state initialization
When instantiating the sub-component, in the initRender
-> resolveSlots
method, the sub-component slot node will be mounted in the component scope vm
, and the mount form is vm.$slots = {slotName: [VNode]}
.
Sub-component compilation phase
In the compilation phase of the sub-components, the slot
node will be compiled into the following AST
structure:
{
tag: 'h1',
parent: undefined,
children: [
{
tag: 'slot',
slotName: "\"hello\"",
// ...
}
],
// ...
}
Sub-component generation and rendering method
The generated rendering method is as follows, where _t
is the abbreviation of the renderSlot
method. From the renderSlot
method, we can intuitively associate the slot content with the slot point.
// 渲染方法
with(this){
return _c('h1',[ _t("hello") ], 2)
}
// 源码路径:vue-dev\src\core\instance\render-helpers\render-slot.js
export function renderSlot (
name: string,
fallback: ?Array<VNode>,
props: ?Object,
bindObject: ?Object
): ?Array<VNode> {
const scopedSlotFn = this.$scopedSlots[name]
let nodes
if (scopedSlotFn) { // scoped slot
props = props || {}
if (bindObject) {
if (process.env.NODE_ENV !== 'production' && !isObject(bindObject)) {
warn(
'slot v-bind without argument expects an Object',
this
)
}
props = extend(extend({}, bindObject), props)
}
// 作用域插槽,获取插槽VNode
nodes = scopedSlotFn(props) || fallback
} else {
// 获取插槽普通插槽VNode
nodes = this.$slots[name] || fallback
}
const target = props && props.slot
if (target) {
return this.$createElement('template', { slot: target }, nodes)
} else {
return nodes
}
}
Difference between scoped slot and named slot
<!-- demo -->
<div id='app'>
<test>
<template slot="hello" slot-scope='scope'>
{{scope.hello}}
</template>
</test>
</div>
<script>
var vm = new Vue({
el: '#app',
components: {
test: {
data () {
return {
hello: '123'
}
},
template: '<h1>' +
'<slot name="hello" :hello="hello"></slot>' +
'</h1>'
}
}
})
</script>
The main difference between the scoped slot and the ordinary slot is that the slot can get the subcomponent scope variable . Due to the need to inject sub-component variables, compared to named slots, scoped slots are different in the following points:
- When the scope slot is assembled in the rendering method, a method containing the injection scope is generated
createElement
VNode
, there is an additional layer of injection scope method package, which determines that the slotVNode
scope slot is in It is generatedVNode
, and the named slot is generated when the parent component createsVNode
._u
isresolveScopedSlots
, which is used to convert the node configuration item to{scopedSlots: {slotName: fn}}
form.
with (this) {
return _c('div', {
attrs: {
"id": "app"
}
}, [_c('test', {
scopedSlots: _u([{
key: "hello",
fn: function(scope) {
return [_v("\n " + _s(scope.hello) + "\n ")]
}
}])
})], 1)
}
- When the sub-component is initialized, the named slot node will be processed, and it will be mounted in the component
$slots
, and the scoped slot will be directly calledrenderSlot
Otherwise, the other processes are roughly the same. The slot mechanism is not difficult to understand. The key is to template and generate the render function The two steps are more content, and the process is longer and more difficult to understand.
skills
Through the above analysis, we can roughly understand the slot processing flow. Most of the work is to use templates to write vue
code, but sometimes the template has certain limitations, you need to use the render
method to amplify the component abstraction ability of vue
Then in the render
method, the method of using our slot is as follows:
Named slot
Slot processing is generally divided into two parts:
- Parent component
The parent component only needs to be written as a rendering method compiled into a template, that is, specify the name of the slot
- Subassembly
VNode
generated during the initialization phase of the parent component, the child component only needs to slot
VNode
generated by the parent component, and the child component will mount the named slot to the component $slots
property when it is initialized.
<div id='app'>
<!-- <test>-->
<!-- <template slot="hello">-->
<!-- 123-->
<!-- </template>-->
<!-- </test>-->
</div>
<script>
new Vue({
// el: '#app',
render (createElement) {
return createElement('test', [
createElement('h3', {
slot: 'hello',
domProps: {
innerText: '123'
}
})
])
},
components: {
test: {
render(createElement) {
return createElement('h1', [ this.$slots.hello ]);
}
// template: '<h1>' +
// '<slot name="hello"></slot>' +
// '</h1>'
}
}
}).$mount('#app')
</script>
Scope slot
The scope of the slot is more flexible to use and can be injected into the sub-component state. Scope slot + render
method is very useful for secondary component packaging. For example, when ElementUI
table
components are JSON
data, the scope slot is very useful.
<div id='app'>
<!-- <test>-->
<!-- <span slot="hello" slot-scope='scope'>-->
<!-- {{scope.hello}}-->
<!-- </span>-->
<!-- </test>-->
</div>
<script>
new Vue({
// el: '#app',
render (createElement) {
return createElement('test', {
scopedSlots:{
hello: scope => { // 父组件生成渲染方法中,最终转换的作用域插槽方法和这种写法一致
return createElement('span', {
domProps: {
innerText: scope.hello
}
})
}
}
})
},
components: {
test: {
data () {
return {
hello: '123'
}
},
render (createElement) {
// 作用域插槽父组件传递过来的是function,需要手动调用生成VNode
let slotVnode = this.$scopedSlots.hello({ hello: this.hello })
return createElement('h1', [ slotVnode ])
}
// template: '<h1>' +
// '<slot name="hello" :hello="hello"></slot>' +
// '</h1>'
}
}
}).$mount('#app')
</script>
summary
Knowing what it is, knowing why it is, then you can stay the same and respond to all changes.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。