Qwik官方入门教程(1)

距离上次看qwik已经过去一年多的时间了,当时qwik才刚出没多久,那时候还是v0.1还是0.2的版本,还有很多bug就没兴趣研究了。如今过去一年多了,qwik版本已经到达了正式版,api也相对固定下来了,所有又有兴趣研究一下。

至于qwik是什么、对比别的框架有什么优势这里我就不重复提了,在掘金站内一搜一大堆。下面直接根据官方教程开始入门。这里推荐有一定的React基础(懂基本的jsx语法)、Vue3响应式基础(会用ref、watch等)、TS基础的小伙伴观看。

官方入门原文:qwik.builder.io/docs/gettin...

前置条件

要在本地开始使用Qwik,你需要以下内容:

国内网络环境的需要先设置sharp国内代理,不然可能安装依赖失败:

shell 复制代码
npm config set sharp_binary_host "https://npmmirror.com/mirrors/sharp"
npm config set sharp_libvips_binary_host "https://npmmirror.com/mirrors/sharp-libvips"

通过cli创建一个app

在你打算新建项目的路径,打开shell或者cmd,执行下面其中一个命令(按照你平时习惯选一个):

shell 复制代码
npm create qwik@latest
pnpm create qwik@latest
yarn create qwik
bun create qwik@latest

然后就会通过交互式的对话来创建项目,这里先全面选默认选项,一直下一步直到常见项目完成,会提示你cd到qwik-app文件夹,安装依赖,比如你用了pnpm创建,那么会提示你:

shell 复制代码
cd qwik-app
pnpm install
pnpm start

执行完start之后,会启动本地开发模式,这时候也会帮你打开网页,这样整个项目就创建好并启动了。

简单的HelloWorld应用

这里先简单的在页面上显示HelloWorld,然后再从一言网址拉取一些名言或者网络流行句子进行展示。

创建一个路由

这一步要基于Qwik的元框架Qwik-city,他能根据项目的目录提供路由。

  1. 在项目的src/routes目录下创建一个新的文件夹:sentence,并且在里面创建一个新文件 index.tsx.
  2. 每个路由下的index.tsx都需要包含:export default component$(...),所以复制下面代码到上面新建的文件
ts 复制代码
// src/sentence/joke/index.tsx
import { component$ } from '@builder.io/qwik';
 
export default component$(() => {
  return <section class="section bright">Hello World!</section>;
});
  1. 在浏览器打开http://127.0.0.1:5173/sentence/

你的sentence路由组件现在被一个默认的布局包裹住,有关什么是布局以及如何使用布局的更多详细信息,请参阅布局

有关如何编写组件的更多细节,请参阅组件API部分

加载数据

我们使用一言的api,从一言拉取一些句子。我们通过 route loader 在服务器拉取数据,然后在浏览器进行渲染。

将上面的index.tsx改成如下:

ts 复制代码
import { component$ } from '@builder.io/qwik';
import { routeLoader$ } from '@builder.io/qwik-city';
 
export const useHitokoto = routeLoader$(async () => {
  // 去一言拉取数据
  const response = await fetch('https://v1.hitokoto.cn/', {
    headers: { Accept: 'application/json' },
  });
  return (await response.json()) as {
    id: string;
    hitokoto: number;
    from: string;
  };
});

export default component$(() => {
  // 调用 `useHitokoto` 钩子, 会返回一个响应式信号量然后加载数据.
  const sentenceSignal = useHitokoto();
  return (
    <section class="section bright">
      <p>{sentenceSignal.value.hitokoto} --{sentenceSignal.value.from}</p>
    </section>
  );
});

保存代码之后再去浏览器查看:http://127.0.0.1:5173/sentence/

代码解析:

  • 通过routeLoader$调用的函数,都会在组件渲染前调用,然后渲染成html传到浏览器进行加载渲染。
  • routeLoader$会返回一个use钩子(use-hook),比如上面可以通过useHitokoto()拿到服务器返回来的数据。

注意

routeLoader$会在任何组件渲染前进行调用,也就是说,export default component$(...)里面就算不写const sentenceSignal = useHitokoto();routeLoader$里的函数也会被调用。

routeLoader$可以根据返回类型进行推导,所以下面的sentenceSignal能得到正确的类型,这也是为什么为什么要在return进行ts的as断言。

提交数据到服务器

在前面,我们通过routeLoader$从服务器拉取数据,下面我们通过routeAction$从浏览器将数据发送到服务器。

注意: routeAction$是向服务器发送数据的首选方式,因为它使用浏览器原生表单API,即使JavaScript被禁用也能正常工作。

下面我们定义一个action,并且在组件用到这个action:

ts 复制代码
import { component$ } from '@builder.io/qwik';
import { routeLoader$, Form, routeAction$ } from '@builder.io/qwik-city';
 
export const useHitokoto = routeLoader$(async () => {
  const response = await fetch('https://v1.hitokoto.cn/', {
    headers: { Accept: 'application/json' },
  });
  return (await response.json()) as {
    id: string;
    hitokoto: number;
    from: string;
  };
});

export const useSentenceVoteAction = routeAction$((props) => {
    console.log('投票', props)
})

export default component$(() => {
  // 调用 `useHitokoto` 钩子, 会返回一个响应式信号量然后加载数据.
  const sentenceSignal = useHitokoto();
  const favoriteSentenceAction = useSentenceVoteAction();
  return (
    <section class="section bright">
      <p>{sentenceSignal.value.hitokoto} ------{sentenceSignal.value.from}</p>
      <Form action={favoriteSentenceAction}>
        <input type="hidden" name="id" value={sentenceSignal.value.id} />
        <input type="hidden" name="sentence" value={sentenceSignal.value.hitokoto} />
        <button name="vote" value="up">👍</button>
        <button name="vote" value="down">👎</button>
      </Form>
    </section>
  );
});

保存代码,页面多出两个按钮,随便点一个,再查看服务端有没有打印:

代码解析:

  • routeAction$ 接收数据.
    • 传递给routeAction$的函数在发送表单时就会在服务器上调用。
    • routeAction$返回一个use-hook, favoriteSentenceAction,你可以在组件中使用它来发送表单数据。
  • Form是一个方便的组件,它封装了浏览器的原生<form>元素

修改状态

类似Vue3的ref,Qwik提供了一个hook:useSignal,用来保存状态,并且提供响应式。下面来使用一下:

  1. qwik 导入 useSignalimport { component$, useSignal } from "@builder.io/qwik";
  2. 在组件定义里面定义这个signal:const isFavoriteSignal = useSignal(false);
  3. 在Form的关闭标签后面添加一个按钮,用于修改状态

最终代码变成:

ts 复制代码
import { component$, useSignal } from '@builder.io/qwik';
import { routeLoader$, Form, routeAction$ } from '@builder.io/qwik-city';
 
export const useHitokoto = routeLoader$(async () => {
  const response = await fetch('https://v1.hitokoto.cn/', {
    headers: { Accept: 'application/json' },
  });
  return (await response.json()) as {
    id: string;
    hitokoto: number;
    from: string;
  };
});

export const useSentenceVoteAction = routeAction$((props) => {
    console.log('投票', props)
})

export default component$(() => {
  // 调用 `useHitokoto` 钩子, 会返回一个响应式信号量然后加载数据.
  const sentenceSignal = useHitokoto();
  const favoriteSentenceAction = useSentenceVoteAction();
  const isFavoriteSignal = useSignal(false);
  return (
    <section class="section bright">
      <p>{sentenceSignal.value.hitokoto} ------{sentenceSignal.value.from}</p>
      <Form action={favoriteSentenceAction}>
        <input type="hidden" name="id" value={sentenceSignal.value.id} />
        <input type="hidden" name="sentence" value={sentenceSignal.value.hitokoto} />
        <button name="vote" value="up">👍</button>
        <button name="vote" value="down">👎</button>
      </Form>
      <button
        onClick$={() => (isFavoriteSignal.value = !isFavoriteSignal.value)}
      >
        {isFavoriteSignal.value ? '❤️' : '🤍'}
      </button>
    </section>
  );
});

监听状态变化并调用服务端函数

在Qwik中,任务(task)是在状态发生变化时需要执行的工作(这类似于其他框架中的"effect")。在本例中,我们使用任务来调用服务端上的代码。

  1. qwik 导入 useTask$: import { component$, useSignal, useTask$ } from "@builder.io/qwik";
  2. 创建一个task来监听isFavoriteSignal的状态变化:
ts 复制代码
useTask$(({ track }) => { 
  track(() => isFavoriteSignal.value);
});
  1. 添加要在状态更改时执行的代码:
ts 复制代码
useTask$(({ track }) => { 
  track(() => isFavoriteSignal.value);
  console.log('FAVORITE (isomorphic)', isFavoriteSignal.value);
});
  1. 如果你希望在服务器上也进行执行某些代码,那么将这些封装在server$()中。
ts 复制代码
useTask$(({ track }) => { 
  track(() => isFavoriteSignal.value);
  console.log('FAVORITE (isomorphic)', isFavoriteSignal.value);
  server$(() => { console.log('FAVORITE (server)', isFavoriteSignal.value); })();
});

最后代码变成:

ts 复制代码
import { component$, useSignal, useTask$ } from '@builder.io/qwik';
import { routeLoader$, Form, routeAction$, server$ } from '@builder.io/qwik-city';
 
export const useHitokoto = routeLoader$(async () => {
  const response = await fetch('https://v1.hitokoto.cn/', {
    headers: { Accept: 'application/json' },
  });
  return (await response.json()) as {
    id: string;
    hitokoto: number;
    from: string;
  };
});

export const useSentenceVoteAction = routeAction$((props) => {
    console.log('投票', props)
})

export default component$(() => {
  // 调用 `useHitokoto` 钩子, 会返回一个响应式信号量然后加载数据.
  const sentenceSignal = useHitokoto();
  const favoriteSentenceAction = useSentenceVoteAction();
  const isFavoriteSignal = useSignal(false);
  useTask$(({ track }) => {
    track(() => isFavoriteSignal.value);
    console.log('FAVORITE (isomorphic)', isFavoriteSignal.value);
    server$(() => {
      console.log('FAVORITE (server)', isFavoriteSignal.value);
    })();
  });
  return (
    <section class="section bright">
      <p>{sentenceSignal.value.hitokoto} ------{sentenceSignal.value.from}</p>
      <Form action={favoriteSentenceAction}>
        <input type="hidden" name="id" value={sentenceSignal.value.id} />
        <input type="hidden" name="sentence" value={sentenceSignal.value.hitokoto} />
        <button name="vote" value="up">👍</button>
        <button name="vote" value="down">👎</button>
      </Form>
      <button
        onClick$={() => (isFavoriteSignal.value = !isFavoriteSignal.value)}
      >
        {isFavoriteSignal.value ? '❤️' : '🤍'}
      </button>
    </section>
  );
});

注意:

组件中的useTask$会在服务端和客户端(浏览器)中执行一次。

当用户单击按钮时,浏览器会打印:FAVORITE (isomorphic) true,服务端打印:FAVORITE (server) true

CSS样式

Qwik提供了一种将样式与组件关联并限定其范围的方法(类似Vue的scoped)。

  1. 创建一个css文件,src/routes/sentence/index.css
css 复制代码
p {
  font-weight: bold;
}

form {
  float: right;
}
  1. 导入样式:import styles from "./index.css?inline";
  2. 从qwik导入useStylesScoped$: import { component$, useSignal, useStylesScoped$, useTask$ } from "@builder.io/qwik";
  3. 告诉组件加载样式:useStylesScoped$(styles);

最后的代码:

ts 复制代码
import { component$, useSignal, useTask$, useStylesScoped$ } from '@builder.io/qwik';
import { routeLoader$, Form, routeAction$, server$ } from '@builder.io/qwik-city';
import styles from './index.css?inline'

export const useHitokoto = routeLoader$(async () => {
  const response = await fetch('https://v1.hitokoto.cn/', {
    headers: { Accept: 'application/json' },
  });
  return (await response.json()) as {
    id: string;
    hitokoto: number;
    from: string;
  };
});

export const useSentenceVoteAction = routeAction$((props) => {
    console.log('投票', props)
})

export default component$(() => {
  // 调用 `useHitokoto` 钩子, 会返回一个响应式信号量然后加载数据.
  const sentenceSignal = useHitokoto();
  const favoriteSentenceAction = useSentenceVoteAction();
  const isFavoriteSignal = useSignal(false);
  useTask$(({ track }) => {
    track(() => isFavoriteSignal.value);
    console.log('FAVORITE (isomorphic)', isFavoriteSignal.value);
    server$(() => {
      console.log('FAVORITE (server)', isFavoriteSignal.value);
    })();
  });
  useStylesScoped$(styles)
  return (
    <section class="section bright">
      <p>{sentenceSignal.value.hitokoto} ------{sentenceSignal.value.from}</p>
      <Form action={favoriteSentenceAction}>
        <input type="hidden" name="id" value={sentenceSignal.value.id} />
        <input type="hidden" name="sentence" value={sentenceSignal.value.hitokoto} />
        <button name="vote" value="up">👍</button>
        <button name="vote" value="down">👎</button>
      </Form>
      <button
        onClick$={() => (isFavoriteSignal.value = !isFavoriteSignal.value)}
      >
        {isFavoriteSignal.value ? '❤️' : '🤍'}
      </button>
    </section>
  );
});

效果:

上面就是Qwik官方文档的入门教程,有兴趣赶紧去试试吧

相关推荐
崔庆才丨静觅16 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606116 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了17 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅17 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅17 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅17 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment17 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅18 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊18 小时前
jwt介绍
前端
爱敲代码的小鱼18 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax