用SolidJS和TypeScript建立一个任务跟踪器的教程

275 阅读11分钟

SolidJS正迅速成为网络开发社区的关注中心。凭借其直截了当的状态管理、细粒度的反应性和高性能,SolidJS已将自己置于其他JavaScript框架的基座上。

SolidJS 是 React 开发者所要求的一切,在这篇文章中,我将指导您用 SolidJS 建立一个任务跟踪器

先决条件

要跟上此教程,您需要了解 JavaScript 和 TypeScript、Node.js 模块以及前端框架的组件。

为什么是 SolidJS?

如果您以前曾使用过 React,SolidJS 将看起来非常熟悉。当 React Hooks 首次公布时,我非常高兴,因为我认为它将解决我们的状态管理危机。钩子使组件中的本地状态管理更容易,但全局状态管理仍然很复杂。

断开的组件仍然很难共享数据,而且出现了许多库来试图解决状态管理问题--这增加了开发疲劳,并给我们的代码库增加了不必要的复杂性。

我也看到同样的问题发生在其他前端框架中;就好像全局状态管理是事后的想法,而不是从一开始就计划好的。

有了 SolidJS,情况就不同了。全局状态管理就像创建状态和导出它一样容易。不需要任何复杂的设置或第三方库。

SolidJS 也使用 JSX,流行的 HTML 类语法扩展到 JavaScript。这使得处理 UI 逻辑、事件和状态变化变得简单明了。再加上,SolidJS 编译为普通 JavaScript,所以不需要虚拟 DOM,这使得它比 React 和 Angular 等框架相对更快。

SolidJS 也有一个简单的工作流程。组件只渲染一次,就像在 JavaScript 中一样,所以更容易预测你的代码的结果。

SolidJS的另一个巨大优势是,它建立在其他网络框架的肩膀上,所以它自豪地模仿了好的部分,并改进了不那么好的部分。

让我们继续设置我们的 SolidJS 应用程序,以逐步学习如何用 SolidJS 构建一个 Web 应用程序。

用 TypeScript 设置 SolidJS 应用程序

要在您的本地机器上设置一个 SolidJS 应用程序,您将需要安装Node.js。如果您已经安装了它,在您的终端上运行以下命令应返回您当前的 Node.js 版本。

node --version

接下来,让我们通过在终端运行以下命令来创建一个新的 SolidJS 应用程序。

npx degit solidjs/templates/ts task-tracker

使用solidjs/templates/ts 生成一个 Solid/TypeScript 应用程序。对于 JavaScript,您必须将该命令改为solidjs/templates/js

运行该命令后,您应该看到一个成功信息,看起来像这样:

> cloned solidjs/templates#HEAD to task-tracker

现在,继续在您选择的代码编辑器或IDE中打开生成的应用程序。下面是应用程序的结构应该是这样的:

View of the generated app structure

请注意,我们的 SolidJS 应用程序使用Vite作为其默认构建工具,使用pnpm作为默认软件包管理器。这些工具结合在一起,为组件渲染、应用程序启动时间和包管理提供了很好的开发体验。

我们的应用程序组件目前住在./src/App.tsx 文件内:

import type { Component } from 'solid-js'
...
const App: Component = () => {
  return (
    <div>
      ...
    </div>
  );
}

export default App

首先,我们从solid-js 导入Component 类型,然后将其作为我们的App 组件的类型。

SolidJS 中的组件是 JavaScript 函数。它们是可重复使用的,并可使用道具进行定制,道具类似于函数参数/参数。

./src/index.tsx 文件内,我们渲染我们的App 组件。

import { render } from 'solid-js/web'
import App from './App'

render(() => <App />, document.getElementById('root') as HTMLElement)

来自solid-js/webrender() 方法期望有两个参数:

  1. 一个函数,返回我们的<App /> 组件
  2. 一个HTML元素

当您导航到./index.html 文件时,您会看到根div 和通过<script /> 标签使用我们的./src/index.tsx 文件:

...
<body>
  <noscript>You need to enable JavaScript to run this app.</noscript>
  <div id="root"></div>
  <script src="/src/index.tsx" type="module"></script>
</body>

为了运行我们的 SolidJS 应用程序,我们必须首先通过在终端上运行命令pnpm install 来安装我们的软件包,然后pnpm dev 在开发模式下启动我们的应用程序。您应该看到一个成功信息,看起来像这样:

 vite v2.9.9 dev server running at:

 > Local: http://localhost:3001/
 > Network: use `--host` to expose

 ready in 378ms.

当您导航到 [http://localhost:3001](http://localhost:3001)或您终端上显示的 URL,您应该看到一个与此类似的页面:

Edit src/App.tsx and save to reload.

安装Bootstrap来设计我们的SolidJS应用程序

现在我们已经成功地设置了我们的 SolidJS 应用程序,让我们安装 Bootstrap 来进行样式设计,这样我们就不必为 CSS 而烦恼。

运行如下命令:

pnpm install bootstrap

接下来,我们将使用以下一行代码,在我们的./src/index.tsx 文件中导入Bootstrap。

import 'bootstrap/dist/css/bootstrap.min.css'

我们也可以删除当前的./index.css ,因为我们不需要它。我们的index.tsx 文件现在看起来应该是这样的:

import { render } from 'solid-js/web'
import App from './App'
import 'bootstrap/dist/css/bootstrap.min.css'

render(() => <App />, document.getElementById('root') as HTMLElement)

使用JJSX来构造我们的任务跟踪器

让我们使用JJSX来构造我们的任务跟踪器。在./src/App.tsx 文件中,将你目前的内容替换成这样:

import type { Component } from 'solid-js'

const App: Component = () => {
  return (
    <div class="container mt-5 text-center">
      <h1 class="mb-4">Whattodo!</h1>

      <form class="mb-5 row row-cols-2 justify-content-center">
        <input type="text" class="input-group-text p-1 w-25" placeholder="Add task here..." id="taskInput" required />

        <button class="btn btn-primary ms-3 w-auto" type="submit">
          Add task
        </button>
      </form>

      <div>
        <h4 class="text-muted mb-4">Tasks</h4>
        <div class="row row-cols-3 mb-3 justify-content-center">
          <button class="btn btn-danger w-auto">X</button>
          <div class="bg-light p-2 mx-2">Push code to GitHub</div>
          <input type="checkbox" role="button" class="form-check-input h-auto px-3" />
        </div>
      </div>
    </div>
  )
}
export default App

我们的JSX代码包含输入新任务的表格和任务部分。现在,我们使用的是硬编码数据,但我们将学习如何使我们的应用程序成为动态的,以便当用户在表单中输入新任务并点击提交按钮时,我们的 SolidJS 应用程序将用新数据进行更新。

当您回到您的浏览器时,您的页面现在应该看起来像这样:

SolidJS App in browser

接下来,让我们学习如何在 SolidJS 中创建和管理状态。我们将通过创建一个taskList 状态来做到这一点,我们还将创建函数,用于将新任务添加到我们的状态中,删除它们,并更新它们的完成状态。

在 SolidJS 中创建和更新状态

SolidJS 有一个createSignal Hook 来创建状态。作为一个例子,让我们创建一个taskList 状态来容纳我们的任务。在./src/App.tsx 文件内,我们将首先为每个任务创建一个类型。

const App: Component = () => {
  type Task = {
    text: string
    text: string
    completed: boolean
  }

  return (...)
}

接下来,我们将创建我们的taskList 状态。

import { Component, createSignal } from 'solid-js'

...
const [taskList, setTaskList] = createSignal([] as Task[])
...

createSignal() Hook返回一个包含两个变量的数组,taskListsetTaskList 。与你将看到的React Hooks不同,这两个变量都是函数。我们调用taskList() 函数来访问我们的任务数据,并调用setTaskList() 函数来更新我们的taskList 状态。

将任务添加到我们的状态中

现在我们已经创建了我们的taskList 状态,让我们创建一个函数来向我们的状态添加任务。我们将它命名为addTask

const [taskList, setTaskList] = createSignal([] as Task[])

const addTask = (e: Event) => {
  e.preventDefault()

  const taskInput = document.querySelector('#taskInput') as HTMLInputElement

  const newTask: Task = {
    id: Math.random().toString(36).substring(2),
    text: taskInput.value,
    completed: false,
  }

  setTaskList([newTask, ...taskList()])
  taskInput.value = ''
}

在我们的addTask() 函数中,我们首先使用e.preventDefault() 方法来防止我们提交表单时的默认重新加载行为。我们还从ID为 "taskInput "的<input /> 元素中获得我们的taskInput

对于每个新的任务,我们创建一个名为newTask 的对象,其属性为idtext ,和completed 。当一个新任务被创建时,我们的函数将使用Math.random() 方法为我们的任务ID生成一个随机字符串,并将默认的completed 值设置为false

最后,setTaskList() 函数将以一个数组作为参数被调用,将newTask 与当前的taskList 状态相附加。

让我们也创建一个删除任务的函数。

...
const deleteTask = (taskId: string) => {
  const newTaskList = taskList().filter((task) => task.id !== taskId)
  setTaskList(newTaskList)
}

当我们以任务ID为参数调用我们的deleteTask() 函数时,它将过滤我们的taskList 状态,并返回每个任务,除了我们想删除的ID。然后,setTaskList() 方法将被调用,并以新的任务列表作为其参数。

为了使用我们的addTask() 函数,我们将在JSX代码中的<form /> 标签上添加一个onSubmit 事件监听器,每当提交按钮被点击时,它将调用我们的函数。

...
return (
 <div class="container mt-5 text-center">
    <h1 class="mb-4">Whattodo!</h1>
    <form class="mb-5 row row-cols-2 justify-content-center" onSubmit={(e) => addTask(e)}>
     ...
    </form>
  </div>
)

接下来让我们看看,每当用户添加新任务时,我们如何在应用程序中显示我们的taskList 数据。

SolidJS 中的控制流和数据循环

SolidJS 有一个<For /> 组件,用于循环浏览数据。虽然 JavaScriptArray.map() 方法会起作用,但我们的组件在更新时总是会映射taskList 状态。有了<For /> 组件,我们的应用程序将只更新需要更新的 DOM 的确切部分。

让我们用这个来取代我们目前在任务div 中的内容。

...
<div>
  <h4 class="text-muted mb-4">Tasks</h4>
  <For each={taskList()}>
    {(task: Task) => (
      <div class="row row-cols-3 mb-3 justify-content-center">
        <button class="btn btn-danger w-auto">X</button>
        <div class="bg-light p-2 mx-2">{task.text}</div>
        <input type="checkbox" checked={task.completed} role="button" class="form-check-input h-auto px-3" />
      </div>
    )}
  </For>
</div>
...

注意我们是如何将我们的taskList 包装在<For /> 组件中的。我们还将任务文本从 "推送代码到GitHub "更新为task 参数中的task.text

现在我们可以继续使用我们之前创建的deleteTask() 方法。我们将为删除按钮添加一个onClick 事件监听器。

...
<button class="btn btn-danger w-auto" onclick={() => deleteTask(task.id)}>
  X
</button>
...

如果我们转到我们的浏览器,我们的 SolidJS 应用程序现在应该像这样工作:

Adding tasks by typing in the task bar

在我们的嵌套状态中更新任务状态

SolidJS 有一个createStore() Hook 用于创建和管理嵌套状态。但在我们谈论它之前,让我们看看我们如何能在我们的createSignal() 状态中对预先存在的任务进行更新。我们将创建一个名为toggleStatus 的新函数,就在deleteTask() 函数的下面。

...
const toggleStatus = (taskId: string) => {
  const newTaskList = taskList().map((task) => {
    if (task.id === taskId) {
      return { ...task, completed: !task.completed }
    }
    return task
  })
  setTaskList(newTaskList)
}

我们的toggleStatus() 函数希望有一个taskId 参数,我们将用它来获得我们想要标记为已完成或未完成的特定任务。我们还使用map() 方法来循环浏览我们的taskList 状态,如果我们找到与参数taskId 的ID相同的任务,我们将把它的completed 属性改为与当前相反。所以,如果是true ,我们就把它变成false ,如果是falsetrue

最后,我们使用setTaskList() 方法,用我们新的taskList 数据更新taskList 状态。

在使用我们的toggleStatus() 函数之前,让我们在JSX代码中添加已完成的任务和未完成的任务之间的区别。如果任务文本的completed 属性是true ,我们将为其添加Bootstrap类*"text-decoration-line-through text-success"*。在我们的JSX代码中,就在删除按钮的下方,让我们将任务文本div 更新为这样。

<div class={`bg-light p-2 mx-2 ${task.completed && 'text-decoration-line-through text-success'}`}>
  {task.text}
</div>

接下来,我们将为复选框输入标签添加一个onClick 事件监听器,每当它被点击时,我们将调用toggleStatus() 方法。

<input
  type="checkbox"
  checked={task.completed}
  role="button"
  class="form-check-input h-auto px-3"
  onClick={() => {
    toggleStatus(task.id)
  }}
/>

我们的<App /> 组件返回的JSX代码现在看起来应该是这样的。

<div class="container mt-5 text-center">
      <h1 class="mb-4">Whattodo!</h1>
      <form class="mb-5 row row-cols-2 justify-content-center" onSubmit={(e) => addTask(e)}>
        <input type="text" class="input-group-text p-1 w-25" placeholder="Add task here..." id="taskInput" required />
        <button class="btn btn-primary ms-3 w-auto" type="submit">
          Add task
        </button>
      </form>
      <div>
        <h4 class="text-muted mb-4">Tasks</h4>
        <For each={taskList()}>
          {(task: Task) => (
            <div class="row row-cols-3 mb-3 justify-content-center">
              <button class="btn btn-danger w-auto" onclick={() => deleteTask(task.id)}>
                X
              </button>
              <div class={`bg-light p-2 mx-2 ${task.completed && 'text-decoration-line-through text-success'}`}>
                {task.text}
              </div>
              <input
                type="checkbox"
                checked={task.completed}
                role="button"
                class="form-check-input h-auto px-3"
                onClick={() => {
                  toggleStatus(task.id)
                }}
              />
            </div>
          )}
        </For>
      </div>
    </div>

当我们转到我们的浏览器时,我们的 SolidJS 应用程序应该能够像这样工作:

Adding tasks, checking them off, and deleting in web app

在 SolidJS 中使用createStore 进行嵌套反应性

在我们总结之前,让我们看看我们如何能在 SolidJS 中使用createStore Hook 来创建和更新嵌套状态。与其映射我们的状态,创建一个新的任务列表,并用新的列表替换我们所有的状态数据,不如直接使用其 ID 更新需要更新的任务。

要使用createStore 钩子,我们首先要从solid-js/store

import { createStore } from 'solid-js/store'

注意,createSignal 是从solid-js 导入的,而createStore 是从solid-js/store 导入的。

接下来,我们将更新我们的taskList 状态创建到这个。

const [taskList, setTaskList] = createStore([] as Task[])

我们用createStore() 钩子创建的存储不是一个函数,与用createSignal() 钩子创建的不同。因此,我们将修改代码中所有taskList 的实例,只用taskList ,而不是taskList() 。第二个变量,setTaskList ,仍然是一个函数,我们将用它来更新我们的存储。

让我们继续使用setTaskList() 方法来修改toggleStatus() 函数。

const toggleStatus = (taskId: string) => {
  setTaskList(
    (task) => task.id === taskId,
    'completed',
    (completed) => !completed,
  )
}

toggleStatus() 函数中,我们向setTaskList() 方法传递三个参数:

  1. 一个函数来获取我们想要更新的特定任务。在我们的例子中,我们要返回与taskId 参数相同的id的任务
  2. 我们想要修改的属性----completed
  3. 对于第三个参数,我们将传递另一个函数,该函数接收我们所选属性的当前值并返回一个新值。在这里,我们要返回与当前值相反的值

当我们回到浏览器时,我们的应用程序应该仍能如期工作:

Task tracker app is still working

结论

在这篇文章中,我们已经通过构建一个任务跟踪器涵盖了 SolidJS 的基础知识。与 Angular 和 React 等其他前端框架相比,Solid 构建 Web 应用程序的方法相当令人印象深刻,而且相对简单明了。由于直接编译到真实的DOM节点,而且不需要虚拟DOM,用SolidJS构建的网络应用有一个不寻常的优势,就是速度快。

也就是说,SolidJS仍然是新的,与React、Vue和Angular等框架相比,其生态系统和社区很小,所以你很有可能是第一个遇到问题或需要特定功能、库或集成的人。但是SolidJS正在快速成长,很多人已经开始将现有的应用程序迁移到该框架。SolidJS 社区的反应相当迅速,当您需要帮助时,您应该不会有任何问题。