Skip to content

Commit e3fbcd9

Browse files
committed
perf: skip VTag render for identical vnode
1 parent e5a6dd9 commit e3fbcd9

File tree

3 files changed

+111
-2
lines changed

3 files changed

+111
-2
lines changed

packages/tiny-react/src/index.test.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,19 @@ describe(render, () => {
6868
},
6969
"ref": undefined,
7070
"type": "tag",
71+
"vnode": {
72+
"child": {
73+
"data": "world",
74+
"type": "text",
75+
},
76+
"key": undefined,
77+
"name": "span",
78+
"props": {
79+
"className": "text-red",
80+
},
81+
"ref": undefined,
82+
"type": "tag",
83+
},
7184
},
7285
],
7386
"parent": [Circular],
@@ -96,6 +109,37 @@ describe(render, () => {
96109
},
97110
"ref": undefined,
98111
"type": "tag",
112+
"vnode": {
113+
"child": {
114+
"children": [
115+
{
116+
"data": "hello",
117+
"type": "text",
118+
},
119+
{
120+
"child": {
121+
"data": "world",
122+
"type": "text",
123+
},
124+
"key": undefined,
125+
"name": "span",
126+
"props": {
127+
"className": "text-red",
128+
},
129+
"ref": undefined,
130+
"type": "tag",
131+
},
132+
],
133+
"type": "fragment",
134+
},
135+
"key": undefined,
136+
"name": "div",
137+
"props": {
138+
"className": "flex items-center gap-2",
139+
},
140+
"ref": undefined,
141+
"type": "tag",
142+
},
99143
}
100144
`);
101145
vnode = h.div({ className: "flex items-center gap-2" }, "reconcile");
@@ -130,6 +174,19 @@ describe(render, () => {
130174
},
131175
"ref": undefined,
132176
"type": "tag",
177+
"vnode": {
178+
"child": {
179+
"data": "reconcile",
180+
"type": "text",
181+
},
182+
"key": undefined,
183+
"name": "div",
184+
"props": {
185+
"className": "flex items-center gap-2",
186+
},
187+
"ref": undefined,
188+
"type": "tag",
189+
},
133190
}
134191
`);
135192
});
@@ -174,6 +231,17 @@ describe(render, () => {
174231
"props": {},
175232
"ref": undefined,
176233
"type": "tag",
234+
"vnode": {
235+
"child": {
236+
"data": "hello",
237+
"type": "text",
238+
},
239+
"key": undefined,
240+
"name": "span",
241+
"props": {},
242+
"ref": undefined,
243+
"type": "tag",
244+
},
177245
},
178246
{
179247
"data": "world",
@@ -199,6 +267,33 @@ describe(render, () => {
199267
"props": {},
200268
"ref": undefined,
201269
"type": "tag",
270+
"vnode": {
271+
"child": {
272+
"children": [
273+
{
274+
"child": {
275+
"data": "hello",
276+
"type": "text",
277+
},
278+
"key": undefined,
279+
"name": "span",
280+
"props": {},
281+
"ref": undefined,
282+
"type": "tag",
283+
},
284+
{
285+
"data": "world",
286+
"type": "text",
287+
},
288+
],
289+
"type": "fragment",
290+
},
291+
"key": undefined,
292+
"name": "div",
293+
"props": {},
294+
"ref": undefined,
295+
"type": "tag",
296+
},
202297
},
203298
"hookContext": HookContext {
204299
"hookCount": 0,

packages/tiny-react/src/reconciler.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,13 @@ function reconcileNode(
4949
bnode = emptyNode();
5050
}
5151
} else if (vnode.type === NODE_TYPE_TAG) {
52-
if (
52+
if (bnode.type === NODE_TYPE_TAG && bnode.vnode === vnode) {
53+
// TODO: is it correct since immutable? but this happens only when used with `memo` wrapper?
54+
// TODO: how about effect or custom component inside? (probably okay since custom component is supposed to re-render itself)
55+
// TODO: can we generalize this to all node types? (probably so)
56+
// TODO: can we also skip `placeChild`? (probably not. ideally for mutating path, child placing should be managed by "caller-side" e.g. Fragment?)
57+
placeChild(bnode.hnode, hparent, preSlot, false);
58+
} else if (
5359
bnode.type === NODE_TYPE_TAG &&
5460
bnode.key === vnode.key &&
5561
bnode.ref === vnode.ref &&
@@ -64,6 +70,7 @@ function reconcileNode(
6470
undefined,
6571
effectManager
6672
);
73+
bnode.vnode = vnode;
6774
placeChild(bnode.hnode, hparent, preSlot, false);
6875
} else {
6976
unmount(bnode);
@@ -75,7 +82,13 @@ function reconcileNode(
7582
undefined,
7683
effectManager
7784
);
78-
bnode = { ...vnode, child, hnode, listeners: new Map() } satisfies BTag;
85+
bnode = {
86+
...vnode,
87+
vnode,
88+
child,
89+
hnode,
90+
listeners: new Map(),
91+
} satisfies BTag;
7992
reconcileTagProps(bnode, vnode.props, {});
8093
placeChild(bnode.hnode, hparent, preSlot, true);
8194
effectManager.refNodes.push(bnode);

packages/tiny-react/src/virtual-dom.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ export type BEmpty = VEmpty & {
7676
};
7777

7878
export type BTag = Omit<VTag, "child"> & {
79+
vnode: VTag;
7980
parent?: BNodeParent;
8081
child: BNode;
8182
hnode: HTag;

0 commit comments

Comments
 (0)