一键导入
signals
Guidelines for state management with createModel, Show, and For using Preact Signals.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Guidelines for state management with createModel, Show, and For using Preact Signals.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | signals |
| description | Guidelines for state management with createModel, Show, and For using Preact Signals. |
Guidelines for state management with createModel, Show, and For using @preact/signals and @preact/signals/utils.
Signals are reactive primitives from @preact/signals that provide fine-grained reactivity. @preact/signals/utils adds ergonomic helpers like Show and For for declarative rendering, while createModel comes from @preact/signals for model-driven state.
createModelimport { computed, createModel, signal } from '@preact/signals';
export const CounterModel = createModel(() => {
const count = signal(0);
const name = signal<string | null>(null);
const items = signal<Item[]>([]);
const hasItems = computed(() => items.value.length > 0);
const increment = () => {
count.value++;
};
const addItem = (item: Item) => {
items.value = [...items.value, item];
};
return {
count,
name,
items,
hasItems,
increment,
addItem,
};
});
import { useModel } from '@preact/signals';
const model = useModel(CounterModel);
// Read with .value
<span>{model.count.value}</span>
// Write with .value assignment
<button onClick={model.increment}>Increment</button>
<button onClick={() => (model.name.value = 'Alice')}>Set Name</button>
Show and Forimport { useModel } from '@preact/signals';
import { For, Show } from '@preact/signals/utils';
function ItemList() {
const model = useModel(CounterModel);
return (
<>
<Show when={model.hasItems} fallback={<p>No items yet.</p>}>
<ul>
<For each={model.items}>
{(item) => <li key={item.value.id}>{item.value.label}</li>}
</For>
</ul>
</Show>
</>
);
}
UI state that changes frequently
State used in event handlers
State derived from async operations
Toggle state (FAQ accordion model):
const FAQItemModel = createModel(() => {
const open = signal(false);
const toggle = () => {
open.value = !open.value;
};
return { open, toggle };
});
function FAQItem({ question, answer }) {
const model = useModel(FAQItemModel);
return (
<div>
<button onClick={model.toggle}>
{question}
</button>
<Show when={model.open}>
<div>{answer}</div>
</Show>
</div>
);
}
Auth state model:
const AuthModel = createModel(() => {
const isAuthenticated = signal<boolean | null>(null);
const checkingAuth = signal(true);
const loadSession = async () => {
const res = await authClient.getSession();
isAuthenticated.value = !!res.data?.user;
checkingAuth.value = false;
};
return { isAuthenticated, checkingAuth, loadSession };
});
const auth = useModel(AuthModel);
useEffect(() => {
auth.loadSession();
}, []);
Upload progress:
const UploadModel = createModel(() => {
const isDragging = signal(false);
const isUploading = signal(false);
const uploadError = signal<string | null>(null);
const uploadProgress = signal(0);
return { isDragging, isUploading, uploadError, uploadProgress };
});
Window drag state:
const WindowModel = createModel(() => {
const isDragging = signal(false);
const position = signal({ x: 0, y: 0 });
const size = signal({ width: 800, height: 600 });
return { isDragging, position, size };
});
Encapsulate complex signal logic in models:
// models/WindowDragModel.ts
export const WindowDragModel = createModel(({ defaultWidth, defaultHeight }) => {
const isDragging = signal(false);
const position = signal({ x: 0, y: 0 });
const size = signal({ width: defaultWidth, height: defaultHeight });
// Event handlers that mutate signals...
return {
isDragging,
position,
size,
handleMouseDown,
};
});
// component
const drag = useModel(WindowDragModel);
| Use Case | Prefer |
|---|---|
| Simple boolean toggle | signal in createModel |
| Object with multiple fields updated together | signal in createModel |
| State updated in event handlers | signal in createModel |
| State passed deep into children | createModel + Show/For |
| State that rarely changes | Either works |
| State managed by external library | Follow library conventions |
Always provide types for non-obvious signal values:
const user = signal<User | null>(null);
const status = signal<'idle' | 'loading' | 'error'>('idle');
Signals don't need functional updates like useState:
// With signals - direct mutation is fine
onClick={() => count.value++}
onClick={() => items.value = [...items.value, newItem]}
// Reading current value in handler
onClick={() => {
if (count.value < 10) {
count.value++;
}
}}
Show and For for RenderingPrefer Show and For for common conditional and list rendering:
<Show when={isLoading}>
<Spinner />
</Show>
<Show when={error}>
<ErrorMessage>{error.value}</ErrorMessage>
</Show>
<For each={items}>
{(item) => <Item key={item.value.id} {...item.value} />}
</For>
You can also use a callback in the
whenandeachto simulate acomputedsignal if needed.
Signals work alongside traditional hooks:
useEffect(() => {
if (isOpen) {
checkingAuth.value = true;
fetchData().then(data => {
result.value = data;
checkingAuth.value = false;
});
}
}, [isOpen]);
Prefer local signals over global state. Only lift state when multiple components need to share it.
.value// Wrong - won't update
{isOpen && <Modal />}
// Correct
{isOpen.value && <Modal />}
When using Show, pass a signal or computed signal to when:
<Show when={isOpen}>
<Modal />
</Show>
// Wrong - won't trigger update
position.value.x = 100;
// Correct
position.value = { ...position.value, x: 100 };
// or
position.value = { x: 100, y: position.value.y };