password
Created
Feb 19, 2023 03:35 PM
type
Post
status
Published
date
Sep 19, 2021
slug
summary
介绍Vue中插槽Slot的使用及实现方式
tags
Vue
Vue Slot
Vue源码
category
源码
icon
插槽的核心是: 在父组件中预定义好子组件插槽部分的模板, 然后再子组件渲染的过程中, 将插槽中的这部分内容填充到这个子组件定义的插槽中.
因此, 在父组件渲染的阶段, 子组件插槽部分的DOM是不能渲染的, 需要以某种方式保留下来, 等子组件渲染的时候, 在一并渲染, 并携带上子组件的对应作用域.
插槽初始化
function setupComponent(instance, isSSR = false) { const { props, children, shapeFlag } = instance.vnode; // ... initSlots(instance, children) // ... }
initSlots
主要就是将插槽对象保存到了
instance.slots
中, 以便后续拿取const initSlots = (instance, children) => { // 32: SLOTS_CHILDREN if (instance.vnode.shapeFlag & 32) { const type = children._ if (type) { instance.slots = children def(children, '_', type) } else { normalizeObjectSlots(children, (instance.slots = {})) } } else { instance.slots = {} if (children) { normalizeVNodeSlots(instance, children) } } def(instance.slots, InternalObjectKey, 1) }
renderSlot
SFC编译之后的代码中,
slot
使用的是renderSlot
函数来进行处理- 根据
name
来获取对应的插槽函数slot
(从前面的slots
, 也就是instance.slots
中取)
- 通过
createBlock
创建vnode
节点
function renderSlot(slots, name, props = {}, fallback) { // 获取对应的slot let slot = slots[name] // 1: STABLE 64: STABLE_FRAGMENT -2: BAIL return (openBlock(), createBlock(Fragment, {key: props.key}, slot ? slot(props) : fallback ? fallback() : [], slots._ === 1 ? 64 : -2)) }
withCtx
再具体到slots函数中, 有这样的结构
{ default: _withCtx(() => [ // 1: TEXT _createVNode("p", null, _toDisplayString(_ctx.main), 1) ]) } function withCtx(fn, ctx = currentRenderingInstance) { if (!ctx) return fn return function renderFnWithContext() { // 先保存当前渲染组件实例 const owner = currentRenderingInstance // 然后将ctx设置为当前渲染组件实例 setCurrentRenderingInstance(ctx) // 执行 const res = fn.apply(null, arguments) // 完毕之后再将渲染组件实例设置回去 setCurrentRenderingInstance(owner) return res } }
如上代码, 将ctx进行了切换执行处理, 然后再设置回来
这样的好处是, 既能够保证在子组件中渲染具体插槽内容, 又能保证它的数据作用域也是父组件
总结
插槽实际上就是一种延时渲染, 把父组件中编写的插槽内容保存到一个对象上
并且吧具体渲染DOM的代码用函数的方式封装, 然后再子组件渲染的时候, 根据插槽名在对象中找到对应的函数, 然后执行这些函数做真正的渲染