Qwik 技术深度回顾:从入门到实战

2026-02-24
14745 分钟
...

Qwik 是一个以 Resumability(可恢复性) 为核心的现代前端框架,它的目标是实现 O(1) 的 JavaScript 加载量,即无论应用多大,首屏加载的 JS 量都几乎为零。

本文将带你回顾项目中实际使用到的 Qwik 核心技术,帮助你快速重拾对 Qwik 的记忆。

1. 核心概念:$ 后缀与懒加载

在 Qwik 中,你会发现大量的 API 以 $ 结尾(如 component$, onClick$, useVisibleTask$)。

  • 含义$ 标志着代码的序列化边界。编译器会将 $ 包裹的函数提取到单独的 chunk 文件中。
  • 作用:只有当浏览器真正需要执行这段逻辑(例如用户点击按钮)时,才会通过网络下载这部分代码。这是 Qwik 实现“零水合(Zero Hydration)”的关键。

2. 组件基础 (component$)

所有 Qwik 组件都必须使用 component$ 包装。这使得组件本身可以被懒加载。

项目示例 (参考 src/components/common/Header.tsx):

import { component$ } from '@builder.io/qwik';

// 组件定义
export default component$(() => {
  // 组件逻辑...
  return (
    <header>
      <h1>我的博客</h1>
    </header>
  );
});

3. 状态管理 (useSignal vs useStore)

Qwik 提供了两种主要的状态管理方式,它们都是细粒度的响应式数据源。

3.1 useSignal:原子值状态

适用于数字、字符串、布尔值等简单类型。访问和修改时需要使用 .value 属性。

项目示例 (参考 src/components/common/Header.tsx):

import { component$, useSignal } from '@builder.io/qwik';

export default component$(() => {
  // 定义一个布尔类型的信号
  const isMenuOpen = useSignal(false);

  return (
    <button onClick$={() => {
      // 修改信号值,触发 UI 更新
      isMenuOpen.value = !isMenuOpen.value;
    }}>
      {isMenuOpen.value ? '关闭菜单' : '打开菜单'}
    </button>
  );
});

3.2 useStore:对象/数组状态

适用于复杂的对象或数组。它是深度响应式的,直接修改属性即可触发更新。

项目示例 (参考 src/components/comments/CommentSection.tsx):

import { component$, useStore } from '@builder.io/qwik';

export default component$(() => {
  // 定义一个对象类型的 store
  const formData = useStore({
    nickname: '',
    content: '',
  });

  return (
    <form>
      <input
        value={formData.nickname}
        onInput$={(e) => (formData.nickname = (e.target as HTMLInputElement).value)}
      />
    </form>
  );
});

4. 生命周期与副作用

Qwik 没有传统 React 的 useEffect,而是提供了更精细的任务调度 API。

4.1 useVisibleTask$:仅浏览器端执行

这是最接近 useEffect 的 API,但它只在浏览器中运行,且只有当组件在视口中可见时才会执行(除非指定 strategy: 'eager')。常用于直接操作 DOM、绑定第三方库或监听 window 事件。

项目示例 (参考 src/root.tsxsrc/components/common/Header.tsx):

import { component$, useVisibleTask$ } from '@builder.io/qwik';

export default component$(() => {
  // 仅在客户端执行
  useVisibleTask$(({ cleanup }) => {
    const handleScroll = () => console.log('scrolling');
    window.addEventListener('scroll', handleScroll);
    
    // 清理函数
    cleanup(() => window.removeEventListener('scroll', handleScroll));
  });

  return <div />;
});

4.2 useTask$:同构执行 (SSR + Client)

在组件挂载前执行,可用于数据计算或状态同步。它在服务端渲染 (SSR) 时也会运行,因此不能在其中直接访问 windowdocument

项目示例 (参考 src/routes/guestbook/index.tsx):

import { component$, useTask$, useSignal } from '@builder.io/qwik';

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

  // 监听 count 变化,自动更新 doubleCount
  useTask$(({ track }) => {
    track(() => count.value); // 追踪依赖
    doubleCount.value = count.value * 2;
  });

  return <div>{doubleCount.value}</div>;
});

5. 样式处理 (useStylesScoped$)

Qwik 推荐使用 useStylesScoped$ 来加载组件级作用域的 CSS。这避免了全局样式污染。

项目示例 (参考 src/components/common/DanmakuOverlay.tsx):

import { component$, useStylesScoped$ } from '@builder.io/qwik';
// 导入 CSS 文件内容 (注意 ?inline 后缀)
import styles from './danmaku.css?inline';

export default component$(() => {
  // 注入样式
  useStylesScoped$(styles);

  return <div class="danmaku-container">...</div>;
});

6. Qwik City:路由与服务端数据

Qwik City 是 Qwik 的元框架(类似 Next.js 之于 React),提供了基于文件的路由和数据加载能力。

6.1 routeLoader$:服务端数据加载

用于在页面渲染前(服务端)获取数据。组件通过 hook 方式读取数据。

项目示例 (参考 src/routes/posts/detail/[...slug]/index.tsx):

import { routeLoader$ } from '@builder.io/qwik-city';
import { component$ } from '@builder.io/qwik';

// 定义 Loader (仅在服务端运行)
export const usePostDetail = routeLoader$(async (requestEvent) => {
  const slug = requestEvent.params.slug;
  const post = await db.findPost(slug);
  return post;
});

export default component$(() => {
  // 获取数据 (Signal)
  const post = usePostDetail();
  
  return (
    <article>
      <h1>{post.value.title}</h1>
    </article>
  );
});

6.2 server$:服务端函数 (RPC)

这是 Qwik 最强大的功能之一。你可以直接在组件文件中定义服务端逻辑,前端像调用普通函数一样调用它,Qwik 会自动处理网络请求序列化。

项目示例 (参考 src/components/comments/CommentSection.tsx):

import { server$ } from '@builder.io/qwik-city';

// 定义服务端函数
const submitComment = server$(async (data: { content: string }) => {
  // 这里可以直接连接数据库,因为代码只在服务端运行
  await db.saveComment(data);
  return { success: true };
});

export default component$(() => {
  return (
    <button onClick$={async () => {
      // 客户端直接调用,像调用本地函数一样
      const result = await submitComment({ content: 'Hello' });
      console.log(result);
    }}>
      提交
    </button>
  );
});

7. 全局状态共享 (Context)

当需要跨组件传递数据(如主题设置)时,Qwik 使用 Context API,机制与 React 类似但针对可恢复性做了优化。

项目示例 (参考 src/routes/layout.tsx):

import { component$, createContextId, useContextProvider, useContext, useStore } from '@builder.io/qwik';

// 1. 创建 Context ID
export const ThemeContext = createContextId<{ theme: string }>('docs.theme-context');

export default component$(() => {
  const state = useStore({ theme: 'dark' });
  
  // 2. 提供 Context
  useContextProvider(ThemeContext, state);

  return <Slot />;
});

// 子组件中使用
export const Child = component$(() => {
  // 3. 消费 Context
  const themeState = useContext(ThemeContext);
  return <div>Current theme: {themeState.theme}</div>;
});

8. <Slot /> 内容投影

类似于 React 的 children 或 Vue 的 <slot>,Qwik 使用 <Slot /> 组件将父组件传递的内容渲染到指定位置。这在布局文件 layout.tsx 中非常常见。

项目示例 (参考 src/routes/layout.tsx):

import { component$, Slot } from '@builder.io/qwik';

export default component$(() => {
  return (
    <main>
      <Header />
      {/* 页面内容将渲染在这里 */}
      <Slot />
      <Footer />
    </main>
  );
});

总结

Qwik 的核心思想是延迟一切。通过 $ 语法、Signals 和 Server Functions,它让你在编写类似 React 的代码时,自动获得了极致的加载性能。

回顾项目代码时,重点关注以下模式:

  1. 交互逻辑是否被 $ 包裹?(确保懒加载)
  2. 副作用是否正确区分了 useTask$ (同构) 和 useVisibleTask$ (仅浏览器)?
  3. 数据获取是否使用了 routeLoader$server$ 从而避免了客户端瀑布流请求?

如果您觉得这篇文章有帮助,请点个赞吧~

分享文章

相关文章

更多文章 →
qwik2026-06-24
Qwik 中本地图片为什么推荐用 ESM 导入
在 Qwik 项目里,如果我们直接这样写本地图片: 代码本身是可以运行的,但 ESLint 可能会提示 。这个提示不是错误,而是 Qwik 在提醒我们: 本地图片可以通过 ESM 导入的方式进行优化 。 问题来源 Qwik 推荐把本地图片放到 目录下,然后通过 导入,并在路径后面加上 。 例如: 这样导入之后,图片会变成一个可以直接使用的组件。 为什么要这样做 直接写 时,浏览器只会加载这一张原图。图片多大,用户就下载多大。 而使用 Q...
学习
qwik2026-06-20
Qwik City 构建后 Godot 游戏 index.html 被删除的根因分析
背景 在 Qwik 博客项目中,我把 Godot Web 导出的游戏资源放在: Qwik 页面通过 iframe 嵌入游戏: 本地开发和本地静态资源检查时,游戏资源是存在的;但服务器执行完整构建后,发现: 消失了,最终导致 iframe 加载失败。 一开始容易误判为服务器部署脚本删文件、Git 没拉到资源、Godot 导出目录不对,或者 public 静态资源没有复制进 dist。但逐步排查后发现,根因不是这些。 现象复现 Qwik 项...
学习
qwik2026-02-28
理解 Qwik 的 routeLoader$:执行时机与 SSR/SSG/CSR 全景解析
🔑 一句话定义 是 Qwik City 专为“路由级数据加载”设计的声明式 API 。 它将数据获取逻辑与组件解耦,通过 序列化 + 状态恢复 实现“零 hydration”体验——这正是 Qwik “可恢复性”(Resumability)架构的灵魂所在。 📊 执行时机全景表(建议收藏!) | 场景 | 执行位置 | 触发时机 | 数据来源 | 客户端是否重执行? | | | | | | | | SSR | 服务端 | 用户请求页面...
学习
qwik2026-02-24
Qwik博客性能优化实战
1\. 回归原生:用标准能力替代冗余脚本 曾几何时,为实现图片懒加载与资源预取,开发者常需手写复杂的 Intersection Observer 逻辑。但随着浏览器能力的演进,这类“黑科技”反而可能成为性能负担。 问题所在 : 自定义懒加载脚本不仅增加首屏 JS 体积,还会频繁触发 DOM 监听与计算,占用主线程资源,推高 Total Blocking Time(TBT)。 优化方案 : 图片加载 :直接采用 。浏览器内核级实现更高效、...
学习
qwik2026-01-15
Qwik 服务端能力深度解析
1\. Qwik 服务端架构概览 Qwik 采用独特的 可恢复性 (resumability)架构,使其服务端渲染(SSR)能力与传统框架有本质区别。在 Qwik 中,服务端不仅负责初始 HTML 生成,还负责: 组件序列化与反序列化 事件监听器的注册与恢复 数据流的管理(从服务端到客户端) 优化资源加载策略 Qwik 的服务端处理是 细粒度 的,允许开发者精确控制哪些代码在服务端执行,哪些在客户端执行,同时保持无缝协作。 2\. Qw...
学习
qwik2026-01-09
qwik api介绍
| 类别 | 名称 | 功能描述 | 适用场景 | | | | | | | 生命周期 | onMount | 组件挂载时执行 | 初始化DOM操作、设置事件监听 | | | onUnmount | 组件卸载时执行 | 清理资源、移除事件监听 | | | onVisible | 组件在视口可见时执行 | 懒加载内容、分析追踪 | | | onResume | 从序列化状态恢复时执行 | 恢复应用状态 | | 核心API | compone...
学习

评论

请登录后发表评论

去登录
加载评论中...

目录