RESUMABLE WEB FRAMEWORKQWIK / V2.0

从服务器
继续运行。

Qwik 把状态和事件监听器写进 HTML。浏览器不需要重新执行整个应用,只加载下一次交互真正需要的代码。零成本打造即时加载的 Web 应用。

HYDRATION
无
QWIKLOADER
≈1 KB
STARTUP
O(1)
LIVE / RESUME点击左侧按钮观察
RESUME_CONSOLE / 001
服务端已暂停
01 / SERVERPAUSED
componentCounter#c_01已序列化
listeneron:click → s_LToOr已序列化
state{ count: 0 }已序列化

没有 re-render,没有事件重绑,只有一份可读的 HTML。

02 / BROWSERWAITING
COUNTER / SIGNALhtml only
00
serialized-htmlSSR已送达
chunk/resume.js0.9 kB待请求
chunk/on-click.js0.3 kB待请求
0 次重渲0/2 chunkcount = 0

SSR 输出 → 浏览器恢复 → 按需加载符号

01 / COMPONENT REPLAY0启动时不重放组件
02 / EAGER JS≈1 KBqwikloader 内联在 HTML
03 / STARTUP SCALEO(1)应用变大,启动成本不变
04 / FIRST PAINTHTML内容先到,交互随后

01 / THE COST

水合,是一次不必要的重启。

其他框架服务端渲染后,还要在浏览器里重新执行一遍组件、恢复状态、重新绑定监听器。页面看起来已经好了,主线程却还在忙。

“可恢复性不是更快的重新执行,而是根本不需要重新执行。”
传统水合HYDRATION

启动时执行全部组件与副作用,再把这些工作交给浏览器。

Qwik 恢复RESUME

读取已序列化的状态,只下载并执行被触发的那个符号。

启动时执行首次交互才加载示意图
STARTUP COST / APP SIZE示意图
应用规模 →启动成本 ↑小大水合框架Qwik规模继续增长,差距持续扩大
启动成本随应用规模变化的示意。Qwik 的启动只与当前页面的交互有关,而不与组件总数线性相关。

02 / O(1) STARTUP

应用变大,
启动不变。

Qwik 的可恢复模型让服务端承担渲染,客户端只恢复状态。新增页面、组件和功能,不会自动变成首屏的 JavaScript 负担。

  • A首屏只发送内容与序列化状态。
  • B事件代码在首次触发时下载。
  • C预取让首次交互通常无需等待。

04 / OPTIMIZER

每个 $ 都是一个
懒加载边界。

构建期切分,运行期按需加载。你写的是普通函数,Optimizer 负责边界。

tsxsrc/components/counter.tsx
import { component$, useSignal, useTask$ } from '@qwik.dev/core';

export const Counter = component$(() => {
  const count = useSignal(0);

  useTask$(({ track }) => {
    track(count);
    console.log('count is', count.value);
  });

  return (
    <button onClick$={() => count.value++}>
      {count.value}
    </button>
  );
});
OPTIMIZER OUTPUTwaiting
01Counter_component.js—

渲染函数

等待拆分

02Counter_useTask.js—

响应任务

等待拆分

03Counter_onClick.js—

事件处理

等待拆分

每个 $ 都是一个可独立请求的符号。Qwik 不会为了一个点击事件加载整棵组件树。

05 / ROUTER & DATA

一次请求,
一条完整的数据路径。

页面、loader、action 与端点共享同一个 RequestEvent,没有客户端与服务端两套数据逻辑。

tsxsrc/routes/products/index.tsx
import { component$ } from '@qwik.dev/core';
import { routeLoader$ } from '@qwik.dev/router';

export const useProducts = routeLoader$(async ({ url }) => {
  const category = url.searchParams.get('category');
  return db.products.list({ category });
});

export default component$(() => {
  const products = useProducts();
  return <ProductGrid items={products.value} />;
});

loader 只在服务端执行,结果随 HTML 一起序列化。

06 / DOCUMENTATION

19 篇,
从零到可恢复。

按心智模型、核心 API、路由与进阶组织。每一篇都能独立阅读,也都能在项目里直接运行。

打开文档总览

NEXT / YOUR FIRST ROUTE

把 JavaScript
留给交互。

从一条路由开始,写一个 loader,加一个 signal。你会看到一个没有水合的应用如何运行。

CREATE A PROJECT
npm create qwik@latest