Solid.js 最新官方文档翻译(19)—— 复杂状态管理

450 阅读6分钟

前言

Solid.js,一个比 React 更 react 的框架。每一个使用 React 的同学,你可以不使用,但不应该不了解

目前 Solid.js 发布了最新的官方文档,但却缺少对应的中文文档。为了帮助大家学习 Solid.js,为爱发电翻译文档。

我同时搭建了 Solid.js 最新的中文文档站点:solid.yayujs.com ,欢迎勘误。

虽说是翻译,但个人并不喜欢严格遵守原文,为了保证中文阅读流畅,会删减部分语句,对难懂的部分也会另做补充解释,希望能给大家带来一个好的中文学习体验。

欢迎围观我的“朋友圈”、加入“低调务实优秀中国好青年”前端社群,分享技术,带你成长。

复杂状态管理

随着应用程序的增长并开始涉及许多组件、更复杂的用户交互以及可能与后端服务的通信,您可能会发现使用基础状态管理方法组织代码可能会变得难以维护。

考虑这个例子:

import { For, createSignal, Show, createMemo } from "solid-js"

const App = () => {
  const [tasks, setTasks] = createSignal([])
  const [numberOfTasks, setNumberOfTasks] = createSignal(tasks.length)
        const completedTasks = createMemo(() => tasks().filter((task) => task.completed))
  let input

  const addTask = (text) => {
    setTasks([...tasks(), { id: tasks().length, text, completed: false }])
    setNumberOfTasks(numberOfTasks() + 1)
  }
  const toggleTask = (id) => {
    setTasks(
      tasks().map((task) =>
        task.id !== id ? task : { ...task, completed: !task.completed }
      )
    )
  }

  return (
    <>
      <h1>My list</h1>
      <span>You have {numberOfTasks()} task(s) today!</span>
      <div>
        <input ref={input} />
        <button
          onClick={(e) => {
            if (!input.value.trim()) return
            addTask(input.value)
            input.value = ""
          }}
        >
          Add Task
        </button>
      </div>
      <For each={tasks()}>
        {(task) => {
          const { id, text } = task
          console.log(`Creating ${text}`)
          return (
            <div>
              <input
                type="checkbox"
                checked={task.completed}
                onChange={[toggleTask, id]}
              />
              <span
                style={{
                  "text-decoration": task.completed ? "line-through" : "none",
                }}
              >
                {text}
              </span>
            </div>
          )
        }}
      </For>
    </>
  )
}

export default App

以这种方式管理状态存在几个挑战:

  • 通过对 tasksnumberOfTasks 进行多次 createSignal 调用以及对 completedTasks 进行 createMemo 函数调用,增加了代码冗余。此外,每次状态更新时,都需要手动更新其他相关状态,这可能导致应用程序不同步。
  • 虽然 Solid 已经经过优化,但该组件设计会导致频繁的重新计算,例如每次切换交互都会更新 completedTasks,这可能会对性能产生负面影响。此外,组件逻辑对 numberOfTaskscompletedTasks 当前状态的依赖可能会使代码理解变得复杂。

随着像这样的应用程序扩展,以这种方式管理状态变得更加复杂。引入其他依赖状态变量将需要更新整个组件,这可能会引入更多错误。这可能会使在不转移大部分状态管理逻辑的情况下将特定功能分离成不同的、可重用的组件变得更加困难。

介绍 stores

通过使用 Stores 重新创建此列表,您将看到 Stores 如何提高代码的可读性和管理性。

如果您对 stores 的概念不熟悉,请参阅 stores 章节

创建一个 store

要减少原始示例中使用的 signals 数量,您可以使用 store 执行以下操作:

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

const App = () => {
    const [state, setState] = createStore({
        tasks: [],
        numberOfTasks: 0,
    })
}

export default App

通过使用 store,您不再需要跟踪 tasksnumberOfTaskscompletedTasks 这些单独的信号。

访问状态值

创建 store 后,可以通过 createStore 函数返回的第一个值直接访问这些值:

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

const App = () => {
    const [state, setState] = createStore({
        tasks: [],
        numberOfTasks: 0,
    })
    return (
        <>
            <h1>My Task List for Today</h1>
            <span>You have {state.numberOfTasks} task(s) for today!</span>
        </>
    )
}

export default App

通过 state.numberOfTasks ,将显示 numberOfTasks 属性中保存的 store 值。

更新 store

当您想要修改 store 时,可以使用 createStore 函数返回的第二个元素。此元素允许您对 store 进行修改,从而添加新属性并更新现有属性。

但是,由于 store 中的属性是延迟创建的,因此在组件函数体中设置属性而不创建响应作用域将不会更新该值。要创建 signal 使其响应式更新,您必须访问跟踪作用域内的属性,例如使用 createEffect

// not reactive
setState("numberOfTasks", state.tasks.length)

// reactive
createEffect(() => {
    setState("numberOfTasks", state.tasks.length)
})

添加到数组

要将元素添加到数组(在本例中为新任务),您可以通过 state.tasks.length添加到数组的下一个索引:

const addTask = (text) => {
    setState("tasks", state.tasks.length, {
        id: state.tasks.length,
        text,
        completed: false,
    })
}

store 中的 setter 遵循路径语法setStore("key", value) 。在 addTask 函数中, tasks 数组通过 setState("tasks", state.tasks.length, { id: state.tasks.length, text, completed: false })添加,这是一个实际的例子。

使用 produce 修改状态

在需要进行多个 setState 调用并定位多个属性的情况下,您可以使用 Solid 的 produce 工具函数来简化代码并提高可读性。

例如切换函数:

const toggleTask = (id) => {
    const currentCompletedStatus = state.tasks[id].completed
    setState(
        "tasks",
        (task) => task.id === id,
        "completed",
        !currentCompletedStatus
    )
}

可以使用 produce 进行简化:

import { produce } from "solid-js/store"

const toggleTask = (id) => {
    setState(
        "tasks",
        (tasks) => tasks.id === id,
        produce((task) => {
            task.completed = !task.completed
        })
    )
}

// You can also rewrite the `addTask` function through produce
const addTask = (text) => {
    setState(
        "tasks",
        produce((task) => {
            task.push({ id: state.tasks.length, text, completed: false })
        })
    )
}

了解使用 produce 的其他优点

说明

使用 produce 的另一个好处是它提供了一种修改 store 的方法,而无需进行多个 setStore 调用。

// without produce
batch(() => {
    setState(0, "text", "I'm updated text")
    setState(0, "completed", true)
})

// with produce
setState(
    0,
    produce((task) => {
        task.text = "I'm updated text";
        task.completed = true;
    })
)

更新后的示例:

import { For, createEffect, Show } from "solid-js"
import { createStore, produce } from "solid-js/store"

const App = () => {
    let input // lets you target the input value
    const [state, setState] = createStore({
        tasks: [],
        numberOfTasks: 0,
    })

    const addTask = (text) => {
        setState("tasks", state.tasks.length, {
            id: state.tasks.length,
            text,
            completed: false,
        })
    }

    const toggleTask = (id) => {
        setState(
            "tasks",
            (tasks) => tasks.id === id,
            produce((task) => {
                task.completed = !task.completed
            })
        )
    }

    createEffect(() => {
        setState("numberOfTasks", state.tasks.length)
    })

    return (
        <>
            <div>
                <h1>My Task List for Today</h1>

                <span>You have {state.numberOfTasks} task(s) for today!</span>

            </div>

            <input ref={input} />
            <button
                onClick={(e) => {
                    if (!input.value.trim()) return
                    addTask(input.value)
                    input.value = ""
                }}
            >
                Add Task
            </button>

            <For each={state.tasks}>
                {(task) => {
                    const { id, text } = task
                    return (
                        <div>
                            <input
                                type="checkbox"
                                checked={task.completed}
                                onChange={() => toggleTask(task.id)}
                            />
                            <span>{text}</span>

                        </div>

                    )
                }}
            </For>

        </>
    )
}

export default App

状态共享

随着应用程序的增长并变得复杂,组件之间共享状态可能成为一个挑战。将状态和函数从父组件传递到子组件,尤其是跨多个层级,通常称为“prop drilling”。

Prop drilling 可能会导致代码冗长且难以维护,并且会使应用程序中的数据流更难以遵循。为了解决这个问题并提供更具可扩展性和可维护性的代码库,Solid 提供了 context

要使用它,您需要创建一个上下文。该上下文将有一个默认值,并且可以由任何后代组件使用。

import { createContext } from "solid-js"

const TaskContext = createContext()

您的组件将使用上下文中的 Provider 进行包装,并传递您希望共享的值。

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

const TaskApp = () => {
    const [state, setState] = createStore({
        tasks: [],
        numberOfTasks: 0,
    })

    return (
        <TaskContext.Provider value={{ state, setState }}>
            {/* Your components */}
        </TaskContext.Provider>
    )
}

在任何后代组件中,您可以使用 useContext 使用上下文值:

import { useContext } from "solid-js"

const TaskList = () => {
    const { state, setState } = useContext(TaskContext)

    // Now you can use the shared state and functions
}

如需更深入的了解,请参阅我们有关上下文章节

Solid.js 中文文档

本篇已收录在掘金专栏 《Solid.js 中文文档》,该系列一共 25 篇。下一篇:Solid.js 最新官方文档翻译(20)—— 获取数据

此外我还写过 JavaScript 系列TypeScript 系列React 系列Next.js 系列VuePress 博客搭建系列等 14 个系列文章, 全系列文章目录:github.com/mqyqingfeng…

通过文字建立交流本身就是一种缘分,欢迎围观我的“朋友圈”、加入“低调务实优秀中国好青年”前端社群,分享技术,带你成长。