简体   繁体   English

Vue.js 渲染函数是否允许返回 VNode 数组?

[英]Do Vue.js render functions allow return of an array of VNodes?

I am working on extending a Vue.js frontend application.我正在扩展 Vue.js 前端应用程序。 I am currently inspecting a render function within a functional component.我目前正在检查功能组件中的渲染 function。 After looking over the docs, I had the current understanding that the render function within the functional component will return a single VNode created with CreateElement aka h.查看文档后,我目前的理解是,功能组件中的渲染 function 将返回使用 CreateElement aka h 创建的单个 VNode。 My confusion came when I saw a VNode being returned as an element in an array.当我看到一个 VNode 作为数组中的一个元素返回时,我感到困惑。 I could not find any reference to this syntax in the docs.我在文档中找不到对此语法的任何引用。 Does anyone have any insight?有没有人有任何见解?

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()
      ]
    }
  }
}

It turns out that returning an array of VNodes actually predatesthe scopedSlots update .事实证明,返回 VNode 数组实际上早于scopedSlots更新

I couldn't find it documented anywhere in the docs either, but viathis comment on a Vue GitHub issue by a member of the Vue.js core team (which predates the scopedSlots commit by ~1 year), render() can return an Array of VNodes, which Vue will take and render in order.我也无法在文档中的任何地方找到它,但是通过 Vue.js 核心团队的成员对Vue GitHub 问题评论(这比scopedSlots提交早了约 1 年), render()可以返回一个数组VNodes,Vue 将按顺序获取和渲染。 However , this only works in one, singular case: functional components .但是,这只适用于一种单一的情况: 功能组件

Trying to return an array of VNodes with greater than 1 element in a normal (non-functional, stateful) component results in an error:尝试在正常(非功能性、有状态)组件中返回一个元素大于 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.

But doing this in a functional component, as your example does, works just fine:但是,正如您的示例所做的那样,在功能组件中执行此操作就可以了:

 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>


If you're interested in the why , another member of the Vue core team explained this limitationfurther down in the thread .如果您对其中的原因感兴趣,Vue 核心团队的另一位成员会在帖子中进一步解释此限制。

It basically boils down to assumptions made by the Vue patching and diffing algorithm, with the main one being that "each child component is represented in its parent virtual DOM by a single VNode", which is untrue if multiple root nodes are allowed.它基本上归结为 Vue 修补和差异算法所做的假设,主要是“每个子组件在其父虚拟 DOM 中由单个 VNode 表示”,如果允许多个根节点,这是不正确的。

The increase in complexity to allow this would require large changes to that algorithm which is at the very core of Vue.为了实现这一点,复杂性的增加将需要对 Vue 的核心算法进行大量更改。 This is a big deal, since this algorithm must not only be good at what it does, but also very, very performant.这是一件大事,因为这个算法不仅必须擅长它所做的事情,而且还必须非常非常高效。

Functional components don't need to conform to this restriction, because " they are not represented with a VNode in the parent, since they don't have an instance and don't manage their own virtual DOM "– they're stateless, which makes the restriction unnecessary.功能组件不需要遵守这个限制,因为“它们在父级中没有用 VNode 表示,因为它们没有实例并且不管理自己的虚拟 DOM ”——它们是无状态的,这使限制变得不必要。

It should be noted, however, that this is possible on non-functional components in Vue 3 , as the algorithm in question was reworked to allow it.然而,应该注意的是,这在Vue 3中的非功能组件上可能的,因为所讨论的算法已经过重新设计以允许它。

It seems this was implemented in:似乎这是在以下位置实现的:

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

This was a result of an issue raised in 2018 ( https://github.com/vuejs/vue/issues/8056 ), because this.$scopedSlots.default() returned both a VNode or an array of VNodes depending on the content.这是 2018 年提出的问题( https://github.com/vuejs/vue/issues/8056 )的结果,因为 this.$scopedSlots.default() 根据内容返回 VNode 或 VNode 数组.

The main argument was that this is inconsistent with how regular slots behave in render functions, and means any render function component rendering scoped slots as children needs to type check the result of invoking the slot to decide if it needs to be wrapped in an array主要论点是这与常规插槽在渲染函数中的行为方式不一致,这意味着任何渲染 function 组件将作用域插槽渲染为子级需要类型检查调用插槽的结果以确定是否需要将其包装在数组中

So Evan comments on the issue threadhere , explaining that this.$scopedSlots.default would always return Arrays beginning v2.6 to allow for consistency, but to avoid breaking changes for how $scopedSlots was being used, the update would also allow return of an Array of a single VNode from render functions as well.因此,Evan 在此处对问题线程发表了评论,并解释说 this.$scopedSlots.default 将始终返回 Arrays 从 v2.6 开始以实现一致性,但为了避免对 $scopedSlots 的使用方式进行重大更改,更新还将允许返回来自渲染函数的单个 VNode 数组。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM