从源码看vue(v2.7.10)中的slot的原理

// app.vue
<template>
  <div>
    <bb>
      <template v-slot:header="user">
        {{user}}
      </template>
    </bb>
  </div>
</template>

<script>
import bb from './bb.vue'
export default {
  name: "app",
  components: {
    bb
  }
};
</script>
// b.vue
<template>
  <div>
    <slot name="header" :user="user"></slot>
  </div>
</template>

<script>
export default {
  name: 'bb',
  data() {
    return {
      user: '王哥'
    }
  }
}
</script>

app.vue的渲染函数:

var render = function render() {
  var _vm = this,
    _c = _vm._self._c
  return _c(
    "div",
    [
      _c("bb", {
        scopedSlots: _vm._u([
          {
            key: "header",
            fn: function (user) {
              return [_vm._v("\n      " + _vm._s(user) + "\n    ")]
            },
          },
        ]),
      }),
    ],
    1
  )
}

可以看到v-slot:header="user"被渲染成一个名为scopedSlots的对象,{{user}}被放入函数中,传入的user就是v-slot:header="user"的value。然后执行_vm._u方法:

function resolveScopedSlots(fns, res, 
  // the following are added in 2.6
  hasDynamicKeys, contentHashKey) {
      res = res || { $stable: !hasDynamicKeys };
      for (var i = 0; i < fns.length; i++) {
          var slot = fns[i];
          if (isArray(slot)) {
              resolveScopedSlots(slot, res, hasDynamicKeys);
          }
          else if (slot) {
              // marker for reverse proxying v-slot without scope on this.$slots
              // @ts-expect-error
              if (slot.proxy) {
                  // @ts-expect-error
                  slot.fn.proxy = true;
              }
              res[slot.key] = slot.fn;
          }
      }
      if (contentHashKey) {
          res.$key = contentHashKey;
      }
      return res;
  }

该函数返回一个对象:
在这里插入图片描述
最后执行_c方法生成bb组件的vnode:
在这里插入图片描述
可以看出来插槽传值的原理,其实就是把你传入的值当做函数变量,插槽里面的值就是函数体。当初始化bb.vue组件的时候会执行initRender(vm)方法,该方法主要执行normalizeScopedSlot方法:

for (var key_1 in scopedSlots) {
    if (scopedSlots[key_1] && key_1[0] !== '$') {
        res[key_1] = normalizeScopedSlot(ownerVm, normalSlots, key_1, scopedSlots[key_1]);
    }
}
function normalizeScopedSlot(vm, normalSlots, key, fn) {
      var normalized = function () {
          var cur = currentInstance;
          setCurrentInstance(vm);
          var res = arguments.length ? fn.apply(null, arguments) : fn({});
          res =
              res && typeof res === 'object' && !isArray(res)
                  ? [res] // single vnode
                  : normalizeChildren(res);
          var vnode = res && res[0];
          setCurrentInstance(cur);
          return res &&
              (!vnode ||
                  (res.length === 1 && vnode.isComment && !isAsyncPlaceholder(vnode))) // #9658, #10391
              ? undefined
              : res;
      };
      // this is a slot using the new v-slot syntax without scope. although it is
      // compiled as a scoped slot, render fn users would expect it to be present
      // on this.$slots because the usage is semantically a normal slot.
      if (fn.proxy) {
          Object.defineProperty(normalSlots, key, {
              get: normalized,
              enumerable: true,
              configurable: true
          });
      }
      return normalized;
  }

该方法是个闭包,返回一个函数,这个函数我们后面会用到。然后赋值给bb组件的vnode。
在这里插入图片描述

我们继续看bb.vue组件的渲染函数:

var render = function render() {
  var _vm = this,
    _c = _vm._self._c
  return _c("div", [_vm._t("header", null, { user: _vm.user })], 2)
}

可以看到name="header"被当做_t函数的参数传入,:user="user"作为第三个参数传入。继续看看_t函数:

function renderSlot(name, fallbackRender, props, bindObject) {
    var scopedSlotFn = this.$scopedSlots[name];
    var nodes;
    if (scopedSlotFn) {
        // scoped slot
        props = props || {};
        if (bindObject) {
            if (!isObject(bindObject)) {
                warn$2('slot v-bind without argument expects an Object', this);
            }
            props = extend(extend({}, bindObject), props);
        }
        nodes =
            scopedSlotFn(props) ||
                (isFunction(fallbackRender) ? fallbackRender() : fallbackRender);
    }
    else {
        nodes =
            this.$slots[name] ||
                (isFunction(fallbackRender) ? fallbackRender() : fallbackRender);
    }
    var target = props && props.slot;
    if (target) {
        return this.$createElement('template', { slot: target }, nodes);
    }
    else {
        return nodes;
    }
}

该函数主要执行scopedSlotFn(props)方法:
在这里插入图片描述

function normalizeScopedSlot(vm, normalSlots, key, fn) {
    var normalized = function () {
        var cur = currentInstance;
        setCurrentInstance(vm);
        var res = arguments.length ? fn.apply(null, arguments) : fn({});
        res =
            res && typeof res === 'object' && !isArray(res)
                ? [res] // single vnode
                : normalizeChildren(res);
        var vnode = res && res[0];
        setCurrentInstance(cur);
        return res &&
            (!vnode ||
                (res.length === 1 && vnode.isComment && !isAsyncPlaceholder(vnode))) // #9658, #10391
            ? undefined
            : res;
    };
   ...
}

这里的fn如下图:
在这里插入图片描述
这里会执行该函数并传入需要的参数user然后返回vnode。从这里我们可以看到,slot的值取决于传入的props和fn的返回值。

总结

我们在组件里面写的<template v-slot:header="user"> {{user}} </template>
会被渲染成一个对象,该对象里面的参数就是header后面的值,template里面的表达式就是函数返回值,header会被当做函数的key。当渲染到<slot name="header" :user="user"></slot>时,会先根据name找到key为header函数,然后将props的值{user:'王哥'}当做参数传给该函数并执行返回一个vnode。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Young soul2

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值