• Qwik开发使用入门


    前言

    前面我们介绍了一下 Qwik 这个新的框架,如果没有看过的童鞋可以移步这里(追求极致性能!Qwik 1.0版本发布)。

    本文结合官方的教学文档,翻译整理,原文链接在文末。

    安装Qwik的前提条件

    1. 本地安装 Node.js v16.8 或更高版本
    2. 有一个开发工具,例如:Visual Studio Code

    第一步 使用CLI安装应用

    首先,使用 Qwik CLI 创建一个 Qwik 应用程序,它会生成一个空白的启动程序。Qwik 支持 NPM、yarn 和 pnpm。选择你喜欢的软件包管理器,然后运行以下命令(选其一):

    npm create qwik@latest
    pnpm create qwik@latest
    yarn create qwik
    bun create qwik@latest
    
    • 1
    • 2
    • 3
    • 4

    安装过程中会提示一些安装依赖项等相关问题,大家根据自己需求选择。将黑窗口切换到下载的目录下,运行下面的命令(选其一),即可启动本地服务:

    npm start
    pnpm start
    yarn start
    bun start
    
    • 1
    • 2
    • 3
    • 4

    第二步 创建一个笑话应用程序

    Qwik 教程将指导您使用 Qwik 创建一个笑话应用程序,同时涵盖最重要的 Qwik 概念。该应用程序从 https://icanhazdadjoke.com 中随机显示一个笑话,并设有一个按钮,点击后即可获得一个新笑话。

    1 创建一个路由

    首先在特定路由上提供一个页面。这个基本应用程序在 /joke/ 路由上随机提供一个爸爸笑话应用程序。本教程依赖 Qwik 的元框架 Qwikcity,它使用基于目录的路由。开始使用

    在你的项目中,在 /src/routes/ 中创建一个 /joke/ 目录,其中包含一个 index.tsx 文件。每个路由的 index.tsx 文件都必须有export default component$(...),以便 Qwikcity 知道要提供哪些内容。将以下内容粘贴到 src/routes/joke/index.tsx

    import { component$ } from '@builder.io/qwik';
     
    export default component$(() => {
      return <section class="section bright">A Joke!</section>;
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5

    浏览器打开 http://127.0.0.1:5173/joke/ 这个链接,查看是否可以正常工作。

    注意:
    您的笑话路由默认组件已被现有布局包围。请参阅 “布局”,了解有关什么是布局以及如何使用布局的更多详情。有关如何编写组件的更多详情,请参阅组件 API 部分。

    2 加载数据

    我们将使用 https://icanhazdadjoke.com 上的外部 JSON API 来加载随机笑话。我们将使用路由加载器在服务器中加载这些数据,然后在组件中渲染。

    打开 src/routes/joke/index.tsx,添加以下代码:

    import { component$ } from '@builder.io/qwik';
    import { routeLoader$ } from '@builder.io/qwik-city';
     
    export const useDadJoke = routeLoader$(async () => {
      const response = await fetch('https://icanhazdadjoke.com/', {
        headers: { Accept: 'application/json' },
      });
      return (await response.json()) as {
        id: string;
        status: number;
        joke: string;
      };
    });
     
    export default component$(() => {
      // Calling our `useDadJoke` hook, will return a reactive signal to the loaded data.
      const dadJokeSignal = useDadJoke();
      return (
        <section class="section bright">
          <p>{dadJokeSignal.value.joke}</p>
        </section>
      );
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    现在查看浏览器,会随机显示一个笑话。

    代码解释:
    传递给 routeLoader$ 的函数会在任何组件渲染之前在服务器上紧急调用,并负责加载数据。routeLoader$ 返回一个使用挂钩 useDadJoke(),可在组件中用于检索服务器数据。

    注意:

    • 在渲染任何组件之前,服务器都会急切地调用 routeLoader$,即使在任何组件中都未调用其使用钩子。
    • RouteLoader$ 的返回类型会在组件中推断,无需任何额外的类型信息。

    3 向服务器发送数据

    之前,我们使用 routeLoader$ 将数据从服务器发送到客户端。要将数据从客户端发回服务器,我们使用 routeAction$

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

    要声明一个动作,请添加以下代码:

    import { routeLoader$, Form, routeAction$ } from '@builder.io/qwik-city';
     
    export const useJokeVoteAction = routeAction$((props) => {
      // Leave it as an exercise for the reader to implement this.
      console.log('VOTE', props);
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    更新 export default 组件,以便在

    中使用 useJokeVoteAction 挂钩。

    export default component$(() => {
      const dadJokeSignal = useDadJoke();
      const favoriteJokeAction = useJokeVoteAction();
      return (
        <section class="section bright">
          <p>{dadJokeSignal.value.joke}</p>
          <Form action={favoriteJokeAction}>
            <input type="hidden" name="jokeID" value={dadJokeSignal.value.id} />
            <button name="vote" value="up">👍</button>
            <button name="vote" value="down">👎</button>
          </Form>
        </section>
      );
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    现在,在 http://localhost:5173/joke/ 上显示按钮,如果点击按钮,其值就会记录到控制台。

    代码解释:

    • routeAction$ 接收数据。
    • 每当张贴表单时,服务器就会调用传递给 routeAction$ 的函数。
    • routeAction$ 返回一个使用挂钩,即 useJokeVoteAction,你可以在组件中使用它来发布表单数据。
    • Form 是一个方便的组件,它封装了浏览器的本地 元素。

    需要注意的事项:

    • 有关验证,请参阅 zod validation
    • 即使禁用了 JavaScriptrouteAction$ 也能正常工作。
    • 如果启用了 JavaScriptForm 组件将阻止浏览器发布表单,而是使用 JavaScript 发布数据,并在不完全刷新的情况下模拟浏览器的本地表单行为。

    4 修改状态

    跟踪状态和更新用户界面是应用程序的核心工作。Qwik 提供了一个 useSignal 钩子来跟踪应用程序的状态。要了解更多信息,请参阅状态管理。

    qwik 引入 useSignal

    import { component$, useSignal } from "@builder.io/qwik";
    
    • 1

    使用 useSignal() 声明组件的状态

    const isFavoriteSignal = useSignal(false);
    
    • 1

    为组件添加一个按钮,以修改状态

    <button
     onClick$={() => {
       isFavoriteSignal.value = !isFavoriteSignal.value;
     }}>
      {isFavoriteSignal.value ? '❤️' : '🤍'}
    </button>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    5 任务和调用服务器代码

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

    qwik 引入 useTask$

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

    创建跟踪 isFavoriteSignal 状态的新任务

    useTask$(({ track }) => {});
    
    • 1

    添加 track 调用,以便在 isFavoriteSignal 状态改变时重新执行任务。

    useTask$(({ track }) => {
      track(() => isFavoriteSignal.value);
    });
    
    • 1
    • 2
    • 3

    添加要在状态变化时执行的工作

    useTask$(({ track }) => {
      track(() => isFavoriteSignal.value);
      console.log('FAVORITE (isomorphic)', isFavoriteSignal.value);
    });
    
    • 1
    • 2
    • 3
    • 4

    如果只想让工作在服务器上进行,则用 server$() 进行封装

    useTask$(({ track }) => {
      track(() => isFavoriteSignal.value);
      console.log('FAVORITE (isomorphic)', isFavoriteSignal.value);
      server$(() => {
        console.log('FAVORITE (server)', isFavoriteSignal.value);
      })();
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    注意:

    • useTask$ 的主体在服务器和客户端上都会执行(同构)。
    • SSR 上,服务器会打印 FAVORITE (isomorphic) falseFAVORITE (server) false
    • 当用户与 favorite 交互时,客户端会打印 FAVORITE (isomorphic) true,服务器会打印 FAVORITE (server) true

    6 样式

    样式是任何应用程序的重要组成部分。Qwik 提供了一种将样式与组件关联起来并设定其范围的方法。

    创建文件 src/routes/joke/index.css

    p {
      font-weight: bold;
    }
    form {
      float: right;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在文件 src/routes/joke/index.tsx 中引入样式文件

    import styles from "./index.css?inline";
    
    • 1

    qwik 导入 useStylesScoped$

    import { component$, useSignal, useStylesScoped$, useTask$ } from "@builder.io/qwik";
    
    • 1

    告诉组件加载样式

    useStylesScoped$(styles);
    
    • 1

    代码解释:

    • ?inline 参数告诉 Vite 将样式内联到组件中。
    • useStylesScoped$ 调用告诉 Qwik 仅将样式与组件关联(范围)。
    • 只有当样式尚未作为 SSR 的一部分内联时,才会被加载,而且只针对第一个组件。

    本节的完整代码片段如下,以供参考:

    import { component$, useSignal, useStylesScoped$, useTask$ } from '@builder.io/qwik';
    import { routeLoader$, Form, routeAction$, server$ } from '@builder.io/qwik-city';
    import styles from './index.css?inline';
     
    export const useDadJoke = routeLoader$(async () => {
      const response = await fetch('https://icanhazdadjoke.com/', {
        headers: { Accept: 'application/json' },
      });
      return (await response.json()) as {
        id: string;
        status: number;
        joke: string;
      };
    });
     
    export const useJokeVoteAction = routeAction$((props) => {
      console.log('VOTE', props);
    });
     
    export default component$(() => {
      useStylesScoped$(styles);
      const isFavoriteSignal = useSignal(false);
      // Calling our `useDadJoke` hook, will return a reactive signal to the loaded data.
      const dadJokeSignal = useDadJoke();
      const favoriteJokeAction = useJokeVoteAction();
      useTask$(({ track }) => {
        track(() => isFavoriteSignal.value);
        console.log('FAVORITE (isomorphic)', isFavoriteSignal.value);
        server$(() => {
          console.log('FAVORITE (server)', isFavoriteSignal.value);
        })();
      });
      return (
        <section class="section bright">
          <p>{dadJokeSignal.value.joke}</p>
          <Form action={favoriteJokeAction}>
            <input type="hidden" name="jokeID" value={dadJokeSignal.value.id} />
            <button name="vote" value="up">👍</button>
            <button name="vote" value="down">👎</button>
          </Form>
          <button
            onClick$={() => (isFavoriteSignal.value = !isFavoriteSignal.value)}
          >
            {isFavoriteSignal.value ? '❤️' : '🤍'}
          </button>
        </section>
      );
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    7 预览

    我们创建了一个最小的应用程序,让您对 Qwik 的关键概念和 API 有一个大致的了解。该应用程序运行在开发模式下,使用热模块重载(HMR)在更改代码的同时持续更新应用程序。

    在开发模式下:

    • 每个文件都是单独加载的,这可能会导致网络选项卡中出现瀑布流。
    • 捆绑包没有投机加载,因此第一次交互时可能会有延迟。

    让我们创建一个能消除这些问题的生产构建。

    创建预览版

    运行 npm run preview 创建生产构建。

    注意:

    • 您的应用程序现在应已完成生产构建,并在不同的端口上运行。
    • 如果您现在与应用程序交互,开发工具的网络选项卡应显示捆绑包已从 ServiceWorker 缓存中即时交付。

    总结

    恭喜!你已经完成了 Qwik 的基础入门教学!希望这篇文章对你有所帮助,更多的 Qwik 知识,需要在实战中学习掌握。

    下面是 Qwik 中各个 API 功能列表和解释:

    1. component$ —— 每个组件导出时,必须使用这个函数
    2. routeLoader$ —— 任何组件渲染之前在服务器上紧急调用,并负责加载数据。并返回一个挂钩函数,可在组件中用于检索服务器数据。
    3. routeAction$ —— 将数据从客户端发回服务器。routeAction$ 是向服务器发送数据的首选方式,因为它使用的是浏览器本地表单 API,即使禁用 JavaScript 也能正常工作。
    4. useSignal —— 跟踪应用程序的状态。
    5. useTask$ —— 类似 effect。当状态发生变化时需要执行某些工作时,可以使用该 API,并且该 API 还提供了:状态变化时,服务器执行某些动作的功能(配合 server$ 使用)。
    6. useStylesScoped$ —— 引入样式文件时使用

    参考链接:
    qwik 官方教程:https://qwik.builder.io/docs/getting-started/

    文章首发地址:
    Qwik开发使用入门 - Cikayo

  • 相关阅读:
    Flutter循序渐进==>数据结构(列表、映射和集合)和错误处理
    如何保护你的网络安全?
    /LatestBuild: Operation not permitted
    前端常用布局方式大全——细致讲解
    【云原生】Docker数据卷学习
    python代码实现论文〖文献引用顺序〗修改校对
    第六节:数组的定义与使用【java】
    ubuntu 20.04 搭建crash dump问题分析环境
    【C++进阶】map和set( 万字详解)—— 上篇
    LinkedHashMap 源码分析
  • 原文地址:https://blog.csdn.net/weixin_42452015/article/details/133945960