Computed计算属性 #
一、createMemo 基础 #
1.1 什么是 Memo #
Memo(记忆化计算)是 Solid 的派生状态原语,用于基于其他 Signal 计算派生值,并自动缓存结果。
jsx
import { createSignal, createMemo } from 'solid-js';
const [count, setCount] = createSignal(0);
// 创建派生状态
const doubled = createMemo(() => count() * 2);
// doubled() 返回 count() * 2 的缓存结果
console.log(doubled()); // 0
setCount(5);
console.log(doubled()); // 10
1.2 Memo 结构 #
text
┌─────────────────────────────────────────────────────────────┐
│ Memo 工作原理 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Signal (count) │
│ │ │
│ ↓ 依赖 │
│ ┌─────────────┐ │
│ │ Memo │ │
│ │ () => count() * 2 │
│ └─────────────┘ │
│ │ │
│ ↓ 缓存结果 │
│ doubled() │
│ │
│ 特点: │
│ 1. 自动追踪依赖 │
│ 2. 缓存计算结果 │
│ 3. 依赖不变时不重新计算 │
│ 4. 只读的 Signal │
│ │
└─────────────────────────────────────────────────────────────┘
二、基本用法 #
2.1 简单计算 #
jsx
function Counter() {
const [count, setCount] = createSignal(0);
// 派生值
const doubled = createMemo(() => count() * 2);
const squared = createMemo(() => count() * count());
return (
<div>
<p>Count: {count()}</p>
<p>Doubled: {doubled()}</p>
<p>Squared: {squared()}</p>
<button onClick={() => setCount(c => c + 1)}>+</button>
</div>
);
}
2.2 字符串处理 #
jsx
function NameDisplay() {
const [firstName, setFirstName] = createSignal('John');
const [lastName, setLastName] = createSignal('Doe');
// 组合字符串
const fullName = createMemo(() => `${firstName()} ${lastName()}`);
// 大写
const upperName = createMemo(() => fullName().toUpperCase());
return (
<div>
<p>Full Name: {fullName()}</p>
<p>Upper: {upperName()}</p>
</div>
);
}
2.3 条件计算 #
jsx
function DiscountCalculator() {
const [price, setPrice] = createSignal(100);
const [isMember, setIsMember] = createSignal(false);
const finalPrice = createMemo(() => {
if (isMember()) {
return price() * 0.9; // 会员 9 折
}
return price();
});
const discount = createMemo(() => {
return price() - finalPrice();
});
return (
<div>
<p>Original: ${price()}</p>
<p>Final: ${finalPrice()}</p>
<p>Discount: ${discount()}</p>
<button onClick={() => setIsMember(m => !m)}>
Toggle Member
</button>
</div>
);
}
三、缓存机制 #
3.1 计算缓存 #
Memo 会缓存计算结果,依赖不变时直接返回缓存:
jsx
function ExpensiveCalculation() {
const [count, setCount] = createSignal(0);
const [other, setOther] = createSignal(0);
let calculationCount = 0;
const expensive = createMemo(() => {
calculationCount++;
console.log('Calculating...', calculationCount);
// 模拟昂贵计算
let result = 0;
for (let i = 0; i < 1000000; i++) {
result += i;
}
return result + count();
});
// 读取多次,只计算一次
console.log(expensive()); // 计算并缓存
console.log(expensive()); // 返回缓存
console.log(expensive()); // 返回缓存
// other 变化不影响 expensive
setOther(1);
console.log(expensive()); // 返回缓存(count 未变)
// count 变化触发重新计算
setCount(1);
console.log(expensive()); // 重新计算
return (
<div>
<p>Result: {expensive()}</p>
<button onClick={() => setCount(c => c + 1)}>Count++</button>
<button onClick={() => setOther(o => o + 1)}>Other++</button>
</div>
);
}
3.2 依赖追踪 #
jsx
function DynamicDeps() {
const [mode, setMode] = createSignal('a');
const [valueA, setValueA] = createSignal(1);
const [valueB, setValueB] = createSignal(2);
const result = createMemo(() => {
// 动态依赖
if (mode() === 'a') {
return valueA(); // 只依赖 mode 和 valueA
} else {
return valueB(); // 只依赖 mode 和 valueB
}
});
createEffect(() => {
console.log('Result:', result());
});
return (
<div>
<p>Mode: {mode()}</p>
<p>Result: {result()}</p>
<button onClick={() => setMode(m => m === 'a' ? 'b' : 'a')}>
Toggle Mode
</button>
</div>
);
}
四、链式 Memo #
4.1 Memo 链 #
Memo 可以依赖其他 Memo:
jsx
function ChainedMemo() {
const [price, setPrice] = createSignal(100);
const [quantity, setQuantity] = createSignal(1);
// 第一层计算
const subtotal = createMemo(() => price() * quantity());
// 第二层计算
const tax = createMemo(() => subtotal() * 0.1);
// 第三层计算
const total = createMemo(() => subtotal() + tax());
return (
<div>
<p>Price: ${price()}</p>
<p>Quantity: {quantity()}</p>
<p>Subtotal: ${subtotal()}</p>
<p>Tax: ${tax()}</p>
<p>Total: ${total()}</p>
</div>
);
}
4.2 计算图 #
text
┌─────────────────────────────────────────────────────────────┐
│ Memo 计算图 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Signal Signal │
│ ┌─────┐ ┌─────────┐ │
│ │price│ │quantity │ │
│ └──┬──┘ └────┬────┘ │
│ │ │ │
│ └───────┬───────┘ │
│ ↓ │
│ ┌─────────┐ │
│ │subtotal │ = price * quantity │
│ └────┬────┘ │
│ │ │
│ ↓ │
│ ┌─────────┐ │
│ │ tax │ = subtotal * 0.1 │
│ └────┬────┘ │
│ │ │
│ ↓ │
│ ┌─────────┐ │
│ │ total │ = subtotal + tax │
│ └─────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
五、高级用法 #
5.1 带初始值的 Memo #
jsx
const [items, setItems] = createSignal([]);
const summary = createMemo(
(prev) => {
const current = items();
if (current.length === 0) return { count: 0, total: 0 };
return {
count: current.length,
total: current.reduce((sum, item) => sum + item.price, 0)
};
},
{ count: 0, total: 0 } // 初始值
);
5.2 自定义 equals #
jsx
const [items, setItems] = createSignal([]);
const summary = createMemo(
(prev) => {
const current = items();
return {
count: current.length,
total: current.reduce((sum, item) => sum + item.price, 0)
};
},
undefined,
{
equals: (prev, next) => {
// 只有 count 和 total 都相等时才认为相等
return prev?.count === next?.count && prev?.total === next?.total;
}
}
);
5.3 延迟计算 #
jsx
function LazyCalculation() {
const [data, setData] = createSignal(null);
// 只有在需要时才计算
const processed = createMemo(() => {
const d = data();
if (!d) return null;
// 复杂计算
return d.map(item => ({
...item,
processed: true
}));
});
return (
<Show when={processed()}>
{(p) => (
<ul>
<For each={p()}>
{(item) => <li>{item.name}</li>}
</For>
</ul>
)}
</Show>
);
}
六、Memo vs 函数 #
6.1 性能对比 #
jsx
function Comparison() {
const [count, setCount] = createSignal(0);
const [other, setOther] = createSignal(0);
// 使用 Memo - 缓存结果
const memoized = createMemo(() => {
console.log('Memo calculated');
return count() * 2;
});
// 使用函数 - 每次调用都计算
const notMemoized = () => {
console.log('Function called');
return count() * 2;
};
createEffect(() => {
// Memo 只在 count 变化时重新计算
console.log('Memo:', memoized());
console.log('Memo again:', memoized()); // 使用缓存
});
return (
<div>
<p>Memo: {memoized()}</p>
<p>Function: {notMemoized()}</p>
</div>
);
}
6.2 使用场景 #
| 场景 | 推荐使用 |
|---|---|
| 简单计算 | 函数 |
| 昂贵计算 | Memo |
| 需要缓存 | Memo |
| 需要追踪依赖 | Memo |
| 动态参数 | 函数 |
七、实际应用 #
7.1 过滤和排序 #
jsx
function TodoList() {
const [todos, setTodos] = createSignal([
{ id: 1, text: 'Learn Solid', done: false },
{ id: 2, text: 'Build app', done: true },
{ id: 3, text: 'Deploy', done: false }
]);
const [filter, setFilter] = createSignal('all');
const [sortBy, setSortBy] = createSignal('id');
const filteredTodos = createMemo(() => {
let result = todos();
// 过滤
switch (filter()) {
case 'active':
result = result.filter(t => !t.done);
break;
case 'completed':
result = result.filter(t => t.done);
break;
}
// 排序
result = [...result].sort((a, b) => {
if (sortBy() === 'id') return a.id - b.id;
return a.text.localeCompare(b.text);
});
return result;
});
return (
<div>
<select value={filter()} onChange={(e) => setFilter(e.target.value)}>
<option value="all">All</option>
<option value="active">Active</option>
<option value="completed">Completed</option>
</select>
<ul>
<For each={filteredTodos()}>
{(todo) => <li>{todo.text}</li>}
</For>
</ul>
</div>
);
}
7.2 购物车计算 #
jsx
function ShoppingCart() {
const [items, setItems] = createSignal([]);
const subtotal = createMemo(() =>
items().reduce((sum, item) => sum + item.price * item.quantity, 0)
);
const itemCount = createMemo(() =>
items().reduce((sum, item) => sum + item.quantity, 0)
);
const discount = createMemo(() => {
const s = subtotal();
if (s > 100) return s * 0.1;
if (s > 50) return s * 0.05;
return 0;
});
const tax = createMemo(() => (subtotal() - discount()) * 0.08);
const total = createMemo(() => subtotal() - discount() + tax());
return (
<div>
<p>Items: {itemCount()}</p>
<p>Subtotal: ${subtotal().toFixed(2)}</p>
<p>Discount: -${discount().toFixed(2)}</p>
<p>Tax: ${tax().toFixed(2)}</p>
<p>Total: ${total().toFixed(2)}</p>
</div>
);
}
7.3 表单验证 #
jsx
function FormValidation() {
const [email, setEmail] = createSignal('');
const [password, setPassword] = createSignal('');
const emailError = createMemo(() => {
const e = email();
if (!e) return 'Email is required';
if (!e.includes('@')) return 'Invalid email format';
return null;
});
const passwordError = createMemo(() => {
const p = password();
if (!p) return 'Password is required';
if (p.length < 8) return 'Password must be at least 8 characters';
return null;
});
const isValid = createMemo(() =>
!emailError() && !passwordError()
);
const handleSubmit = (e) => {
e.preventDefault();
if (isValid()) {
console.log('Submit:', { email: email(), password: password() });
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<input
type="email"
value={email()}
onInput={(e) => setEmail(e.target.value)}
/>
<Show when={emailError()}>
<span class="error">{emailError()}</span>
</Show>
</div>
<div>
<input
type="password"
value={password()}
onInput={(e) => setPassword(e.target.value)}
/>
<Show when={passwordError()}>
<span class="error">{passwordError()}</span>
</Show>
</div>
<button type="submit" disabled={!isValid()}>
Submit
</button>
</form>
);
}
八、总结 #
8.1 Memo 核心 API #
| API | 说明 |
|---|---|
createMemo(fn) |
创建 Memo |
createMemo(fn, initialValue) |
带初始值 |
createMemo(fn, initialValue, options) |
带选项 |
8.2 Memo 特点 #
- 只读:不能直接设置值
- 缓存:依赖不变时返回缓存
- 自动追踪:自动收集依赖
- 惰性求值:只在被读取时计算
8.3 最佳实践 #
- 昂贵计算使用 Memo:避免重复计算
- 简单计算用函数:减少开销
- 合理链式:避免过深的依赖链
- 注意依赖:理解动态依赖行为
- 自定义 equals:优化更新判断
最后更新:2026-03-28