solidjs-patterns
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseWhy this skill exists
该技能的存在意义
OpenWork’s UI is SolidJS: it updates via signals, not React-style rerenders.
Most “UI stuck” bugs are actually state coupling bugs (e.g. one global disabling an unrelated action), not rerender issues.
busy()This skill captures the patterns we want to consistently use in OpenWork.
OpenWork的UI基于SolidJS构建:它通过signals(信号)进行更新,而非React风格的重新渲染。
大多数“UI卡住”的bug实际上是状态耦合问题(例如,一个全局的状态禁用了无关的操作),而非重新渲染问题。
busy()本技能记录了我们希望在OpenWork中统一遵循的开发模式。
Core rules
核心规则
- Prefer fine-grained signals over shared global flags.
- Keep async actions scoped (each action gets its own state).
pending - Derive UI state via instead of duplicating booleans.
createMemo() - Avoid mutating arrays/objects stored in signals; always create new values.
- 优先使用细粒度signals,而非共享全局标记。
- 保持异步操作的作用域隔离(每个操作拥有独立的状态)。
pending - 通过推导UI状态,而非重复定义布尔值。
createMemo() - 不要修改存储在signals中的数组/对象;始终创建新值。
Scoped async actions (recommended)
作用域隔离的异步操作(推荐方案)
When an operation can overlap with others (permissions, installs, background refresh), don’t reuse a global .
busy()Use a dedicated signal per action:
ts
const [replying, setReplying] = createSignal(false);
async function respond() {
if (replying()) return;
setReplying(true);
try {
await doTheThing();
} finally {
setReplying(false);
}
}当某个操作可能与其他操作(权限验证、安装、后台刷新)重叠时,不要复用全局的状态。
busy()应为每个操作使用独立的signal:
ts
const [replying, setReplying] = createSignal(false);
async function respond() {
if (replying()) return;
setReplying(true);
try {
await doTheThing();
} finally {
setReplying(false);
}
}Why
原因说明
A single boolean creates deadlocks:
busy()- Long-running task sets
busy(true) - A permission prompt appears and its buttons are disabled by
busy() - The task can’t continue until permission is answered
- The user can’t answer because buttons are disabled
Fix: permission UI must be disabled only by a permission-specific pending state.
单一的布尔值会导致死锁:
busy()- 长时间运行的任务将设置为
busytrue - 权限提示框弹出,但其按钮被状态禁用
busy() - 任务需等待权限确认才能继续
- 用户因按钮被禁用无法确认权限
解决方法:权限UI应仅由权限专属的pending状态控制。
Signal snapshots in async handlers
异步处理程序中的Signal快照
If you read signals inside an async function and you need stable values, snapshot early:
ts
const request = activePermission();
if (!request) return;
const requestID = request.id;
await respondPermission(requestID, "always");如果在异步函数中读取signal且需要稳定值,请提前获取快照:
ts
const request = activePermission();
if (!request) return;
const requestID = request.id;
await respondPermission(requestID, "always");Derived UI state
推导式UI状态
Prefer for computed disabled states:
createMemo()ts
const canSend = createMemo(() => prompt().trim().length > 0 && !busy());优先使用计算禁用状态:
createMemo()ts
const canSend = createMemo(() => prompt().trim().length > 0 && !busy());Lists
列表处理
- Use setter callbacks for derived updates:
ts
setItems((current) => current.filter((x) => x.id !== id));- Don’t mutate in-place.
current
- 使用setter回调进行衍生更新:
ts
setItems((current) => current.filter((x) => x.id !== id));- 不要原地修改。
current
Practical checklist (SolidJS UI changes)
实用检查清单(SolidJS UI变更)
- Does any button depend on a global flag that could be true during long-running work?
- Could two async actions overlap and fight over one boolean?
- Is any UI state duplicated (can be derived instead)?
- Do event handlers read signals after an where values might have changed?
await - If you refactor props/types, did you update all intermediate component signatures and call sites?
- 是否有按钮依赖可能在长时间运行任务中变为true的全局标记?
- 是否存在两个异步操作因共用同一个布尔值而产生冲突的情况?
- 是否有可通过推导而非重复定义的UI状态?
- 事件处理程序是否在之后读取signal,而此时值可能已发生变化?
await - 重构props/类型时,是否更新了所有中间组件的签名和调用位置?
References
参考资料
- SolidJS: https://www.solidjs.com/docs/latest
- SolidJS signals: https://www.solidjs.com/docs/latest/api#createsignal
- SolidJS memos: https://www.solidjs.com/docs/latest/api#creatememo
- SolidJS: https://www.solidjs.com/docs/latest
- SolidJS signals: https://www.solidjs.com/docs/latest/api#createsignal
- SolidJS memos: https://www.solidjs.com/docs/latest/api#creatememo