Skip to content

Commit 3114302

Browse files
authored
Optimize splitProps (#2545)
* test: Add more benchmarks for splitProps * perf: Optimize `splitProps` function
1 parent 2270ae9 commit 3114302

File tree

3 files changed

+64
-34
lines changed

3 files changed

+64
-34
lines changed

.changeset/thirty-eggs-learn.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"solid-js": patch
3+
---
4+
5+
Improve `splitProps` performance

packages/solid/src/render/component.ts

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -288,8 +288,10 @@ export function splitProps<
288288
T extends Record<any, any>,
289289
K extends [readonly (keyof T)[], ...(readonly (keyof T)[])[]]
290290
>(props: T, ...keys: K): SplitProps<T, K> {
291+
const len = keys.length;
292+
291293
if (SUPPORTS_PROXY && $PROXY in props) {
292-
const blocked = new Set<keyof T>(keys.length > 1 ? keys.flat() : keys[0]);
294+
const blocked = len > 1 ? keys.flat() : keys[0];
293295
const res = keys.map(k => {
294296
return new Proxy(
295297
{
@@ -310,45 +312,44 @@ export function splitProps<
310312
new Proxy(
311313
{
312314
get(property) {
313-
return blocked.has(property) ? undefined : props[property as any];
315+
return blocked.includes(property) ? undefined : props[property as any];
314316
},
315317
has(property) {
316-
return blocked.has(property) ? false : property in props;
318+
return blocked.includes(property) ? false : property in props;
317319
},
318320
keys() {
319-
return Object.keys(props).filter(k => !blocked.has(k));
321+
return Object.keys(props).filter(k => !blocked.includes(k));
320322
}
321323
},
322324
propTraps
323325
)
324326
);
325327
return res as SplitProps<T, K>;
326328
}
327-
const otherObject: Record<string, any> = {};
328-
const objects: Record<string, any>[] = keys.map(() => ({}));
329+
const objects: Record<string, any>[] = [];
330+
for (let i = 0; i <= len; i++) {
331+
objects[i] = {};
332+
}
329333

330334
for (const propName of Object.getOwnPropertyNames(props)) {
335+
let keyIndex = len;
336+
337+
for (let i = 0; i < keys.length; i++) {
338+
if (keys[i].includes(propName)) {
339+
keyIndex = i;
340+
break;
341+
}
342+
}
343+
331344
const desc = Object.getOwnPropertyDescriptor(props, propName)!;
332345
const isDefaultDesc =
333346
!desc.get && !desc.set && desc.enumerable && desc.writable && desc.configurable;
334-
let blocked = false;
335-
let objectIndex = 0;
336-
for (const k of keys) {
337-
if (k.includes(propName)) {
338-
blocked = true;
339-
isDefaultDesc
340-
? (objects[objectIndex][propName] = desc.value)
341-
: Object.defineProperty(objects[objectIndex], propName, desc);
342-
}
343-
++objectIndex;
344-
}
345-
if (!blocked) {
346-
isDefaultDesc
347-
? (otherObject[propName] = desc.value)
348-
: Object.defineProperty(otherObject, propName, desc);
349-
}
347+
isDefaultDesc
348+
? (objects[keyIndex][propName] = desc.value)
349+
: Object.defineProperty(objects[keyIndex], propName, desc);
350350
}
351-
return [...objects, otherObject] as any;
351+
352+
return objects as any;
352353
}
353354

354355
// lazy load a function component asynchronously

packages/solid/test/component.bench.ts

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { mergeProps, splitProps } from "../src/index.js";
2+
import { $PROXY } from "../src/reactive/signal.js";
23
import { bench, describe } from "vitest";
34

45
const staticDesc = {
@@ -81,7 +82,26 @@ type SplitProps = (...args: any[]) => Record<string, any>[];
8182
const generator = {
8283
static: (amount: number) => createObject("static", amount, () => staticDesc),
8384
signal: (amount: number) => createObject("signal", amount, () => signalDesc),
84-
mixed: (amount: number) => createObject("mixed", amount, v => (v % 2 ? staticDesc : signalDesc))
85+
mixed: (amount: number) => createObject("mixed", amount, v => (v % 2 ? staticDesc : signalDesc)),
86+
store: (amount: number) => {
87+
const data = createObject("store", amount, () => staticDesc);
88+
// Create a proxy that mimics store behavior with $PROXY symbol
89+
const proxy = new Proxy(data, {
90+
get(target, property) {
91+
if (property === $PROXY) return proxy;
92+
return target[property];
93+
},
94+
has(target, property) {
95+
if (property === $PROXY) return true;
96+
return property in target;
97+
},
98+
ownKeys(target) {
99+
return Reflect.ownKeys(target);
100+
}
101+
});
102+
Object.defineProperty(data, $PROXY, { value: proxy, configurable: true });
103+
return proxy;
104+
}
85105
} as const;
86106

87107
const filter = new RegExp(process.env.FILTER || ".+");
@@ -98,14 +118,16 @@ const splitPropsTests = createTest({
98118
generator,
99119
inputs: g => ({
100120
"(5, 1)": [g(5), keys(g(1))],
101-
"(5, 1, 2)": [g(5), keys(g(1)), keys(g(2))],
121+
"(2, 15)": [g(2), keys(g(15))],
122+
"(2, 100)": [g(2), keys(g(100))],
102123
"(0, 15)": [g(0), keys(g(15))],
103-
"(0, 3, 2)": [g(0), keys(g(3)), keys(g(2))],
104-
"(0, 100)": [g(0), keys(g(100))],
105-
"(0, 100, 3, 2)": [g(0), keys(g(100)), keys(g(3)), keys(g(2))],
124+
"(25, 5)": [g(25), keys(g(5))],
106125
"(25, 100)": [g(25), keys(g(100))],
107126
"(50, 100)": [g(50), keys(g(100))],
108-
"(100, 25)": [g(100), keys(g(25))]
127+
"(100, 25)": [g(100), keys(g(25))],
128+
"(5, 1, 2)": [g(5), keys(g(1)), keys(g(2))],
129+
"(2, 3, 2)": [g(2), keys(g(3)), keys(g(2))],
130+
"(2, 100, 3, 2)": [g(2), keys(g(100)), keys(g(3)), keys(g(2))]
109131
})
110132
});
111133

@@ -121,14 +143,16 @@ const mergePropsTest = createTest({
121143
generator,
122144
inputs: g => ({
123145
"(5, 1)": [g(5), g(1)],
124-
"(5, 1, 2)": [g(5), g(1), g(2)],
146+
"(2, 15)": [g(2), g(15)],
147+
"(2, 100)": [g(2), g(100)],
125148
"(0, 15)": [g(0), g(15)],
126-
"(0, 3, 2)": [g(0), g(3), g(2)],
127-
"(0, 100)": [g(0), g(100)],
128-
"(0, 100, 3, 2)": [g(0), g(100), g(3), g(2)],
149+
"(25, 5)": [g(25), g(5)],
129150
"(25, 100)": [g(25), g(100)],
130151
"(50, 100)": [g(50), g(100)],
131-
"(100, 25)": [g(100), g(25)]
152+
"(100, 25)": [g(100), g(25)],
153+
"(5, 1, 2)": [g(5), g(1), g(2)],
154+
"(2, 3, 2)": [g(2), g(3), g(2)],
155+
"(2, 100, 3, 2)": [g(2), g(100), g(3), g(2)]
132156
})
133157
});
134158

0 commit comments

Comments
 (0)