From 4c1f7cf6416cddb5239c6a2803004b30d171b929 Mon Sep 17 00:00:00 2001 From: Li Kai Date: Thu, 18 Sep 2025 04:32:03 +0800 Subject: [PATCH 1/2] test: Add more benchmarks for splitProps --- packages/solid/test/component.bench.ts | 46 ++++++++++++++++++++------ 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/packages/solid/test/component.bench.ts b/packages/solid/test/component.bench.ts index fd7fd00c..dd29d172 100644 --- a/packages/solid/test/component.bench.ts +++ b/packages/solid/test/component.bench.ts @@ -1,4 +1,5 @@ import { mergeProps, splitProps } from "../src/index.js"; +import { $PROXY } from "../src/reactive/signal.js"; import { bench, describe } from "vitest"; const staticDesc = { @@ -81,7 +82,26 @@ type SplitProps = (...args: any[]) => Record[]; const generator = { static: (amount: number) => createObject("static", amount, () => staticDesc), signal: (amount: number) => createObject("signal", amount, () => signalDesc), - mixed: (amount: number) => createObject("mixed", amount, v => (v % 2 ? staticDesc : signalDesc)) + mixed: (amount: number) => createObject("mixed", amount, v => (v % 2 ? staticDesc : signalDesc)), + store: (amount: number) => { + const data = createObject("store", amount, () => staticDesc); + // Create a proxy that mimics store behavior with $PROXY symbol + const proxy = new Proxy(data, { + get(target, property) { + if (property === $PROXY) return proxy; + return target[property]; + }, + has(target, property) { + if (property === $PROXY) return true; + return property in target; + }, + ownKeys(target) { + return Reflect.ownKeys(target); + } + }); + Object.defineProperty(data, $PROXY, { value: proxy, configurable: true }); + return proxy; + } } as const; const filter = new RegExp(process.env.FILTER || ".+"); @@ -98,14 +118,16 @@ const splitPropsTests = createTest({ generator, inputs: g => ({ "(5, 1)": [g(5), keys(g(1))], - "(5, 1, 2)": [g(5), keys(g(1)), keys(g(2))], + "(2, 15)": [g(2), keys(g(15))], + "(2, 100)": [g(2), keys(g(100))], "(0, 15)": [g(0), keys(g(15))], - "(0, 3, 2)": [g(0), keys(g(3)), keys(g(2))], - "(0, 100)": [g(0), keys(g(100))], - "(0, 100, 3, 2)": [g(0), keys(g(100)), keys(g(3)), keys(g(2))], + "(25, 5)": [g(25), keys(g(5))], "(25, 100)": [g(25), keys(g(100))], "(50, 100)": [g(50), keys(g(100))], - "(100, 25)": [g(100), keys(g(25))] + "(100, 25)": [g(100), keys(g(25))], + "(5, 1, 2)": [g(5), keys(g(1)), keys(g(2))], + "(2, 3, 2)": [g(2), keys(g(3)), keys(g(2))], + "(2, 100, 3, 2)": [g(2), keys(g(100)), keys(g(3)), keys(g(2))] }) }); @@ -121,14 +143,16 @@ const mergePropsTest = createTest({ generator, inputs: g => ({ "(5, 1)": [g(5), g(1)], - "(5, 1, 2)": [g(5), g(1), g(2)], + "(2, 15)": [g(2), g(15)], + "(2, 100)": [g(2), g(100)], "(0, 15)": [g(0), g(15)], - "(0, 3, 2)": [g(0), g(3), g(2)], - "(0, 100)": [g(0), g(100)], - "(0, 100, 3, 2)": [g(0), g(100), g(3), g(2)], + "(25, 5)": [g(25), g(5)], "(25, 100)": [g(25), g(100)], "(50, 100)": [g(50), g(100)], - "(100, 25)": [g(100), g(25)] + "(100, 25)": [g(100), g(25)], + "(5, 1, 2)": [g(5), g(1), g(2)], + "(2, 3, 2)": [g(2), g(3), g(2)], + "(2, 100, 3, 2)": [g(2), g(100), g(3), g(2)] }) }); From 8bbc610a226a4ad5ab454063b40c75f1add0affd Mon Sep 17 00:00:00 2001 From: Li Kai Date: Thu, 18 Sep 2025 04:32:42 +0800 Subject: [PATCH 2/2] perf: Optimize `splitProps` function --- .changeset/thirty-eggs-learn.md | 5 +++ packages/solid/src/render/component.ts | 47 +++++++++++++------------- 2 files changed, 29 insertions(+), 23 deletions(-) create mode 100644 .changeset/thirty-eggs-learn.md diff --git a/.changeset/thirty-eggs-learn.md b/.changeset/thirty-eggs-learn.md new file mode 100644 index 00000000..8e98fd3e --- /dev/null +++ b/.changeset/thirty-eggs-learn.md @@ -0,0 +1,5 @@ +--- +"solid-js": patch +--- + +Improve `splitProps` performance diff --git a/packages/solid/src/render/component.ts b/packages/solid/src/render/component.ts index 84d855d6..afd807ef 100644 --- a/packages/solid/src/render/component.ts +++ b/packages/solid/src/render/component.ts @@ -288,8 +288,10 @@ export function splitProps< T extends Record, K extends [readonly (keyof T)[], ...(readonly (keyof T)[])[]] >(props: T, ...keys: K): SplitProps { + const len = keys.length; + if (SUPPORTS_PROXY && $PROXY in props) { - const blocked = new Set(keys.length > 1 ? keys.flat() : keys[0]); + const blocked = len > 1 ? keys.flat() : keys[0]; const res = keys.map(k => { return new Proxy( { @@ -310,13 +312,13 @@ export function splitProps< new Proxy( { get(property) { - return blocked.has(property) ? undefined : props[property as any]; + return blocked.includes(property) ? undefined : props[property as any]; }, has(property) { - return blocked.has(property) ? false : property in props; + return blocked.includes(property) ? false : property in props; }, keys() { - return Object.keys(props).filter(k => !blocked.has(k)); + return Object.keys(props).filter(k => !blocked.includes(k)); } }, propTraps @@ -324,31 +326,30 @@ export function splitProps< ); return res as SplitProps; } - const otherObject: Record = {}; - const objects: Record[] = keys.map(() => ({})); + const objects: Record[] = []; + for (let i = 0; i <= len; i++) { + objects[i] = {}; + } for (const propName of Object.getOwnPropertyNames(props)) { + let keyIndex = len; + + for (let i = 0; i < keys.length; i++) { + if (keys[i].includes(propName)) { + keyIndex = i; + break; + } + } + const desc = Object.getOwnPropertyDescriptor(props, propName)!; const isDefaultDesc = !desc.get && !desc.set && desc.enumerable && desc.writable && desc.configurable; - let blocked = false; - let objectIndex = 0; - for (const k of keys) { - if (k.includes(propName)) { - blocked = true; - isDefaultDesc - ? (objects[objectIndex][propName] = desc.value) - : Object.defineProperty(objects[objectIndex], propName, desc); - } - ++objectIndex; - } - if (!blocked) { - isDefaultDesc - ? (otherObject[propName] = desc.value) - : Object.defineProperty(otherObject, propName, desc); - } + isDefaultDesc + ? (objects[keyIndex][propName] = desc.value) + : Object.defineProperty(objects[keyIndex], propName, desc); } - return [...objects, otherObject] as any; + + return objects as any; } // lazy load a function component asynchronously