Vue Slot 的实现方式(源码~)

因为平时在公司写代码业务不是很通用,除了一些使用 elementUI 的 B 端项目,用 slot 的机会比较少。前段时间阿里面试(没想找工作 o(╥﹏╥)o)问到了这个问题,写个文章稍微研究一下。我当时的回答是:没看过源码,应该是基于 Vnode 类似的渲染逻辑解析的

源码来自 vue 2 版本的 vue-dev 分支的 2.6.12

源码定位

先在 src 文件下搜索 slot,东西很杂,不太好定位,只好去 src/core/intance/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)
        }
        nodes = scopedSlotFn(props) || fallback
    } else {
        nodes = this.$slots[name] || fallback
    }

    const target = props && props.slot
    if (target) {
        return this.$createElement('template', { slot: target }, nodes)
    } else {
        return nodes
    }
}

从里面大致可以看住,这函数的功能是:返回插入的 nodes 节点 / 在目标模板上插入这个 nodes 节点

render-helpers 目录下的文件从名字上就能理解是辅助 render 的,这些函数会通过 src/core/instance/render-helpers/index.js 绑在 Vue.FunctionalRenderContext 上

export function installRenderHelpers (target: any) {
  ...
  target._t = renderSlot
  ...
}

Object.defineProperty(Vue, 'FunctionalRenderContext', {
    value: FunctionalRenderContext
})

installRenderHelpers(FunctionalRenderContext.prototype)

搜 ._t 的调用是搜不到的,因为这里的 _t 函数还有其他的函数,是用于 render 函数中使用的,举个例子:cli 自带 demo 的 helloWorld 组件搞一个插槽(父组件插入 123),他的 render 函数返回值长这个德行:return _c("div", { staticClass: "hello" }, [_vm._t("default")], 2),父组件的长这个德行return _c( "div", { attrs: { id: "app" } }, [_c("HelloWorld", [_vm._v("123")])], 1),其中_c 代表 createElement、 _t 代表 renderSlot、 _v 代表 createTextVNode

具体实现

子组件的 slot

子组件是怎么知道这有个 slot 的?其实就是 template 解析或者 render 函数解析,把 <slot> 变成了一个_t(renderSlot) 函数,_t 函数会自己去 $slots 里面取插槽的内容

父组件的 slot 内容

父组件怎么知道子组件有东西接着 slot?其实父组件并不怎么关心子组件是不是有插槽,子组件作为父组件的节点,父组件只需要把插槽里面的内容当做子组件的 children 传进去就可以了

父子组件 slot 内容的传递

所以这个问题就变成了,父组件传进来的内容是怎么赋值到子组件的 $slots 上的

Vue.prototype._init = function (options?: Object) {
    if (options && options._isComponent) {
        initInternalComponent(vm, options) 
    }
    ...
    initRender(vm);
    ...
}

function initInternalComponent(vm: Component, options: InternalComponentOptions) {
    ...
    const parentVnode = options._parentVnode
    ...
    const vnodeComponentOptions = parentVnode.componentOptions
    ...
    opts._renderChildren = vnodeComponentOptions.children
    ...
}

function initRender(vm) {
    ...
    vm.$slots = resolveSlots(options._renderChildren, renderContext);
    ...
}

initInternalComponent 会把 _renderChildren 挂在 options 上
这里的 options._renderChildren 就是 上面提到的 [_vm._v("123")] 对应的 [Vnode]

function resolveSlots(
    children: ?Array<VNode>,
    context: ?Component
): { [key: string]: Array<VNode> } {
    if (!children || !children.length) {
        return {}
    }
    const slots = {}
    for (let i = 0, l = children.length; i < l; i++) {
        const child = children[i]
        const data = child.data
        // remove slot attribute if the node is resolved as a Vue slot node
        if (data && data.attrs && data.attrs.slot) {
            delete data.attrs.slot
        }
        // named slots should only be respected if the vnode was rendered in the
        // same context.
        if ((child.context === context || child.fnContext === context) &&
            data && data.slot != null
        ) {
            const name = data.slot
            const slot = (slots[name] || (slots[name] = []))
            if (child.tag === 'template') {
                slot.push.apply(slot, child.children || [])
            } else {
                slot.push(child)
            }
        } else {
            (slots.default || (slots.default = [])).push(child)
        }
    }
    // ignore slots that contains only whitespace
    for (const name in slots) {
        if (slots[name].every(isWhitespace)) {
            delete slots[name]
        }
    }
    return slots
}

这里就是把 _renderChildren 变成 slots,这里还有点 匿名和具名插槽 的内容:也就是 slot 的 name 是有值还是默认的 default。。。

完~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342