2

我正在扩展 Vue.js 前端应用程序。我目前正在检查功能组件中的渲染功能。查看文档后,我了解到功能组件中的渲染函数将返回使用 CreateElement aka h 创建的单个 VNode。当我看到一个 VNode 作为数组中的一个元素返回时,我感到困惑。我在文档中找不到对此语法的任何引用。有没有人有任何见解?

export default {
  name: 'OfferModule',
  functional: true,
  props: {
    data: Object,
    placementInt: Number,
    len: Number
  },
  render (h, ctx) {
    let adunitTheme = []
    const isDev = str => (process.env.dev ? str : '')

    const i = parseInt(ctx.props.placementInt)
    const isDevice = ctx.props.data.Component === 'Device'
    const Component = isDevice ? Device : Adunit

    /* device helper classes */
    const adunitWrapper = ctx.props.data.Decorate?.CodeName === 'AdunitWrapper'

    if (!isDevice /* is Adunit */) {
      const offerTypeInt = ctx.props.data.OfferType
      adunitTheme = [
        'adunit-themes',
        `adunit-themes--${type}`.toLowerCase(),
        `adunit-themes--${theme}`.toLowerCase(),
        `adunit-themes--${type}-${theme}`.toLowerCase(),
      ]
    }

    const renderOfferModuleWithoutDisplayAdContainersWithAboveTemplate =
      ctx.props.data.Decorate?.Position === 'AboveAdunit' || false

    const renderOfferModuleWithoutDisplayAdContainers =
      ctx.props.data.Decorate?.RemoveAds /* for adunits */ ||
      ctx.props.data.DeviceData?.RemoveAds /* for devices */ ||
      false

    const getStyle = (className) => {
      try {
        return ctx.parent.$style[className]
      } catch (error) {
        console.log('$test', 'invalid style not found on parent selector')
      }
    }

    const PrimaryOfferModule = (aboveAdunitSlot = {}) =>
      h(Component, {
        props: {
          data: ctx.props.data,
          itemIndex: i,
          adunitTheme: adunitTheme.join('.')
        },
        attrs: {
          class: [
            ...adunitTheme,
            getStyle('product')
          ]
            .join(' ')
            .trim()
        },
        scopedSlots: {
          ...aboveAdunitSlot
        }
      })

    if (renderOfferModuleWithoutDisplayAdContainersWithAboveTemplate) {
      return [
        PrimaryOfferModule({
          aboveAdunit (props) {
            return h({
              data () {
                return ctx.props.data.Decorate
              },
              template: ctx.props.data.Decorate?.Template.replace(
                'v-show="false"',
                ''
              )
            })
          }
        })
      ]
    } else if (renderOfferModuleWithoutDisplayAdContainers) {
      return [PrimaryOfferModule()]
    } else {
      const withAd = i > 0 && i % 1 === 0

      const adWrap = (placement, position, className) => {
        return h(
          'div',
          {
            class: 'm4d-wrap-sticky'
          },
          [
            h(Advertisement, {
              props: {
                placement,
                position: String(position)
              },
              class: getStyle(className)
            })
          ]
        )
      }

      return [
        withAd && adWrap('inline-sticky', i, 'inlineAd'),
        h('div', {
          class: 'm4d-wrap-sticky-adjacent'
        }),

        h(
          'div',
          {
            attrs: {
              id: `inline-device--${String(i)}`
            },
            class: 'inline-device'
          },
          isDev(`inline-device id#: inline-device--${String(i)}`)
        ),

        withAd &&
          i !== ctx.props.len - 1 &&
          h(EcomAdvertisement, {
            props: {
              placement: 'inline-static',
              position: String(i)
            },
            class: getStyle('inlineStaticAd')
          }),

        PrimaryOfferModule()
      ]
    }
  }
}
4

2 回答 2

1

事实证明,返回 VNode 数组实际上早scopedSlots更新

我在文档中的任何地方也找不到它的文档,但是通过Vue.js 核心团队的成员(比提交早了约 1 年)对Vue GitHub 问题的评论,可以返回一个 VNode 数组, Vue 将按顺序呈现和呈现。但是,这只适用于一种单一的情况:功能组件scopedSlotsrender()

尝试在正常(非功能性、有状态)组件中返回一个元素大于 1 的 VNode 数组会导致错误:

Vue.config.productionTip = false;
Vue.config.devtools = false;

Vue.component('render-func-test', {
  render(h, ctx) {
    return [
      h('h1', "I'm a heading"),
      h('h2', "I'm a lesser heading"),
      h('h3', "I'm an even lesser heading")
    ];
  },
});

new Vue({
  el: '#app',
});
<script src="https://unpkg.com/vue@2/dist/vue.js"></script>

<div id="app">
  Test
  <render-func-test></render-func-test>
</div>

[Vue warn]: Multiple root nodes returned from render function. Render function should return a single root node.

但是,正如您的示例所做的那样,在功能组件中执行此操作就可以了:

Vue.config.productionTip = false;
Vue.config.devtools = false;

Vue.component('render-func-test', {
  functional: true, // <--- This is the key
  render(h, ctx) {
    return [
      h('h1', "I'm a heading"),
      h('h2', "I'm a lesser heading"),
      h('h3', "I'm an even lesser heading")
    ];
  },
});

new Vue({
  el: '#app',
});
<script src="https://unpkg.com/vue@2/dist/vue.js"></script>

<div id="app">
  Test
  <render-func-test></render-func-test>
</div>


如果您对其中的原因感兴趣,Vue 核心团队的另一位成员会在帖子中进一步解释此限制。

它基本上归结为 Vue 修补和差异算法所做的假设,主要是“每个子组件在其父虚拟 DOM 中由单个 VNode 表示”,如果允许多个根节点,这是不正确的。

为了实现这一点,复杂性的增加将需要对 Vue 的核心算法进行大量更改。这是一件大事,因为这个算法不仅必须擅长它所做的事情,而且还必须非常非常高效。

功能组件不需要遵守这个限制,因为“它们在父级中没有用 VNode 表示,因为它们没有实例并且不管理自己的虚拟 DOM ”——它们是无状态的,这使限制变得不必要。

然而,应该注意的是,这在Vue 3中的非功能组件上可能的,因为所讨论的算法已经过重新设计以允许它。

于 2021-06-15T18:35:05.027 回答
0

似乎这是在以下位置实现的:

https://github.com/vuejs/vue/commit/c7c13c2a156269d29fd9c9f8f6a3e53a2f2cac3d

这是 2018 年提出的问题 ( https://github.com/vuejs/vue/issues/8056 ) 的结果,因为 this.$scopedSlots.default() 根据内容返回 VNode 或 VNode 数组.

主要论点是这与常规插槽在渲染函数中的行为方式不一致,这意味着任何渲染函数组件将作用域插槽作为子级渲染需要类型检查调用插槽的结果以确定是否需要将其包装在数组中

因此,Evan 在此处对问题线程发表了评论,解释说 this.$scopedSlots.default 将始终返回从 v2.6 开始的数组以保持一致性,但为了避免破坏 $scopedSlots 的使用方式,更新还允许返回来自渲染函数的单个 VNode 数组。

于 2021-06-15T15:37:23.577 回答