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:
- We collect all leaf vnodes in the wrapper slot's vnode tree
- We inject our content into the leaves like they'd support a default slot. For that we should set
shapeFlag
of the leaves asShapeFlags.ARRAY_CHILDREN | ShapeFlags.ELEMENT
.
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 };
Usage:
<wrap :is-wrapped>
<template #wrapper>
<div class="wrapper">
<div class="inner-wrapper"></div>
</div>
</template>
<p>
I'm wrapped
</p>
</wrap>
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 (2)
Great article!I am trying to convert the above to TS but am struggling to properly type slots in the functional component. Vue official docs seem to have left out typing slots in functional component completely.
Actually, I made it TS compliant... to some degree at least:
However, I found another issue - the above approach doesn't deal with recursive (nested) usage of the conditional wrapper.
Here's a link, which demonstrates the problem.
For context, the current published solution deals very well with conditional wrapping of an element and even accounts for complex nested structures to be used as "wrapper". But, what if the complex nested structure needs to have the conditional wrapping behaviour in an off itself?
In the published solution the below is a single whole wrapper:
What if we need to use
.wrapper
, wrapping the.inner-wrapper
only when a condition B istrue
. Otherwise, we only use.inner-wrapper
to wrap thep
element in the published example?Upon further investigation, seems like the above example doesn't work when trying to use any Vue component as "wrapper"
I'm currently working with VUE/TS, will try to investigate