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官方文档的入门教程,有兴趣赶紧去试试吧

相关推荐
Calm5501 分钟前
Vue3:uv-upload图片上传
前端·vue.js
浮游本尊5 分钟前
Nginx配置:如何在一个域名下运行两个网站
前端·javascript
m0_748239836 分钟前
前端bug调试
前端·bug
m0_748232928 分钟前
[项目][boost搜索引擎#4] cpp-httplib使用 log.hpp 前端 测试及总结
前端·搜索引擎
新中地GIS开发老师13 分钟前
《Vue进阶教程》(12)ref的实现详细教程
前端·javascript·vue.js·arcgis·前端框架·地理信息科学·地信
m0_7482495416 分钟前
前端:base64的作用
前端
html组态22 分钟前
web组态可视化编辑器
前端·物联网·编辑器·web组态·组态·组态软件
~央千澈~29 分钟前
如果你的网站是h5网站,如何将h5网站变成小程序-除开完整重做方法如何快速h5转小程序-h5网站转小程序的办法-优雅草央千澈
前端·apache
m0_7482398341 分钟前
基于web的音乐网站(Java+SpringBoot+Mysql)
java·前端·spring boot
时雨h1 小时前
RuoYi-ue前端分离版部署流程
java·开发语言·前端