QWIK DOCS / V2

任务与生命周期

useTask$、useVisibleTask$、track 与清理函数。

约 3 分钟Qwik v209 / 19
本页目录5 节
  1. useTask$()
  2. 用 track() 响应状态变化
  3. cleanup()
  4. useVisibleTask$()
  5. Hook 规则

Qwik 只有三个生命周期阶段:Task、Render、VisibleTask。因为组件在服务端和浏览器之间只挂载一次,大多数副作用不需要在两边各跑一遍。

阶段运行位置典型用途
Task服务端或浏览器初始化、读取数据、跟踪状态变化
Render服务端或浏览器生成 JSX 与 DOM
VisibleTask浏览器需要真实 DOM 或可见性信息的副作用

01useTask$()#

useTask$ 在首次渲染前运行,可以阻塞渲染。没有 track() 时,它在组件生命周期内只运行一次。

tsxsrc/components/search.tsx
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(),任务会在它变化时重新运行。重新运行总是发生在浏览器,响应式是客户端行为。

tsxcode
useTask$(({ track }) => {
  const query = track(searchInput);
  console.log('搜索词变为', query);
});

任务默认串行执行,异步任务会阻塞下一个任务。跟踪状态后的重复执行默认阻塞 DOM 更新;传入 { deferUpdates: true } 可以让后续执行不阻塞更新。

tsxcode
useTask$(
  ({ track }) => {
    track(count);
    return logToAnalytics(count.value);
  },
  { deferUpdates: true }
);

03cleanup()#

任务再次运行或组件卸载时,注册的清理函数会执行。取消请求、清除定时器、移除手动绑定的事件都应该放在这里。

tsxcode
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$。它在浏览器渲染后运行,默认在组件进入视口时执行。代价是它会提前加载框架核心与任务代码,因此应当作为最后手段。

tsxcode
useVisibleTask$(({ cleanup }) => {
  const observer = new IntersectionObserver((entries) => {
    visible.value = entries[0]?.isIntersecting ?? false;
  });
  observer.observe(ref.value!);
  cleanup(() => observer.disconnect());
});

定时器同理。计时必须在组件真正挂载后启动,且只存在于浏览器,这类副作用必须放在 useVisibleTask$ 里:

tsxsrc/components/clock.tsx
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 的根部。不要放进条件分支、事件处理函数或 $() 包装的闭包里。