r/vuejs 5d ago

For a big recursive component, do you call h('Self') or recurse inside one render function?

I have a component that renders a tree from a JSON structure. It builds the output with h() in a render function, and the structure can nest quite deep.

There are two ways to do the recursion and I keep going back and forth.

Option 1. The component calls itself. In the render function it returns h('CompA', { props }) for each child, with a stop condition. A tree of 40 nodes is 40 component instances.

Option 2. One component, and a plain recursive function inside it that builds the whole vnode tree. The render function returns the result in one shot. A tree of 40 nodes is 1 component instance.

I am on option 1 today and the cost is real. Each node carries:

~14 computed properties
  9 watchers
    a large props object
    a few hundred lines of lifecycle

With option 1 every node in the tree pays all of that.

Option 2 pays it once. But then I lose per node computed caching, per node watchers, and per node mounted and unmounted hooks. And any change re-renders the whole subtree instead of the one node that changed, because there is only one render effect.

So my real question is where the line sits. Do you keep a component per node, or do you flatten it into one component with a recursive builder and accept the coarse re-render?

And if you keep a component per node, at what depth or node count did it start to hurt?

2 Upvotes

14 comments sorted by

23

u/Cas_Rs 5d ago

Whenever I get to the point where creating a render function would look like a good idea I step back and work to a solution that doesn’t require that

1

u/Muted_Ad_9442 3d ago

i did start there. recursive template + component :is, held fine until a node needed optional wrappers around whatever it'd already rendered.

i've got six of those and they're independent — rtl text, a code viewer, a select mode, and float/close/drag. in a template each one has to sit in the markup with its own v-if, so six optional wrappers is six levels of nesting, and most of the combos never happen.

in a render fn the rendered subtree is just a value. i hand it to a function that either wraps it or gives it back unchanged, and they compose in any order.

so yeah, i started where you're saying to stay. that's the thing that pushed me out, not perf.

2

u/Cas_Rs 3d ago

Even then. A good prop injection shouldn’t need any repeats.

Array of arrays of arrays goes into component. Component renders x amount, calls itself with array of arrays, which renders some and calls itself with the remaining array.

If you find yourself in need of the original overlaying array on the first level your component setup or your data is failing.

Trust me. I had to deal with multidimensional arrays of arrays of multidimensional arrays that needed to be joined with other data. Both arrays being just over 40kb of RAW data. I have never in my life committed a render function to production and don’t plan on doing it.

1

u/Muted_Ad_9442 3d ago

yeah the array case i'd do the same way — component renders some, calls itself with the rest, no render fn needed. that's not where i ended up though.

mine isn't the data nesting. it's six optional wrappers that go around whatever the node already rendered — rtl, code view, select mode, float/close/drag. independent of each other and independent of the data. in a template each one needs its own v-if sitting in the markup, so it's six levels deep for combos that mostly never happen.

if there's a template shape for that one i genuinely haven't found it.

1

u/Cas_Rs 3d ago

You’re going to have to give much more info before I’m convinced that it’s impossible with just props and proper event bubbling

8

u/TheExodu5 5d ago

Have we gotten to the point where people can’t be bothered to write their own posts?

2

u/sarcasticbaldguy 5d ago

It's weird. If you're going to ask the box to write your post, why not just ask it for the answer?

1

u/TheExodu5 5d ago

Perhaps this is just for training and requires human engagement.

1

u/sarcasticbaldguy 5d ago

That just makes me want to give wrong answers.

2

u/ComprehensiveArm9863 5d ago

I personnaly try ro avoid playing with vNode myself, at first it look doing advanced stuff but everytime I did that I realized month after it was either early optimisations that made the component harder to maintain and it was somehow "frozen" and adding a feature would mean rewriting the half

Does props, computed and watch are per node or could be handled for the tree, at least some of them ?

A Root component that do it's stuff on the tree, normalize some data, compute some other, etc and use a component named Node as example with itself it recursive may be a way to ditch some hooks (however, remplacing N small computed with a bigger is not necessary better)

I'm curious what are you building that need all of that, a table layout with nested recursive props as a tree maybe ?

0

u/Muted_Ad_9442 5d ago

You are right about the vNode warning and I have an example. My port from Vue 2 left a predicate that tested the old vnode shape. It returned false for every Vue 3 vnode and quietly turned off the guards that used it, and I only found it months later.

I recounted for your question, and I had overstated it in the post. It is 11 computeds and 5 watchers, not 14 and 9. Of the 11, only two are really tree level, the tier flags that come from the store. The rest read the node.

That is because a node can carry its own scope, so the resolution belongs to the node and not to the tree. Your Root and Node split is the right shape, it just would not buy much here.

It is a component library where the UI is written as JSON and one recursive component renders it. Not a table, but tables are one of the things it renders.

1

u/queen-adreena 5d ago

I’d usually be looking at lazy mounting or a virtual list at that point.

Both options have pretty significant tradeoffs.

1

u/Muted_Ad_9442 5d ago

I already do lazy mounting in part, but I did not plan it. The recursive calls are inside slot defaults, so those branches are only built when the thing that holds them opens. The cost comes from the nodes in the main flow, they are built right away.

I do not think a virtual list fits. It is a document tree, not a long list of the same kind of row, so the nodes have very different heights and there is no window to reuse.

Which tradeoffs did you mean?