How does Vue pass the implementation of Slot across components

  • 2021-10-13 06:41:26
  • OfStack

In the development process, I encountered such a problem, how to pass slots across components. Because in the process of developing tree-like components, slots need to be passed externally to the root node of the tree, and then passed through the root node to each leaf node in turn. So how do you pass the Slot of the root node to the subcomponents?
During the development process, we hope to redefine the structure of leaf nodes as follows:


<data-tree>
 <template v-slot:node="data">
   <div>{{data.title}} - {{data.text}}</div>
  </template>
</data-tree>

Then how to pass Slot within components is a problem.

Nested transfer

Through a fixed level of component structure, you can write directly < v-slot ... > To transfer the corresponding Slot element, so as to realize the transfer of 1 layer and 1 layer.


<data-tree>
 <data-tree-item>
   <template :node="data">
      <slot :data="data"> xxx </slot>
    </template>
  </data-tree-item>
</data-tree>

slot can be passed layer by layer by creating slot in the outer layer, but this can be cumbersome if there are too many nested layers.

Render

Another scheme is to display through Render function, which can access slot element of current component through $slots, and then pass slot to the next layer when creating new component through Render function.


h('data-tree-item',{
 scopedSlots: {
    node: props => this.$slots.node(props)
  },
})

In this way, the corresponding Slot can be received through the Render child element, and the transfer is also realized.

Dynamic component

Another way is through dynamic components, which is also considered as a recommended implementation way. Instead of passing Slot, the child nodes actively obtain the Slot object of the root node, and then render it directly in UI.

To do this, we need to create a component to render the corresponding Slot object.

First, you need to get the root node:


const rootComponentName = 'data-tree'
/**
 *  Get parent component 
 */
const getRootComponent = (
  component: ComponentInternalInstance | null
): ComponentInternalInstance | undefined => {
  if (component && component.type.name === rootComponentName) {
    return component
  }

  if (component && component.parent) {
    const parent = component.parent
    return getRootComponent(parent)
  }
}

By recursion, we can get the corresponding parent node, so that we can expose Slot as Data


setup(props) {
    //  Get the root node 
    const dataTree = getRootComponent(getCurrentInstance())
    const parentSlots = dataTree?.slots
    const nodeTemplate = parentSlots?.node as any
    return {
      nodeTemplate
    }
  }

At this point, we need a component to render the exposed Slot:


components: {
    TemplateContainer: {
      functional: true,
      props: {
        template: {
          type: Function
        },
        data: {
          type: Object
        }
      },
      render: (props, ctx) => h('div', [props.template(props.data)])
    }
  }

Ok, now that everything is ready, you can realize the display of UI:


 <template-container
          v-if="nodeTemplate"
          :template="nodeTemplate"
          :data="node">
</template-container>
<template v-else>
    {{ node.label }}
</template>

In this way, we implemented the delivery of Slot similar to that defined below, and also solved the problem of passing Slot across components.


<slot :data="node" name="node">
 {{ node.label }}
</slot>

This paper uses the example of Vue 3, and Vue 2 is the same concept. In Vue 3, besides using getRootComponent to query the heel node, Provide/Inject can also be used to actively pass Slot to the child node.


Related articles: