DEV Community

Alexander Nenashev
Alexander Nenashev

Posted on • Updated on

Conditional wrap component in Vue 3 pt.3

Previously we observed 2 options to create a conditional wrap component in Vue 3. Now it's time for the most complex one which exploits vnodes in a deeper way.

On the client side it's pretty ordinary with the wrapper provided in a wrapper slot and the wrapped content in the default one. But we do some magic inside the wrap component:

  1. We collect all leaf vnodes in the wrapper slot's vnode tree
  2. We inject our content into the leaves like they'd support a default slot. For that we should set shapeFlag of the leaves as ShapeFlags.ARRAY_CHILDREN | ShapeFlags.ELEMENT.

See on Vue SFC Playground

import { ShapeFlags } from "@vue/shared";
import { cloneVNode} from "vue";

const findLeaves = (vnode, cb) => 
  vnode.children?.length ? 
    vnode.children.forEach(vnode => findLeaves(vnode, cb)) : 
    cb(vnode)
;

export default function Wrap({isWrapped}, {slots}){

  if (!isWrapped) return slots.default();

  const wrapper = slots.wrapper().map(vnode => cloneVNode(vnode));
  findLeaves({children: wrapper}, vnode => {
    vnode.shapeFlag = ShapeFlags.ARRAY_CHILDREN | ShapeFlags.ELEMENT;
    (vnode.children ??= []).push(...slots.default());
  });

  return wrapper;
}

Wrap.props = { isWrapped: Boolean };  
Enter fullscreen mode Exit fullscreen mode

Usage:

  <wrap :is-wrapped>
    <template #wrapper>
      <div class="wrapper">
        <div class="inner-wrapper"></div>
      </div>
    </template>
    <p>
      I'm wrapped
    </p>
  </wrap>
Enter fullscreen mode Exit fullscreen mode

As you see the DX is pretty good, with full ability to define our wrapper with any nesting fully inside a template.

Now you have a choice to select a wrap component from 3 options or probably combine them into one ultimate component.

Top comments (0)