1. 函数组件:只运行一次
SolidJS 中的函数组件与 React 最大的区别是:组件函数只在初始化时执行一次,之后不会因为状态变化而重新执行(re-render)。
当 Signal 变化时,SolidJS 只会重新执行对应的 Effect 或 Memo,不会重新调用组件函数。这意味着:
- 不需要担心"重复创建副作用"的问题
- 不需要
useMemo、useCallback来"缓存"值(因为根本不会重新执行) - 在组件顶层直接写任何逻辑都是安全的
function ExpensiveComponent() {
// 这行只执行一次!不管后续 Signal 如何变化
const config = buildExpensiveConfig(); // 安全,不需要 useMemo
const [count, setCount] = createSignal(0);
return <p>{count()}</p>;
// count() 变化时,只更新这个文本节点,不重跑组件函数
}
2. Props:响应式代理
SolidJS 的 props 是一个响应式代理对象(Proxy),而不是普通的 JavaScript 对象。通过 props.xxx 访问属性时会触发响应式追踪——当父组件传入的值变化时,子组件中使用该 prop 的地方会自动更新。
正确访问 props
interface ButtonProps {
label: string;
color?: string;
onClick: () => void;
}
function Button(props: ButtonProps) {
// ✅ 正确:在 JSX 或 Effect 中直接访问 props.xxx
return (
<button
style={{ color: props.color }}
onClick={props.onClick}
>
{props.label}
</button>
);
}
function BadButton(props: ButtonProps) {
// ❌ 错误:解构 props 会失去响应性!
const { label, color } = props; // label 现在只是一个普通字符串
return <button style={{ color }}>{label}</button>;
// 当父组件更新 label 时,这里不会响应式更新!
}
SolidJS 最重要的规则:永远不要解构 props。这是从 React 迁移到 SolidJS 最容易犯的错误。如果你在函数参数处写 function Btn({ label }),label 会立即变成一个普通值,失去响应性。
3. children prop
SolidJS 的 children 与 React 类似,但需要用 children helper 函数来处理(因为 children 本身也是响应式的):
import { children } from "solid-js";
import type { JSX } from "solid-js";
interface CardProps {
title: string;
children: JSX.Element;
}
function Card(props: CardProps) {
// children() helper 正确处理响应式子节点
const c = children(() => props.children);
return (
<div class="card">
<h3>{props.title}</h3>
<div class="card-body">{c()}</div>
</div>
);
}
// 使用
<Card title="我的卡片">
<p>这是卡片内容</p>
</Card>
4. mergeProps — 安全合并默认值
mergeProps 以响应式安全的方式合并多个 props 对象,常用于设置默认值:
import { mergeProps } from "solid-js";
interface ButtonProps {
label?: string;
variant?: "primary" | "secondary";
disabled?: boolean;
}
function Button(props: ButtonProps) {
// 安全地设置默认值,保持响应性
const merged = mergeProps(
{ label: "按钮", variant: "primary", disabled: false },
props
);
return (
<button
class={`btn btn-${merged.variant}`}
disabled={merged.disabled}
>
{merged.label}
</button>
);
}
// 注意:不能用这种方式——这会破坏响应性!
// const label = props.label ?? "按钮"; // ❌ 只读一次
5. splitProps — 分离 props
splitProps 将 props 按照指定 key 分成两个部分,常用于将"自身 props"和"透传 props"分离:
import { splitProps } from "solid-js";
interface InputProps {
label: string;
error?: string;
// 还有很多原生 input 属性...
[key: string]: any;
}
function FormInput(props: InputProps) {
// 分离自定义 props 和原生 input props
const [local, inputProps] = splitProps(props, ["label", "error"]);
return (
<div>
<label>{local.label}</label>
{/* inputProps 包含所有其他属性,如 type, placeholder, value 等 */}
<input {...inputProps} />
{local.error && <span class="error">{local.error}</span>}
</div>
);
}
// 使用
<FormInput
label="用户名"
error="必填项"
type="text"
placeholder="请输入用户名"
/>
6. 组件生命周期:onMount 与 onCleanup
SolidJS 通过两个函数提供基本的生命周期钩子:
-
onMount(fn)组件挂载到 DOM 后执行一次。用于初始化第三方库、获取 DOM 元素尺寸、设置订阅等。等价于 React 的
useEffect(() => {...}, [])。 -
onCleanup(fn)组件销毁时(或 Effect 重新执行前)调用。用于清理定时器、取消网络请求、移除事件监听器。可以在任意 createEffect、createMemo 或组件顶层调用。
import { onMount, onCleanup, createSignal } from "solid-js";
function CanvasChart() {
let canvasRef: HTMLCanvasElement;
const [data, setData] = createSignal([]);
// 挂载后初始化 canvas
onMount(() => {
const ctx = canvasRef.getContext("2d");
drawChart(ctx, data());
console.log("Canvas mounted, size:", canvasRef.width);
});
// WebSocket 连接示例
onMount(() => {
const ws = new WebSocket("wss://api.example.com");
ws.onmessage = (e) => setData(JSON.parse(e.data));
// 组件卸载时自动关闭连接
onCleanup(() => ws.close());
});
return <canvas ref={canvasRef!} width="400" height="300" />;
}
7. 实战:可复用卡片组件
import { splitProps, mergeProps, children } from "solid-js";
import type { JSX } from "solid-js";
interface CardProps {
title?: string;
variant?: "default" | "primary" | "danger";
collapsible?: boolean;
children: JSX.Element;
class?: string;
}
function Card(props: CardProps) {
const merged = mergeProps({ variant: "default", collapsible: false }, props);
const [local, rest] = splitProps(merged, ["title", "variant", "collapsible", "children"]);
const c = children(() => local.children);
const [open, setOpen] = createSignal(true);
return (
<div class={`card card-${local.variant} ${rest.class ?? ""}`}>
{local.title && (
<div class="card-header" onClick={() => local.collapsible && setOpen(v => !v)}>
<h3>{local.title}</h3>
{local.collapsible && <span>{open() ? "▲" : "▼"}</span>}
</div>
)}
{open() && <div class="card-body">{c()}</div>}
</div>
);
}
本章小结:SolidJS 组件只执行一次,props 是响应式代理不能解构,mergeProps 安全合并默认值,splitProps 分离属性,onMount/onCleanup 提供生命周期控制。掌握这些规则,你就避免了 80% 的 SolidJS 新手错误。