状态
useSignal、useStore、useComputed$ 与可序列化状态。
Qwik 的状态分两类:普通可序列化数据,以及能触发 UI 更新的响应式状态。响应式状态用 useSignal() 或 useStore() 创建。
01useSignal()#
signal 是一个带 .value 属性的对象。读写 .value 会被 Qwik 跟踪:
import { component$, useSignal } from '@qwik.dev/core';
export default component$(() => {
const count = useSignal(0);
return (
<button onClick$={() => count.value++}>
点击 {count.value} 次
</button>
);
});LIVE / USE_SIGNALidle
这个数字由 signal 驱动。点击任意按钮,只有这块 DOM 更新。
把 signal 传给子组件时有两种选择:传 signal.value 只传当前值;传 signal 本身,子组件就能读写它。
02useStore()#
store 是对象版的 signal。Qwik 默认深度跟踪嵌套对象与数组:
const state = useStore({
name: 'Qwik',
meta: { stars: 0 },
tags: ['resumable'],
});
state.meta.stars++;
state.tags.push('streaming');store 中的方法需要用 $() 包装才能序列化。需要 this 时使用 function 关键字,并把接口单独声明出来——直接在类型参数里写 typeof counter 会触发 TS7022 自引用错误。
import { $, type QRL } from '@qwik.dev/core';
interface CounterStore {
count: number;
increment: QRL<(this: CounterStore) => void>;
}
const counter = useStore<CounterStore>({
count: 0,
increment: $(function () {
this.count++;
}),
});03useComputed$()#
派生值用 useComputed$。它只重新计算依赖变化的部分,不会让整个组件重新渲染:
const price = useSignal(120);
const quantity = useSignal(2);
const total = useComputed$(() => price.value * quantity.value);
return <p>合计 {total.value} 元</p>;异步派生值同样可以交给 useComputed$。它不会阻塞首次渲染,并且可以拿到 abortSignal 取消上一次请求:
const query = useSignal('');
const results = useComputed$(async ({ abortSignal }) => {
const res = await fetch(`/api/search?q=${query.value}`, {
signal: abortSignal,
});
return res.json();
}, { initial: [] });04useConstant()#
只在组件整个生命周期内计算一次的值,用 useConstant()。它不具响应性,适合随机 id、配置对象这类不需要触发更新的数据。
05共享状态#
- props:父子组件之间最直接的方式,props 必须可序列化。
- context:跨越多层组件,见上下文。
- store 方法:把行为与状态放在同一个可序列化对象里。
06序列化边界#
需要跨过服务端与客户端边界的状态必须可序列化。无法序列化的对象(例如浏览器 API 实例、第三方类实例)可以用 noSerialize() 标记:
import { noSerialize, useStore } from '@qwik.dev/core';
const state = useStore({
chart: noSerialize(new Chart(canvas)),
});