zoukankan      html  css  js  c++  java
  • 什么是作用域插槽?插槽与作用域插槽的区别

    一、概念

    1.  
      // 有name的属于具名插槽,没有name属于匿名插槽
    2.  
      <app>
    3.  
      <div slot="a">xxxx</div>
    4.  
      <div slot="b">xxxx</div>
    5.  
      </app>
    6.  
      <slot name="a"></slot>
    7.  
      <slot name="b"></slot>
    8.  
       
    9.  
      普通插槽渲染的位置是在它的父组件里面,而不是在子组件里面
    10.  
      作用域插槽渲染是在子组件里面

    1.插槽slot

    在渲染父组件的时候,会将插槽中的先渲染。

    创建组件虚拟节点时,会将组件的儿子的虚拟节点保存起来。当初始化组件时,通过插槽属性将儿 子进行分类 {a:[vnode],b[vnode]}

    渲染组件时会拿对应的slot属性的节点进行替换操作。(插槽的作用域为父组件,插槽中HTML模板显示不显示、以及怎样显示由父组件来决定)

    有name的父组件通过html模板上的slot属性关联具名插槽。没有slot属性的html模板默认关联匿名插槽。

    2.作用域插槽slot-scope

    作用域插槽在解析的时候,不会作为组件的孩子节点。会解析成函数,当子组件渲染时,会调用此函数进行渲染。

    或者可以说成作用域插槽是子组件可以在slot标签上绑定属性值,在父组件可以拿到子组件的数据,通过子组件绑定数据传递给父组件。(插槽的作用域为子组件)

    1.  
      子组件:
    2.  
      <slot :nickName="'wthreesix'"></slot>
    3.  
       
    4.  
      父组件:
    5.  
      <slot-child>
    6.  
      <template slot-scope="scope">
    7.  
      <div>{{scope.nickName}}</div>
    8.  
      </template>
    9.  
      </slot-child>

    二、源码图

    三、插槽渲染分析

    1.插槽

    1.  
      // 父组件:
    2.  
      const VueTemplateCompiler = require('vue-template-compiler');
    3.  
      let ele = VueTemplateCompiler.compile(`
    4.  
      <my-component>
    5.  
      <div slot="header">node</div>
    6.  
      <div>react</div>
    7.  
      <div slot="footer">vue</div>
    8.  
      </my-component>
    9.  
      `)
    10.  
       
    11.  
      将上面组件编译后:
    12.  
      /**
    13.  
      with(this) {
    14.  
      return _c('my-component', // _c 创建虚拟dom
    15.  
      [ // 组件中包含子节点
    16.  
      _c('div', // 第一个是一个插槽,插槽叫header
    17.  
      {
    18.  
      attrs: {
    19.  
      "slot": "header"
    20.  
      },
    21.  
      slot: "header"
    22.  
      },
    23.  
      [_v("node")] // 将里面内容存起来了(_v是createTextVNode创建文本节点)
    24.  
      ),
    25.  
      _v(" "),
    26.  
      _c('div',[_v("react")]),
    27.  
      _v(" "),
    28.  
      _c('div', {
    29.  
      attrs: {
    30.  
      "slot": "footer"
    31.  
      },
    32.  
      slot: "footer"
    33.  
      }, [_v("vue")])
    34.  
      ]
    35.  
      )
    36.  
      }
    37.  
      */
    38.  
      在调render方法的时候已经将组件全部渲染好了
    39.  
       
    40.  
      // 子组件
    41.  
      let ele = VueTemplateCompiler.compile(`
    42.  
      <div>
    43.  
      <slot name="header"></slot>
    44.  
      <slot name="footer"></slot>
    45.  
      <slot></slot>
    46.  
      </div>
    47.  
      `);
    48.  
      /**
    49.  
      with(this) {
    50.  
      // 渲染的时候会找header对应的是谁
    51.  
      return _c('div', [
    52.  
      _t("header"), _v(" "),
    53.  
      _t("footer"), _v(" "),
    54.  
      _t("default")], 2)
    55.  
      }
    56.  
      当找到就会换过来,如下:
    57.  
      return _c('div', [_v("node"), _v(" "), _v("vue"), _v(" "),
    58.  
      _t("default")], 2)
    59.  
      }
    60.  
      }
    61.  
      **/
    62.  
      // _t是renderSlot
    63.  
       
    64.  
      插槽就是一个替换的过程,将父组件渲染好的结果直接替换到自己的上面,创建的过程相当于在父组件渲染的

    2.作用域插槽

    1.  
      父组件:
    2.  
      let ele = VueTemplateCompiler.compile(`
    3.  
      <app>
    4.  
      <div slot-scope="msg" slot="footer">{{msg.a}}</div>
    5.  
      </app>
    6.  
      `);
    7.  
      /**
    8.  
      with(this) {
    9.  
      // 编译出的不是一个child,而是一个属性scopedSlots
    10.  
      return _c('app', {
    11.  
      scopedSlots: _u([{
    12.  
      key: "footer",
    13.  
      fn: function (msg) { 将子节点变成一个函数,这个函数不调用就不会去渲染这个子节点
    14.  
      return _c('div', {}, [_v(_s(msg.a))])
    15.  
      }
    16.  
      }])
    17.  
      })
    18.  
      }
    19.  
      }
    20.  
      在初始化的时候并不会渲染子节点
    21.  
      */
    22.  
       
    23.  
      子组件:
    24.  
      const VueTemplateCompiler = require('vue-template-compiler');
    25.  
      VueTemplateCompiler.compile(`
    26.  
      // 当我们在写slot去执行的时候进行渲染
    27.  
      <div>
    28.  
      <slot name="footer" a="1" b="2"></slot>
    29.  
      </div>
    30.  
      `);
    31.  
      /**
    32.  
      with(this) {
    33.  
      // 去找footer,找见会调用上面的那个函数,并且将属性传入到这个函数里面,这时候才会把这节点进行渲染,完成之后替换调
    34.  
      return _c('div', [_t("footer", null, {
    35.  
      "a": "1",
    36.  
      "b": "2"
    37.  
      })], 2)
    38.  
      }
    39.  
      **/
    40.  
       
    41.  
      // 作用域插槽的内容会被渲染成一个函数
    42.  
      // 作用域插槽渲染是在当前组件的内部,不是在父组件中

    四、源码

    1.initRender(初始化render,构建vm.$slots)

    1.  
      export function initRender (vm: Component) {
    2.  
      vm.$slots = resolveSlots(options._renderChildren, renderContext)
    3.  
      vm.$scopedSlots = emptyObject
    4.  
      }

    2.resolveSlots(映射slot名字和对应的vnode)

    1.  
      export function resolveSlots (
    2.  
      children: ?Array<VNode>,
    3.  
      context: ?Component
    4.  
      ): { [key: string]: Array<VNode> } {
    5.  
      if (!children || !children.length) {
    6.  
      return {}
    7.  
      }
    8.  
      const slots = {}
    9.  
      for (let i = 0, l = children.length; i < l; i++) {
    10.  
      const child = children[i]
    11.  
      const data = child.data
    12.  
      // remove slot attribute if the node is resolved as a Vue slot node
    13.  
      if (data && data.attrs && data.attrs.slot) {
    14.  
      delete data.attrs.slot
    15.  
      }
    16.  
      // named slots should only be respected if the vnode was rendered in the
    17.  
      // same context.
    18.  
      if ((child.context === context || child.fnContext === context) &&
    19.  
      data && data.slot != null
    20.  
      ) {
    21.  
      const name = data.slot
    22.  
      const slot = (slots[name] || (slots[name] = []))
    23.  
      if (child.tag === 'template') {
    24.  
      slot.push.apply(slot, child.children || [])
    25.  
      } else {
    26.  
      slot.push(child)
    27.  
      }
    28.  
      } else {
    29.  
      (slots.default || (slots.default = [])).push(child)
    30.  
      }
    31.  
      }
    32.  
      // ignore slots that contains only whitespace
    33.  
      for (const name in slots) {
    34.  
      if (slots[name].every(isWhitespace)) {
    35.  
      delete slots[name]
    36.  
      }
    37.  
      }
    38.  
      return slots
    39.  
      }

    3.normalizeScopedSlots(core/vdom/helpers/normalize-scoped-slot.js)

    1.  
      export function normalizeScopedSlots (
    2.  
      slots: { [key: string]: Function } | void,
    3.  
      normalSlots: { [key: string]: Array<VNode> },
    4.  
      prevSlots?: { [key: string]: Function } | void
    5.  
      ): any {
    6.  
      let res
    7.  
      const hasNormalSlots = Object.keys(normalSlots).length > 0
    8.  
      const isStable = slots ? !!slots.$stable : !hasNormalSlots
    9.  
      const key = slots && slots.$key
    10.  
      if (!slots) {
    11.  
      res = {}
    12.  
      } else if (slots._normalized) {
    13.  
      // fast path 1: child component re-render only, parent did not change
    14.  
      return slots._normalized
    15.  
      } else if (
    16.  
      isStable &&
    17.  
      prevSlots &&
    18.  
      prevSlots !== emptyObject &&
    19.  
      key === prevSlots.$key &&
    20.  
      !hasNormalSlots &&
    21.  
      !prevSlots.$hasNormal
    22.  
      ) {
    23.  
      // fast path 2: stable scoped slots w/ no normal slots to proxy,
    24.  
      // only need to normalize once
    25.  
      return prevSlots
    26.  
      } else {
    27.  
      res = {}
    28.  
      for (const key in slots) {
    29.  
      if (slots[key] && key[0] !== '$') {
    30.  
      res[key] = normalizeScopedSlot(normalSlots, key, slots[key]) // 作用域插槽
    31.  
      }
    32.  
      }
    33.  
      }
    34.  
      // expose normal slots on scopedSlots
    35.  
      for (const key in normalSlots) {
    36.  
      if (!(key in res)) {
    37.  
      res[key] = proxyNormalSlot(normalSlots, key) // 普通插槽
    38.  
      }
    39.  
      }
    40.  
      // avoriaz seems to mock a non-extensible $scopedSlots object
    41.  
      // and when that is passed down this would cause an error
    42.  
      if (slots && Object.isExtensible(slots)) {
    43.  
      (slots: any)._normalized = res
    44.  
      }
    45.  
      def(res, '$stable', isStable)
    46.  
      def(res, '$key', key)
    47.  
      def(res, '$hasNormal', hasNormalSlots)
    48.  
      return res
    49.  
      }

    4.proxyNormalSlot(将slot代理到scopeSlots上)

    1.  
      function proxyNormalSlot(slots, key) {
    2.  
      return () => slots[key]
    3.  
      }

    5.normalizeScopedSlot(将scopeSlots对应属性和方法挂载到scopeSlots)

    1.  
      function normalizeScopedSlot(normalSlots, key, fn) {
    2.  
      const normalized = function () {
    3.  
      let res = arguments.length ? fn.apply(null, arguments) : fn({})
    4.  
      res = res && typeof res === 'object' && !Array.isArray(res)
    5.  
      ? [res] // single vnode
    6.  
      : normalizeChildren(res)
    7.  
      return res && (
    8.  
      res.length === 0 ||
    9.  
      (res.length === 1 && res[0].isComment) // #9658
    10.  
      ) ? undefined
    11.  
      : res
    12.  
      }
    13.  
      // this is a slot using the new v-slot syntax without scope. although it is
    14.  
      // compiled as a scoped slot, render fn users would expect it to be present
    15.  
      // on this.$slots because the usage is semantically a normal slot.
    16.  
      if (fn.proxy) {
    17.  
      Object.defineProperty(normalSlots, key, {
    18.  
      get: normalized,
    19.  
      enumerable: true,
    20.  
      configurable: true
    21.  
      })
    22.  
      }
    23.  
      return normalized
    24.  
      }

    6.最后调用renderSlot用函数的返回值进行渲染。

  • 相关阅读:
    linux解压缩各种命令
    memset
    STRUCT_OFFSET( s, m )宏
    请问这个宏是什么意思 #define NOTUSED(v) ((void) v)?
    typedef特殊用法:typedef void* (*fun)(void*)
    localtime、localtime_s、localtime_r的使用
    Git使用(一)
    【转】linux gcc _attribute__((weak)) 简介及作用
    我的技术博客
    .net 4.0(2.0)“检测到有潜在危险的 Request.Form 值”的解决方法
  • 原文地址:https://www.cnblogs.com/1549983239yifeng/p/14277604.html
Copyright © 2011-2022 走看看