Vue Slotの実現方式(ソースコード~)

4863 ワード

普段は会社でコードを書く業務が通用しないため、elementUIを使うBエンドのプロジェクトを除いて、slotを使う機会は少ない.この間、アリさんは面接を受けました.ソースコードを見たことがないので、Vnodeのようなレンダリング論理解析に基づいているはずです.
ソースコードvue 2バージョンのvue-devブランチの2.6.12から
ソース位置
まずsrcファイルの下でslotを検索して、ものはとても雑で、あまり位置付けがよくなくて、src/core/intance/render-helpers/render-slot.jsに行って見るしかなくて、この中に1つの関数があります
export function renderSlot(
    name: string,
    fallback: ?Array,
    props: ?Object,
    bindObject: ?Object
): ?Array {
    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関数解析で、1つの_になりました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,
    context: ?Component
): { [key: string]: Array } {
    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かです.の
完~