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


    // app.vue
    
    
    
    // b.vue
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    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
      )
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    可以看到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;
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    该函数返回一个对象:
    在这里插入图片描述
    最后执行_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]);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    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;
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    该方法是个闭包,返回一个函数,这个函数我们后面会用到。然后赋值给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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    可以看到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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    该函数主要执行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;
        };
       ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

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

    总结

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

  • 相关阅读:
    java多线程-Lock对象的使用
    spring设置kafka超时时间没有生效的解决方法(解决rebalancing问题)
    线代 | 【提神醒脑】自用笔记串联三 —— 相似对角化 · 二次型 · 合同变换
    【ICE】2:基于webrtc的 ice session设计及实现
    Nature文章|博士后对就业更有信心 但仍是学术界的苦力
    【电商运营】在节日期间,这几个营销误区一定要避免!
    apktool反编译及后续打包
    Cell:水平基因转移在昆虫中广泛存在,增强鳞翅目雄性昆虫求偶行为
    运行vue,浏览器出错
    【云原生之docker-compose篇】docker-compose工具的安装和基本使用
  • 原文地址:https://blog.csdn.net/qq_35094120/article/details/127554774