Skip to content

参考

基于 Qwik 1.x(@builder.io/qwik / @builder.io/qwik-city)—— API 速查 / 文件约定 / 配置选项 / 命名约定 / 与同类对比

包结构

用途必需
@builder.io/qwik核心 runtime(JSX + Resumability + Optimizer)
@builder.io/qwik/server服务端渲染(renderToString / renderToStreamSSR 时
@builder.io/qwik/optimizerVite/Rollup plugin(编译时 $ 拆分)
@builder.io/qwik/build构建时常量(isServer / isDev / isBrowser常用
@builder.io/qwik-city元框架(路由 + loaders + actions + middleware)几乎必需
@builder.io/qwik-city/viteVite plugin(路由扫描)
@builder.io/qwik-city/middleware/*各 adapter 的请求适配器部署时
@builder.io/qwik-city/adapters/*各 adapter 的 Vite 配置工厂部署时
@unpic/qwik图片优化(CDN 集成)可选
qwik-image自定义 image loader可选
styled-vanilla-extractCSS-in-JS(零运行时)可选
@modular-forms/qwik复杂表单库可选
valibot / zod校验库(配 validator$ / zod$可选

Qwik 核心 API(@builder.io/qwik

组件

ts
// 定义 lazy-loaded 组件
component$<T>(onMount: (props: T) => JSXNode): Component<T>

// 内联组件(不 lazy load)
const InlineComp = (props) => <div>{props.x}</div>
tsx
import { component$ } from '@builder.io/qwik'

export const Button = component$<{ label: string }>(({ label }) => {
  return <button>{label}</button>
})

Signals & Stores

API签名说明
useSignaluseSignal<T>(initial?: T): Signal<T>创建响应式单值
useStoreuseStore<T>(state: T, options?: { deep?: boolean, reactive?: boolean }): T创建响应式对象(默认深响应)
useComputed$useComputed$<T>(fn: () => T): ReadonlySignal<T>同步派生值
useConstantuseConstant<T>(value: T | (() => T)): T持久化常量(跨 render 稳定)
tsx
import { useSignal, useStore, useComputed$ } from '@builder.io/qwik'

const count = useSignal(0)            // Signal<number>
const user = useStore({ name: 'A' })  // 直接属性访问
const double = useComputed$(() => count.value * 2)  // ReadonlySignal<number>

Tasks(生命周期)

API签名何时运行
useTask$useTask$(fn: (ctx: TaskCtx) => void | Promise<void>): void渲染前(server + client)
useVisibleTask$useVisibleTask$(fn, options?: { strategy }): void客户端,组件可见时
useResource$useResource$<T>(fn: (ctx) => Promise<T>): ResourceReturn<T>异步数据 + 状态
tsx
import { useTask$, useVisibleTask$, useResource$ } from '@builder.io/qwik'

useTask$(async ({ track, cleanup }) => {
  const value = track(() => signal.value)
  cleanup(() => console.log('cleanup'))
})

useVisibleTask$(
  ({ cleanup }) => { /* DOM 操作 */ },
  { strategy: 'intersection-observer' }
)

const data = useResource$<Data>(async ({ track, cleanup }) => {
  const id = track(() => idSignal.value)
  const ctrl = new AbortController()
  cleanup(() => ctrl.abort())
  return await fetch(`/api/${id}`, { signal: ctrl.signal }).then(r => r.json())
})

Context

API签名说明
createContextIdcreateContextId<T>(name: string): ContextId<T>创建 context 标识
useContextProvideruseContextProvider<T>(ctx: ContextId<T>, value: T): void在组件内提供 context
useContextuseContext<T>(ctx: ContextId<T>): T消费 context
tsx
import { createContextId, useContext, useContextProvider } from '@builder.io/qwik'

export const ThemeId = createContextId<Signal<string>>('app.theme')

// Provider
const theme = useSignal('dark')
useContextProvider(ThemeId, theme)

// Consumer
const theme = useContext(ThemeId)

样式

API签名说明
useStyles$useStyles$(styles: string): void全局样式
useStylesScoped$useStylesScoped$(styles: string): { scopeId }组件作用域样式
tsx
import styles from './card.css?inline'

useStyles$(styles)
useStylesScoped$(`.card { padding: 1rem; }`)

事件 Hooks

API签名说明
useOnuseOn(event: string, qrl: QRL<...>): void给当前组件加事件监听
useOnDocumentuseOnDocument(event: string, qrl): voiddocument 事件
useOnWindowuseOnWindow(event: string, qrl): voidwindow 事件
tsx
import { useOnDocument, $ } from '@builder.io/qwik'

useOnDocument('mousemove', $((e: MouseEvent) => {
  console.log(e.clientX, e.clientY)
}))

序列化

API签名说明
$$<T>(fn: T): QRL<T>把函数包装成 QRL(lazy reference)
noSerializenoSerialize<T>(value: T): NoSerialize<T>标记值不参与序列化
useLexicalScopeuseLexicalScope<T>(): T在 QRL 内部访问闭包(Optimizer 生成代码用)
tsx
import { $, noSerialize, type NoSerialize } from '@builder.io/qwik'

const handler = $(() => console.log('lazy'))
const store = useStore<{ x: NoSerialize<MyClass> | undefined }>({ x: undefined })

内置组件

组件用途
<Slot />内容投影槽
<Slot name="x" />命名 slot
<Fragment /><>多元素包装
<Resource value={r} onPending onRejected onResolved />useResource$ 三态渲染
<SSRStream>流式 SSR

其他工具

API用途
useId()生成跨服务器/客户端一致的唯一 ID
useErrorBoundary()错误边界
event$ / eventQrl$显式标记事件 QRL
sync$同步事件处理(用于 preventDefault)
implicit$FirstArg把函数的第一参数自动 $ 包装(库作者用)
untrack在 task 内不追踪某段代码

@builder.io/qwik/build:构建时常量

ts
import { isServer, isBrowser, isDev } from '@builder.io/qwik/build'

if (isServer) {
  // 编译时这块代码只保留在服务端 bundle
}
if (isBrowser) {
  // 仅客户端
}
if (isDev) {
  // 仅开发模式
}

关键特性:这些常量是编译时条件——if (isServer) 块会被 dead code elimination 完全移除,不增加 client bundle。

Qwik City API(@builder.io/qwik-city

路由 Hooks

API签名说明
useLocationuseLocation(): RouteLocation当前 URL / params / 等
useNavigateuseNavigate(): NavigationFn编程式导航
useContentuseContent(): { headings, menu }当前页面的内容元数据
useDocumentHeaduseDocumentHead(): DocumentHead当前页面 head meta
tsx
import { useLocation, useNavigate } from '@builder.io/qwik-city'

const loc = useLocation()
// loc.url, loc.params, loc.pathname, loc.search, loc.isNavigating

const nav = useNavigate()
nav('/about')                   // 跳转
nav('/about', { replaceState: true })  // 替换 history
nav()                           // 刷新当前页

数据加载

API签名说明
routeLoader$routeLoader$<T>(fn: (ev: RequestEvent) => T | Promise<T>): RouteLoader<T>路由数据加载
routeAction$routeAction$<T, I>(fn, validator?): Action<T, I>路由副作用
globalAction$globalAction$(...)跨路由 action
server$server$<T>(fn: function): TRPC 风格远程调用
validator$validator$(fn): Validator自定义校验器
zod$zod$(schema)zod$((z, ev) => schema)Zod 校验器
tsx
import { routeLoader$, routeAction$, server$, zod$, z } from '@builder.io/qwik-city'

export const useUser = routeLoader$(async ({ cookie }) => {
  return await db.user.fromSession(cookie.get('s')?.value)
})

export const useSaveUser = routeAction$(
  async (data, ev) => { /* ... */ },
  zod$({
    name: z.string().min(1),
    email: z.string().email(),
  })
)

export const greet = server$(function (name: string) {
  return `Hello ${name}, your IP: ${this.headers.get('x-forwarded-for')}`
})

内置组件

组件用途
<QwikCityProvider>应用根(注入路由 context)
<RouterOutlet />渲染当前路由
<Link href prefetch reload>SPA 链接(自动 prefetch)
<Form action={...} spaReset onSubmitCompleted$>表单(含无 JS 支持)
<ServiceWorkerRegister />注册 PWA
<QwikCityMockProvider>测试用 mock provider
tsx
<Link href="/about" prefetch>About</Link>
<Link href="/refresh" reload>Reload</Link>

<Form action={action} spaReset onSubmitCompleted$={() => console.log('done')}>
  ...
</Form>

Middleware 类型

ts
type RequestHandler = (ev: RequestEvent) => Promise<void> | void

// 在 layout.tsx / index.tsx 中导出
export const onRequest: RequestHandler = async (ev) => { /* 所有 */ }
export const onGet: RequestHandler = async (ev) => { /* GET */ }
export const onPost: RequestHandler = async (ev) => { /* POST */ }
export const onPut: RequestHandler = async (ev) => { /* PUT */ }
export const onPatch: RequestHandler = async (ev) => { /* PATCH */ }
export const onDelete: RequestHandler = async (ev) => { /* DELETE */ }

RequestEvent 接口

ts
interface RequestEvent {
  // 路径 / 方法
  url: URL
  pathname: string
  basePathname: string
  method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'OPTIONS' | 'HEAD'
  query: URLSearchParams
  params: Record<string, string>
  request: Request

  // 响应控制
  status(code?: number): number
  headers: Headers
  cacheControl(control: CacheControl): void
  cookie: Cookie
  send(response: Response): void
  send(status: number, body: string | Uint8Array | ReadableStream): void
  json(status: number, data: unknown): void
  html(status: number, html: string): void
  text(status: number, text: string): void
  redirect(status: number, url: string): RedirectMessage  // throw 来重定向
  error(status: number, message: string): ErrorResponse   // throw 来报错
  fail(status: number, data: T): FailReturn<T>            // return 失败响应

  // 上下文
  sharedMap: Map<string, unknown>
  env: EnvGetter      // env.get(key)
  platform: unknown   // 平台特定 API(CF Workers / Vercel / 等)
  locale(locale?: string): string

  // 流程控制
  next(): Promise<void>           // 调用下一个 middleware
  exit(): never                   // 跳过后续处理
  resolveValue<T>(loader: T): Promise<T>  // 等待其他 loader

  // 输入解析
  parseBody(): Promise<unknown>
  getWritableStream(): WritableStream
}

DocumentHead 类型

ts
type DocumentHead = DocumentHeadValue | ((ev: DocumentHeadProps) => DocumentHeadValue)

interface DocumentHeadValue {
  title?: string
  meta?: DocumentMeta[]
  links?: DocumentLink[]
  styles?: DocumentStyle[]
  scripts?: DocumentScript[]
  frontmatter?: Record<string, unknown>
}
tsx
// 静态
export const head: DocumentHead = {
  title: 'About',
  meta: [
    { name: 'description', content: 'About page' },
    { property: 'og:title', content: 'About' },
  ],
  links: [
    { rel: 'canonical', href: 'https://example.com/about' },
  ],
}

// 动态(基于 loader)
export const head: DocumentHead = ({ resolveValue, params }) => {
  const product = resolveValue(useProduct)
  return {
    title: `Buy ${product.name}`,
    meta: [
      { name: 'description', content: product.description },
    ],
  }
}

文件约定

路由文件

文件路径含义
src/routes/index.tsx/首页
src/routes/about/index.tsx/about静态路由
src/routes/blog/[slug]/index.tsx/blog/:slug动态段
src/routes/docs/[...rest]/index.tsx/docs/*捕获所有
src/routes/(marketing)/index.tsx/分组(括号目录不映射 URL)
src/routes/layout.tsx-根 layout
src/routes/admin/layout.tsx-嵌套 layout(仅 /admin/*)
src/routes/api/posts/index.ts/api/postsAPI endpoint(无 default export)
src/routes/404.tsx-404 页面
src/routes/500.tsx-500 错误页面
src/routes/sitemap.xml/index.ts/sitemap.xml自定义响应

入口文件

文件用途
src/root.tsx应用根(含 <html> / <head> / <body>
src/entry.ssr.tsxSSR 渲染入口(被 adapter 调用)
src/entry.dev.tsx开发模式入口
src/entry.preview.tsx本地预览入口
src/entry.{adapter}.tsx各 adapter 的运行时入口
src/global.css全局样式
src/plugin@*.ts全局 middleware(在所有 layout 之前)

配置文件

文件用途
vite.config.ts主 Vite 配置
adapters/{name}/vite.config.ts各 adapter 的 Vite 配置
tsconfig.jsonTypeScript 配置
tailwind.config.jsTailwind 配置(v3)/ v4 直接在 CSS 内
package.json含 scripts: start / build / preview / qwik

Optimizer 行为

$ 后缀的位置

类型示例自动 QRL 化
API 名以 $ 结尾component$(fn) / $(fn)第一参数
JSX 事件属性以 $ 结尾<button onClick$={fn}>属性值
Props 类型 QRL<T>onClick$: QRL<...>自动包装

闭包变量规则

变量类型$ 边界
const + 可序列化
let / var
const + class 实例❌(用 noSerialize
const + function❌(用 $() 包装)
模块级 export const✅(始终允许)
模块级未导出常量
import 的符号

Optimizer 输出

源代码:

tsx
export const Hello = component$((props: { name: string }) => {
  const count = useSignal(0)
  return (
    <div>
      Hello {props.name}
      <button onClick$={() => count.value++}>{count.value}</button>
    </div>
  )
})

构建后大致:

js
// main.js
export const Hello = componentQrl(
  qrl(() => import('./q-abc123.js'), 'Hello_component')
)

// q-abc123.js
export const Hello_component = (props) => {
  const count = useSignal(0)
  return _jsx('div', {
    children: ['Hello ', props.name,
      _jsx('button', {
        onClick$: qrl(() => import('./q-def456.js'), 'Hello_button_onClick', [count]),
        children: count.value,
      })
    ]
  })
}

// q-def456.js
export const Hello_button_onClick = () => {
  const [count] = useLexicalScope()
  count.value++
}

适配器速查

Vercel Edge

ts
// adapters/vercel-edge/vite.config.ts
import { vercelEdgeAdapter } from '@builder.io/qwik-city/adapters/vercel-edge/vite'

vercelEdgeAdapter({
  ssg: {
    include: ['/blog/*'],   // 这些路由 SSG 预渲染
    origin: 'https://example.com',
  },
})

Cloudflare Pages

ts
// adapters/cloudflare-pages/vite.config.ts
import { cloudflarePagesAdapter } from '@builder.io/qwik-city/adapters/cloudflare-pages/vite'

cloudflarePagesAdapter({
  ssg: { /* ... */ },
})

Node.js(Express / Fastify)

ts
// adapters/express/vite.config.ts
import { nodeServerAdapter } from '@builder.io/qwik-city/adapters/node-server/vite'

nodeServerAdapter({ name: 'express' })

Static SSG

ts
// adapters/static/vite.config.ts
import { staticAdapter } from '@builder.io/qwik-city/adapters/static/vite'

staticAdapter({
  origin: 'https://example.com',
})

Bun / Deno

ts
// adapters/bun/vite.config.ts
import { bunServerAdapter } from '@builder.io/qwik-city/adapters/bun-server/vite'
bunServerAdapter({ name: 'bun' })

// adapters/deno/vite.config.ts
import { denoServerAdapter } from '@builder.io/qwik-city/adapters/deno-server/vite'
denoServerAdapter({ name: 'deno' })

命名约定

类型推荐示例
组件PascalCaseCounter / UserCard
Loader hookuse{Name} + 类似动词后缀useUser / useProducts
Action hookuse{Action}ActionuseSignupAction / useLikeAction
Context IDcamelCase + Id 后缀themeContextId / userContextId
Context 命名反向域名'app.theme' / 'myapp.feature.user'
文件名kebab-casecounter.tsx / user-card.tsx
routeLoader$ 导出useXxxuseProduct 而非 productLoader
Server function动词为主greet / fetchUser
Slot 命名kebab 或 camelCaseq:slot="title" / q:slot="footer"

配置:vite.config.ts

ts
import { defineConfig } from 'vite'
import { qwikVite } from '@builder.io/qwik/optimizer'
import { qwikCity } from '@builder.io/qwik-city/vite'
import tsconfigPaths from 'vite-tsconfig-paths'

export default defineConfig(() => {
  return {
    plugins: [
      qwikCity({
        // 路由配置
        routesDir: 'src/routes',         // 默认
        platform: undefined,             // 平台特定(CF / Vercel)
        rewriteRoutes: [                  // i18n 路由重写
          // {
          //   prefix: 'zh',
          //   paths: { 'about': 'guanyu' },
          // },
        ],
      }),
      qwikVite({
        // Qwik 编译器配置
        srcDir: 'src',                   // 默认
        client: {
          devInput: 'src/entry.dev.tsx',
        },
      }),
      tsconfigPaths(),
    ],
    server: {
      port: 5173,
    },
    preview: {
      port: 4173,
    },
  }
})

校验器:Zod / Valibot

Zod

tsx
import { routeAction$, zod$, z } from '@builder.io/qwik-city'

export const useSignup = routeAction$(
  async (data) => {
    // data 类型:z.infer<schema>
    return await db.users.create(data)
  },
  zod$({
    email: z.string().email(),
    password: z.string().min(8),
    age: z.coerce.number().min(18),
    role: z.enum(['user', 'admin']).default('user'),
  })
)

// 动态 schema(基于 RequestEvent)
zod$((z, ev) => {
  const isAdmin = ev.sharedMap.get('user')?.role === 'admin'
  return z.object({
    name: z.string(),
    role: isAdmin ? z.enum(['user', 'admin']) : z.literal('user'),
  })
})

Valibot

tsx
import { routeAction$, valibot$ } from '@builder.io/qwik-city'
import * as v from 'valibot'

export const useSignup = routeAction$(
  async (data) => { /* ... */ },
  valibot$(
    v.object({
      email: v.pipe(v.string(), v.email()),
      password: v.pipe(v.string(), v.minLength(8)),
    })
  )
)

测试

单元测试(Vitest)

bash
pnpm add -D vitest @builder.io/qwik
ts
// counter.test.ts
import { describe, expect, test } from 'vitest'
import { createDOM } from '@builder.io/qwik/testing'
import { Counter } from './counter'

describe('Counter', () => {
  test('renders initial value', async () => {
    const { screen, render } = await createDOM()
    await render(<Counter />)
    expect(screen.outerHTML).toContain('Clicked 0 times')
  })

  test('increments on click', async () => {
    const { screen, render, userEvent } = await createDOM()
    await render(<Counter />)
    await userEvent('button', 'click')
    expect(screen.outerHTML).toContain('Clicked 1 times')
  })
})

E2E(Playwright)

bash
pnpm create playwright@latest
ts
// e2e/home.spec.ts
import { test, expect } from '@playwright/test'

test('home page loads', async ({ page }) => {
  await page.goto('/')
  await expect(page).toHaveTitle(/Qwik/)
  await page.getByRole('button', { name: '+1' }).click()
})

常见 import 来源速查

ts
// === Qwik core ===
import {
  component$,
  $,
  useSignal,
  useStore,
  useComputed$,
  useResource$,
  useTask$,
  useVisibleTask$,
  useContext,
  useContextProvider,
  createContextId,
  useStyles$,
  useStylesScoped$,
  useOn,
  useOnDocument,
  useOnWindow,
  useId,
  useConstant,
  noSerialize,
  type NoSerialize,
  type Signal,
  type QRL,
  type Component,
  type PropFunction,
  Slot,
  Resource,
  Fragment,
  sync$,
  event$,
  implicit$FirstArg,
  useErrorBoundary,
  useTaskQrl,
  untrack,
} from '@builder.io/qwik'

// === Build-time constants ===
import { isServer, isBrowser, isDev } from '@builder.io/qwik/build'

// === SSR ===
import {
  renderToString,
  renderToStream,
  type RenderToStreamOptions,
} from '@builder.io/qwik/server'

// === Optimizer ===
import { qwikVite } from '@builder.io/qwik/optimizer'

// === Qwik City ===
import {
  // Hooks
  useLocation,
  useNavigate,
  useContent,
  useDocumentHead,
  // Loaders / Actions / Server
  routeLoader$,
  routeAction$,
  globalAction$,
  server$,
  // Validators
  validator$,
  zod$,
  z,
  valibot$,
  // Components
  QwikCityProvider,
  QwikCityMockProvider,
  RouterOutlet,
  Link,
  Form,
  ServiceWorkerRegister,
  // Types
  type DocumentHead,
  type RequestHandler,
  type RequestEvent,
  type RouteLocation,
  type Cookie,
} from '@builder.io/qwik-city'

// === Qwik City Vite plugin ===
import { qwikCity } from '@builder.io/qwik-city/vite'

// === Image (optional) ===
import { Image } from '@unpic/qwik'
import { Image as QwikImage } from 'qwik-image'

与同类元框架对比

维度QwikNext.js 15Nuxt 4SvelteKitSolid StartAstro 5
底层 UIQwikReactVueSvelteSolid多框架
Server Components-RSCNitro--island
启动模式ResumableHydration(含 RSC)HydrationHydrationHydrationIsland
首屏 JS~1KB数十 KB数十 KB数 KB数 KB0
数据加载routeLoader$fetch 在 RSCuseFetch+page.server.tscreateAsyncAstro.props
表单routeAction$ + <Form>Server ActionsForm 提交Form Actionsaction()-
RPCserver$Server ActionsdefineEventHandler+server.tsserver functionendpoint
路由文件文件文件文件文件文件
部署12+Vercel + 多Nitro 多adapter 多adapter 多静态 + 多
心智Resumability + $RSC + HydrationComposition + Auto-importStores + ReactiveSignals静态优先
适合内容站 / 电商 / 极致 TTI通用通用现代 SPA高性能 SPA内容站 / 文档

性能基准(典型场景)

Bundle Size 对比(生产构建后)

框架Hello World含路由 + 表单含 100 组件
Qwik1-2 KB(启动)1-2 KB(启动)1-2 KB(启动)
React + Next80 KB120 KB250 KB+
Vue + Nuxt60 KB90 KB200 KB+
Svelte + SvelteKit15 KB30 KB80 KB
Solid + Start10 KB25 KB70 KB

注意:Qwik 的「启动」JS 不变,但用户交互累计下载量与其他框架相近——只是「分摊到交互」而非「一次性下载」。

TTI(Time to Interactive)

框架简单页中等页复杂页
Qwik<100ms<100ms<100ms
Astro Island0(无 JS)/ <100ms(有 island)同左同左
Next.js 15(RSC)200ms500ms1.5s
SvelteKit150ms400ms1.2s
Nuxt 4200ms600ms1.8s

学习资源

Qwik 1.x → 2.0 主要差异(待定)

1.x2.0(预期)
@builder.io/qwik@qwik.dev/core
@builder.io/qwik-city@qwik.dev/router(合并)
useTask$可能保留 / 改名待定
useVisibleTask$标记 deprecated(推荐 useTask$ + useOnVisible$
routeLoader$保留
server$增强(更好的流式 / 类型)

Qwik 2.0 仍在 alpha 阶段,迁移时官方会提供 codemod 工具。建议生产环境继续用 1.x stable。