视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
Vue中的slots/scopedslots应该如何使用
2020-11-27 19:56:17 责编:小采
文档


这次给大家带来Vue中的slots/scoped slots应该如何使用,Vue中的slots/scoped slots使用的注意事项有哪些,下面就是实战案例,一起来看一下。

一直对Vue中的slot插槽比较感兴趣,下面是自己的一些简单理解,希望可以帮助大家更好的理解slot插槽

下面结合一个例子,简单说明slots的工作原理

dx-li子组件的template如下:

<li class="dx-li">
 <slot>
 你好!
 </slot>
</li>
dx-ul父组件的template如下:
<ul>
 <dx-li>
 hello juejin!
 </dx-li>
</ul>
结合上述例子以及vue中相关源码进行分析
dx-ul父组件中template编译后,生成的组件render函数:
module.exports={
 render:function (){
 var _vm=this;
 var _h=_vm.$createElement;
 var _c=_vm._self._c||_h;
 // 其中_vm.v为createTextVNode创建文本VNode的函数
 return _c('ul', 
 [_c('dx-li', [_vm._v("hello juejin!")])],
 1)
 },
 staticRenderFns: []
}

传递的插槽内容'hello juejin!'会被编译成dx-li子组件VNode节点的子节点。

渲染dx-li子组件,其中子组件的render函数:

module.exports={
 render:function (){
 var _vm=this;
 var _h=_vm.$createElement;
 var _c=_vm._self._c||_h;
 // 其中_vm._v 函数为renderSlot函数
 return _c('li', 
 {staticClass: "dx-li" }, 
 [_vm._t("default", [_vm._v("你好 掘金!")])], 
 2
 )
 },
 staticRenderFns: []
}

初始化dx-li子组件vue实例过程中,会调用initRender函数:

function initRender (vm) {
 ...
 // 其中_renderChildren数组,存储为 'hello juejin!'的VNode节点;renderContext一般为父组件Vue实例
 这里为dx-ul组件实例
 vm.$slots = resolveSlots(options._renderChildren, renderContext);
 ...
}

其中resolveSlots函数为:

/**
 * 主要作用是将children VNodes转化成一个slots对象.
 */
export function resolveSlots (
 children: ?Array<VNode>,
 context: ?Component
): { [key: string]: Array<VNode> } {
 const slots = {}
 // 判断是否有children,即是否有插槽VNode
 if (!children) {
 return slots
 }
 // 遍历父组件节点的孩子节点
 for (let i = 0, l = children.length; i < l; i++) {
 const child = children[i]
 // data为VNodeData,保存父组件传递到子组件的props以及attrs等
 const data = child.data
 /* 移除slot属性
 * <span slot="abc"></span> 
 * 编译成span的VNode节点data = {attrs:{slot: "abc"}, slot: "abc"},所以这里删除该节点attrs的slot
 */
 if (data && data.attrs && data.attrs.slot) {
 delete data.attrs.slot
 }
 /* 判断是否为具名插槽,如果为具名插槽,还需要子组件/函数子组件渲染上下文一致。主要作用:
 *当需要向子组件的子组件传递具名插槽时,不会保持插槽的名字。
 * 举个栗子:
 * child组件template: 
 * <p>
 * <p class="default"><slot></slot></p>
 * <p class="named"><slot name="foo"></slot></p>
 * </p>
 * parent组件template:
 * <child><slot name="foo"></slot></child>
 * main组件template:
 * <parent><span slot="foo">foo</span></parent>
 * 此时main渲染的结果:
 * <p>
 * <p class="default"><span slot="foo">foo</span></p>
 <p class="named"></p>
 * </p>
 */
 if ((child.context === context || child.fnContext === context) &&
 data && data.slot != null
 ) {
 const name = data.slot
 const slot = (slots[name] || (slots[name] = []))
 // 这里处理父组件采用template形式的插槽
 if (child.tag === 'template') {
 slot.push.apply(slot, child.children || [])
 } else {
 slot.push(child)
 }
 } else {
 // 返回匿名default插槽VNode数组
 (slots.default || (slots.default = [])).push(child)
 }
 }
 // 忽略仅仅包含whitespace的插槽
 for (const name in slots) {
 if (slots[name].every(isWhitespace)) {
 delete slots[name]
 }
 }
 return slots
}

然后挂载dx-li组件时,会调用dx-li组件render函数,在此过程中会调用renderSlot函数:

export function renderSlot (
 name: string, // 子组件中slot的name,匿名default
 fallback: ?Array<VNode>, // 子组件插槽中默认内容VNode数组,如果没有插槽内容,则显示该内容
 props: ?Object, // 子组件传递到插槽的props
 bindObject: ?Object // 针对<slot v-bind="obj"></slot> obj必须是一个对象
 ): ?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)
 }
 // 传入props生成相应的VNode
 nodes = scopedSlotFn(props) || fallback
 } else {
 // 如果父组件没有传递作用域插槽
 const slotNodes = this.$slots[name]
 // warn duplicate slot usage
 if (slotNodes) {
 if (process.env.NODE_ENV !== 'production' && slotNodes._rendered) {
 warn(
 `Duplicate presence of slot "${name}" found in the same render tree ` +
 `- this will likely cause render errors.`,
 this
 )
 }
 // 设置父组件传递插槽的VNode._rendered,用于后面判断是否有重名slot
 slotNodes._rendered = true
 }
 // 如果没有传入插槽,则为默认插槽内容VNode
 nodes = slotNodes || fallback
 }
 // 如果还需要向子组件的子组件传递slot
 /*举个栗子:
 * Bar组件: <p class="bar"><slot name="foo"/></p>
 * Foo组件:<p class="foo"><bar><slot slot="foo"/></bar></p>
 * main组件:<p><foo>hello</foo></p>
 * 最终渲染:<p class="foo"><p class="bar">hello</p></p>
 */
 const target = props && props.slot
 if (target) {
 return this.$createElement('template', { slot: target }, nodes)
 } else {
 return nodes
 }
 }

scoped slots理解

dx-li子组件的template如下:

<li class="dx-li"> 
 <slot str="你好 掘金!">
 hello juejin!
 </slot>
</li>
dx-ul父组件的template如下:
<ul>
 <dx-li>
 <span slot-scope="scope">
 {{scope.str}}
 </span>
 </dx-li>
</ul>
结合例子和Vue源码简单作用域插槽
dx-ul父组件中template编译后,产生组件render函数:
module.exports={
 render:function (){
 var _vm=this;
 var _h=_vm.$createElement;
 var _c=_vm._self._c||_h;
 return _c('ul', [_c('dx-li', {
 // 可以编译生成一个对象数组
 scopedSlots: _vm._u([{
 key: "default",
 fn: function(scope) {
 return _c('span', 
 {},
 [_vm._v(_vm._s(scope.str))]
 )
 }
 }])
 })], 1)
 },
 staticRenderFns: []
 }

其中 _vm._u函数:

function resolveScopedSlots (
 fns, // 为一个对象数组,见上文scopedSlots
 res
) {
 res = res || {};
 for (var i = 0; i < fns.length; i++) {
 if (Array.isArray(fns[i])) {
 // 递归调用
 resolveScopedSlots(fns[i], res);
 } else {
 res[fns[i].key] = fns[i].fn;
 }
 }
 return res
}

子组件的后续渲染过程与slots类似。scoped slots原理与slots基本是一致,不同的是编译父组件模板时,会生成一个返回结果为VNode的函数。当子组件匹配到父组件传递作用域插槽函数时,调用该函数生成对应VNode。

相信看了本文案例你已经掌握了方法,更多精彩请关注Gxl网其它相关文章!

推荐阅读:

react实现手机号的数据同步

babel怎么转换es6的class语法

Vue filter的使用详解

下载本文
显示全文
专题