任务与生命周期
useTask$、useVisibleTask$、track 与清理函数。
Qwik 只有三个生命周期阶段:Task、Render、VisibleTask。因为组件在服务端和浏览器之间只挂载一次,大多数副作用不需要在两边各跑一遍。
| 阶段 | 运行位置 | 典型用途 |
|---|---|---|
| Task | 服务端或浏览器 | 初始化、读取数据、跟踪状态变化 |
| Render | 服务端或浏览器 | 生成 JSX 与 DOM |
| VisibleTask | 浏览器 | 需要真实 DOM 或可见性信息的副作用 |
01useTask$()#
useTask$ 在首次渲染前运行,可以阻塞渲染。没有 track() 时,它在组件生命周期内只运行一次。
import { component$, useSignal, useTask$ } from '@qwik.dev/core';
export default component$(() => {
const keyword = useSignal('');
const matches = useSignal<string[]>([]);
useTask$(({ track }) => {
track(keyword);
matches.value = articles.filter((item) =>
item.title.includes(keyword.value)
);
});
return <input bind:value={keyword} placeholder="搜索文章" />;
});关键区别在于执行位置:服务端执行过的 useTask$,浏览器恢复时不会重跑。没有 track() 的任务因此等价于「只跑一次的初始化」,而带 track() 的任务会在浏览器里跟随状态变化重新执行。
02用 track() 响应状态变化#
把 signal 传给 track(),任务会在它变化时重新运行。重新运行总是发生在浏览器,响应式是客户端行为。
useTask$(({ track }) => {
const query = track(searchInput);
console.log('搜索词变为', query);
});任务默认串行执行,异步任务会阻塞下一个任务。跟踪状态后的重复执行默认阻塞 DOM 更新;传入 { deferUpdates: true } 可以让后续执行不阻塞更新。
useTask$(
({ track }) => {
track(count);
return logToAnalytics(count.value);
},
{ deferUpdates: true }
);03cleanup()#
任务再次运行或组件卸载时,注册的清理函数会执行。取消请求、清除定时器、移除手动绑定的事件都应该放在这里。
useTask$(async ({ track, cleanup }) => {
track(query);
const controller = new AbortController();
cleanup(() => controller.abort());
const res = await fetch(`/api/search?q=${query.value}`, {
signal: controller.signal,
});
results.value = await res.json();
});04useVisibleTask$()#
需要真实 DOM 时用 useVisibleTask$。它在浏览器渲染后运行,默认在组件进入视口时执行。代价是它会提前加载框架核心与任务代码,因此应当作为最后手段。
useVisibleTask$(({ cleanup }) => {
const observer = new IntersectionObserver((entries) => {
visible.value = entries[0]?.isIntersecting ?? false;
});
observer.observe(ref.value!);
cleanup(() => observer.disconnect());
});定时器同理。计时必须在组件真正挂载后启动,且只存在于浏览器,这类副作用必须放在 useVisibleTask$ 里:
import { component$, useSignal, useVisibleTask$ } from '@qwik.dev/core';
export default component$(() => {
const time = useSignal('');
useVisibleTask$(({ cleanup }) => {
const tick = () => {
time.value = new Date().toLocaleTimeString();
};
tick();
const id = setInterval(tick, 1000);
cleanup(() => clearInterval(id));
});
return <time>{time.value}</time>;
});确实需要立即运行时,可以传 { strategy: 'document-ready' } 或 { strategy: 'document-idle' }。前者在应用启动后马上运行,后者等到空闲。
05Hook 规则#
所有 use* hook 只能出现在 component$ 的根部,或另一个 use* hook 的根部。不要放进条件分支、事件处理函数或 $() 包装的闭包里。