Vue
提供了pro
可以进行参数的传递,但是有时需要给子组件的模板进行定制化,此时传递参数有时候就不太方便了。 Vue
借鉴了Web Components实现了插槽slot
。
插槽slot
通过在父组件中编写DOM,在子组件渲染的时候将这些DOM放置在对应的位置,从而实现内容的分发。
使用方法介绍
基本使用
父组件传入的内容
我们想将一些内容渲染在
Son
子组件中,我们在组件中间写了一些内容,例如父组件传入的内容
,但是最终这些内容会被Vue
抛弃,是不会被渲染出来的。如果我们想将
父组件传入的内容
这部分内容在子组件中渲染,则需要使用slot
了。
我们只需要在渲染的结果:Son
组件模板中加入标签,则
父组件传入的内容
将替换
渲染
父组件传入的内容
默认内容 有些情况下,如果父组件不传入内容,插槽需要显示默认的内容。这时候只需要在
中放置默认的内容就行:子组件的默认内容
- 如果父组件不传入插槽内容,则渲染为:
子组件的默认内容
- 如果父组件传入插槽内容
父组件传入的内容
,则渲染为:
父组件传入的内容
具名插槽 在有些情况下可能需要多个插槽进行内容的放置, 这时候就需要给插槽一个名字:
子组件的默认内容
我们的例子中有三个插槽,其中这时候可以根据名称对每个插槽放置不同的内容:header
,footer
,还有一个没有给名字,其实它也是有名字的,不写名字它的名字就是default
, 等同于子组件的默认内容
。
父组件的内容1
父组件的内容2
外部传入的header
外部传入的footer
渲染内容如下:
外部传入的header父组件的内容1
父组件的内容2
外部传入的footer
插槽作用域 插槽的内容使用到数据,那这个数据来自于于父组件,而不是子组件:v-slot:header
包含的内容替换;
v-slot:footer
包含的内容替换;
其他所有内容都被当成v-slot:default
替换;
- 父组件
插槽的name {{ name }}
setup() {
return {
name: ref("parent"),
}
},
- 子组件
setup() {
return {
name: ref("chile"),
}
},
渲染结果:
插槽的name: parent
作用域插槽 我们刚才提到插槽的数据的作用域是父组件,有时候插槽也需要使用来自于子组件的数据,这时候可以使用作用域插槽。
- 将数据以
pro
的形式传递
- 父组件接收
pro
插槽的name {{ pro.pro }}
此时渲染的内容:
插槽的name: child
实现原理介绍 分析案例:
【Vue|Vue3.0的插槽是如何实现的()】插槽的name {{ name }}
外部传入的header
外部传入的footer
子组件的默认内容
渲染函数分析
- parent
const _hoisted_1 = /*#__PURE__*/_createElementVNode("p", null, "外部传入的header", -1 /* HOISTED */)
const _hoisted_2 = /*#__PURE__*/_createElementVNode("p", null, "外部传入的footer", -1 /* HOISTED */)function render(_ctx, _cache) {
with (_ctx) {
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = _Vueconst _component_Son = _resolveComponent("Son")return (_openBlock(), _createBlock(_component_Son, null, {
header: _withCtx(() => [
_hoisted_1
]),
footer: _withCtx(() => [
_hoisted_2
]),
default: _withCtx(() => [
_createElementVNode("p", null, "插槽的name " + _toDisplayString(name), 1 /* TEXT */)
]),
_: 1 /* STABLE */
}))
}
}
生成子组件的VNode
时传了1个children
对象, 这个对象有headr
,footer
,default
属性,这 3个属性的值就是对应的DOM
。
- son
function render(_ctx, _cache) {
with (_ctx) {
const { renderSlot: _renderSlot, createTextVNode: _createTextVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vuereturn (_openBlock(), _createElementBlock("div", _hoisted_1, [
_renderSlot($slots, "header"),
_renderSlot($slots, "default", {}, () => [
_hoisted_2
]),
_renderSlot($slots, "footer")
]))
}
}
联系这两个渲染函数我们就可以大概有个猜测:子组件渲染的时候遇到slot
这个标签,然后就找对应名字的children
对应的渲染DOM的内容,进行渲染。即通过renderSlot
会渲染headr
,footer
,default
这三个插槽的内容。
withCtx
的作用
export function withCtx(
fn: Function,
ctx: ComponentInternalInstance | null = currentRenderingInstance,
isNonScopedSlot?: boolean // __COMPAT__ only
) {const renderFnWithContext: ContextualRenderFn = (...args: any[]) => {const prevInstance = setCurrentRenderingInstance(ctx)
const res = fn(...args)
setCurrentRenderingInstance(prevInstance)return res
}return renderFnWithContext
}
保存withCtx
的作用封装 返回的函数为传入的fn
,重要的是保存当前的组件实例currentRenderingInstance
,作为函数的作用域。
children
到组件实例的slots
setupComponent
setup组件实例的时候会调用initSlots
setup组件实例是什么作用?如果不知道可以参阅我前面的文章。不想看,可以直接理解为先准备数据的阶段,之后会进行组件渲染。
export function setupComponent(
instance: ComponentInternalInstance,
isSSR = false
) {
initSlots(instance, children)
}
children
保存到instance.slots
上
export const initSlots = (
instance: ComponentInternalInstance,
children: VNodeNormalizedChildren
) => {
// we should avoid the proxy object polluting the slots of the internal instance
instance.slots = toRaw(children as InternalSlots)
def(instance.slots, "__vInternal", 1)
}
renderSlot
渲染slot
内容对应的VNode
export function renderSlot(
slots: Slots,
name: string,
props: Data = https://www.it610.com/article/{},
// this is not a user-facing function, so the fallback is always generated by
// the compiler and guaranteed to be a function returning an array
fallback?: () => VNodeArrayChildren,
noSlotted?: boolean
): VNode {let slot = slots[name]const validSlotContent = slot && ensureValidVNode(slot(props))
const rendered = createBlock(
Fragment,
{ key: props.key || `_${name}` },
validSlotContent || (fallback ? fallback() : []),
validSlotContent && (slots as RawSlots)._ === SlotFlags.STABLE
? PatchFlags.STABLE_FRAGMENT
: PatchFlags.BAIL
)
return rendered
}
renderSlot
创建的VNode
是一个类型为Fragment
,children
为对应name
的插槽的返回值。
结合前面的withCtx
的分析,总结来就是renderSlot
创建的VNode
是一个类型为Fragment
,children
为对应name
的插槽的内容,但是插槽内的数据的作用域是属于父组件的。
processFragment
挂载slot
内容对应的DOM
const processFragment = (
n1: VNode | null,
n2: VNode,
container: RendererElement,
anchor: RendererNode | null,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
slotScopeIds: string[] | null,
optimized: boolean
) => {
//
const fragmentStartAnchor = (n2.el = n1 ? n1.el : hostCreateText(''))!
const fragmentEndAnchor = (n2.anchor = n1 ? n1.anchor : hostCreateText(''))!let { patchFlag, dynamicChildren, slotScopeIds: fragmentSlotScopeIds } = n2if (n1 == null) {
// 插入两个空文本节点
hostInsert(fragmentStartAnchor, container, anchor)
hostInsert(fragmentEndAnchor, container, anchor)// 挂载数组子节点
mountChildren(
n2.children as VNodeArrayChildren,
container,
fragmentEndAnchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
} else {
// 更新数组子节点
patchChildren(
n1,
n2,
container,
fragmentEndAnchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
}
}
作用域插槽和默认内容的实现逻辑processFragment
先插入两个空文本节点作为锚点,然后挂载数组子节点。
// 默认内容
const _hoisted_2 = /*#__PURE__*/_createTextVNode("子组件的默认内容")// pro
renderSlot($slots, "default", { pro: name }, () => [
_hoisted_2
])
子组件的数据和默认插槽内容作为我们再回到renderSlot
函数的第3个和第4个参数,进行插槽的内容渲染。
renderSlot
函数/**
* @param slots 组件VNode的slots
* @param nameslot的name
* @param props slot的pro
* @param fallback 默认的内容
* @param noSlotted
* @returns
*/
export function renderSlot(
slots: Slots,
name: string,
props: Data = https://www.it610.com/article/{},
fallback?: () => VNodeArrayChildren,
noSlotted?: boolean
): VNode {// 从 组件VNode的slots对象中找到name对应的渲染函数
let slot = slots[name]// props作为参数执行渲染函数,这样渲染函数就拿到了子组件的数据
const validSlotContent = slot && ensureValidVNode(slot(props))
const rendered = createBlock(
Fragment,
{ key: props.key || `_${name}` },
validSlotContent || (fallback ? fallback() : []),
PatchFlags.STABLE_FRAGMENT
)
return rendered
}
总结renderSlot
函数接收pros
的参数,将其传入slots
对象中找到name
对应的渲染函数,这样就能获取到子组件的数据pros
了;
fallback
是默认的渲染函数,如果父组件没有传递slot
,就渲染默认的DOM。
- 父组件渲染的时候生成一些
withCtx
包含的渲染函数,此时将父组件的实例对象持有在函数内部,,所以数据的作用域是父组件;- 子组件在
setupComponent
先将这些withCtx
包含的渲染函数存储在子组件实例对象的slots
上;- 子组件渲染的时候,插槽内容的渲染是先找到
slots
中对应的withCtx
包含的渲染函数,然后传入子组件的pro
和默认的渲染DOM内容,最后生成插槽渲染内容的DOM内容。
文章图片
一句话总结:父组件先编写DOM存在子组件实例对象上,渲染子组件的时候再渲染对应的这部分DOM内容。
推荐阅读
- 操作系统|[译]从内部了解现代浏览器(1)
- web网页模板|如此优秀的JS轮播图,写完老师都沉默了
- 接口|axios接口报错-参数类型错误解决
- JavaScript|vue 基于axios封装request接口请求——request.js文件
- vue.js|vue中使用axios封装成request使用
- JavaScript|JavaScript: BOM对象 和 DOM 对象的增删改查
- JavaScript|JavaScript — 初识数组、数组字面量和方法、forEach、数组的遍历
- JavaScript|JavaScript — call()和apply()、Date对象、Math、包装类、字符串的方法
- JavaScript|JavaScript之DOM增删改查(重点)
- 前端|web前端dya07--ES6高级语法的转化&render&vue与webpack&export