Instantly share code, notes, and snippets.

Embed
What would you like to do?

2.6 Internal Change: Scoped Slot Function Return Value

This change only affects render function users. If you only use templates, you can ignore this change.

The Problem

In render functions, scoped slots are exposed on this.$scopedSlots as functions. Up until now, calling a scoped slot function can return a single VNode or an Array of VNodes based on what the parent component is passing in. For example, given this component:

const Child = {
  render(h) {
    const slotContent = this.$scopedSlots.default({})
    console.log(slotContent)
    // ...
  }
}

The value of slotContent can change between a single vnode vs. an array of vnodes depending on how it is used:

<child>
  <!-- slotContent is a single VNode for the div -->
  <div slot-scope="slotProps"></div>
</child>

<child>
  <!-- slotContent is an Array of multiple div VNodes -->
  <template slot-scope="slotProps">
    <div></div>
    <div></div>
  </template>
</child>

This makes it easy to write code that doesn't properly cover all possible cases, for example:

const Child = {
  render(h) {
    // this works if the slot returns a single node
    // but will break if it returns an Array, since stateful components can
    // only have a single root node.
    return this.$scopedSlots.default({})
  }
}

To properly handle edge cases, we would have to always check whether the returned value is an Array or not. This can become quite cumbersome. This behavior is also inconsistent with normal slots (exposed via this.$slots) where a slot is either an Array of VNodes or undefined.

The Fix

In 2.6, we have made functions exposed on this.$scopedSlots to always return either an Array of VNodes or undefined. If you are just using the returned value as children for other VNodes, this change does not affect you since VNode children can handle nested Arrays.

Potential Side Effects

This is technically a breaking change for two cases:

  1. Component returning scoped slot return value as root node:

    const Child = {
      render(h) {
        return this.$scopedSlots.default({})
      }
    }

    To compensate for this, we have adjusted stateful components to also accept an Array containing only one single VNode. This means existing code like the above that worked will continue to work. However, it is still recommended to check for the case of multiple VNodes, as it will still break the above usage.

  2. Code that does not check for possible Array values:

    const Child = {
      render(h) {
        const slotContent = this.$scopedSlots.default({})
        // access values on `slotContent` assuming it's always a single VNode
        console.log(slotContent.data.class)
      }
    }

    This should be uncommon in practice, and already breaks if the slot ends up receiving multiple VNodes. It should be updated to properly handle Array return values regardless of whether you are upgrading to 2.6 or not.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment