Solid.js 最新官方文档翻译(14)—— Stores

652 阅读9分钟

前言

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

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

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

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

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

Stores

信号类似,Stores 也用于状态管理。不过信号管理单个状态,Stores 会创建一个集中位置以减少代码冗余。在 Solid 中,这些 stores 可以生成一组响应式信号,每个信号对应一个特定的属性,这在处理复杂状态时非常有用。

创建一个 store

Stores 可以管理多种数据类型,包括:对象、数组、字符串和数字。

使用 JavaScript 的代理机制,响应式不只局限于顶层对象或数组。通过 Stores,您可以创建响应式数据的动态树,并定位其中的嵌套属性和元素:

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

// 初始化 store
const [store, setStore] = createStore({
    userCount: 3,
    users: [
        {
            id: 0,
            username: "felix909",
            location: "England",
            loggedIn: false,
        },
        {
            id: 1,
            username: "tracy634",
            location: "Canada",
            loggedIn: true,
        },
        {
            id: 2,
            username: "johny123",
            location: "India",
            loggedIn: true,
        },
    ],
})

访问 store 值

可以通过直接引用目标属性来从状态代理中访问 store 属性:

console.log(store.userCount) // 输出: 3

访问追踪作用域内的 store 遵循与信号类似的模式。不过信号是使用 createSignal 函数创建,并且需要调用信号函数来访问其值,但 store 可以直接访问其值,而无需调用函数。这可以直接在追踪作用域内访问 store 的值:

const App = () => {
    const [mySignal, setMySignal] = createSignal("This is a signal.")
    const [store, setStore] = createStore({
        userCount: 3,
        users: [
            {
                id: 0,
                username: "felix909",
                location: "England",
                loggedIn: false,
            },
            {
                id: 1,
                username: "tracy634",
                location: "Canada",
                loggedIn: true,
            },
            {
                id: 2,
                username: "johny123",
                location: "India",
                loggedIn: true,
            },
        ],
    })
    return (
        <div>
            <h1>Hello, {store.users[0].username}</h1> {/* 获取一个 store 的值 */}
            <span>{mySignal()}</span> {/* 获取一个 signal 的值*/}
        </div>

    )
}

创建 store 时,它从初始状态开始,但不会立即设置信号来追踪改变。这些信号是惰性创建的,这意味着它们仅在响应式上下文中访问时才会创建。

一旦在响应式上下文中使用数据,例如在组件函数的返回语句中、计算属性或 effect 中,就会创建信号并建立依赖关系。

例如,如果您想打印出每个新用户,添加下面的控制台日志并不会起作用,因为它不在追踪作用域内。

const App = () => {
    const [store, setStore] = createStore({
        userCount: 3,
        users: [ ... ],
    })

    const addUser = () => { ... }

+    console.log(store.users.at(-1)) // 不会工作

    return (
        <div>
            <h1>Hello, {store.users[0].username}</h1>
            <p>User count: {store.userCount}</p>
            <button onClick={addUser}>Add user</button>
        </div>
    )
}

相反,这需要位于追踪作用域内,例如在 createEffect 内,以便建立依赖关系。

const App = () => {
    const [store, setStore] = createStore({
        userCount: 3,
        users: [ ... ],
    })

    const addUser = () => { ... }

-    console.log(store.users.at(-1))
+    createEffect(() => {
+        console.log(store.users.at(-1))
+    })

    return (
        <div>
            <h1>Hello, {store.users[0].username}</h1>

            <p>User count: {store.userCount}</p>

      <button onClick={addUser}>Add user</button>

        </div>

    )
}

修改 store 值

更新 store 中的值最好使用 createStore 初始化提供的 setter 来完成。此 setter 允许修改特定键及其关联值,格式为 setStore(key, newValue)

const [store, setStore] = createStore({
  userCount: 3,
  users: [ ... ],
})

setStore("users", (currentUsers) => [
  ...currentUsers,
  {
    id: 3,
    username: "michael584",
    location: "Nigeria",
    loggedIn: false,
  },
])

每当添加新用户时,userCount 的值也可以自动更新,使其与用户数组保持同步:

const App = () => {
    const [store, setStore] = createStore({
        userCount: 3,
        users: [ ... ],
    })

    const addUser = () => { ... }

    createEffect(() => {
        console.log(store.users.at(-1))
+        setStore("userCount", store.users.length)
    })

    return (
        <div>
            <h1>Hello, {store.users[0].username}</h1>
            <p>User count: {store.userCount}</p>
            <button onClick={addUser}>Add user</button>
        </div>
    )
}

说明:

将 store 的读取和写入功能分开可以提供宝贵的调试优势。

这种分离有助于追踪和控制正在访问或更改值的组件。

进阶:

store 的一个隐藏功能是您还可以创建嵌套 store 以方便设置嵌套属性。

  const [store, setStore] = createStore({
    userCount: 3,
    users: [ ... ],
  })

  const [users, setUsers] = createStore(store.users)

  setUsers((currentUsers) => [
    ...currentUsers,
    {
      id: 3,
      username: "michael584",
      location: "Nigeria",
      loggedIn: false,
    },
  ])

通过 setUsers 所做的更改将更新 store.users 属性,并且从此派生 store 中读取 users 也会与 store.users 中的值同步。

注意,上述内容依赖于现有 store 已经设置了 store.users

路径语法灵活性

使用此方法修改 store 被称为“路径语法”。在此方法中,第一个参数用于指定要修改的目标值的键,最后一个参数提供新值。

字符串键名用于使用路径语法精确获得指定值。然而路径语法提供了更多的功能。

可以使用键名数组,此方法可以让您选择 store 中的多个属性,从而快捷访问嵌套结构。或者,您可以使用过滤函数基于动态条件或特定规则访问键值。

路径语法的灵活性有助于高效导航、检索和修改 store 中的数据,无论 store 的复杂性或者应用程序中动态访问方案要求如何。

修改数组中的值

路径语法提供了一种修改数组的便捷方法,使访问和更新其元素变得更加容易。路径语法不依赖于发现单个索引,而是引入了几种强大的数组操作技术。

追加新值

要将新元素附加到 store 中的数组,您可以指定目标数组并设置期望的索引位置。举个例子,如果您想将新元素追加到数组末尾,则可以将索引设置为 array.length

setStore("users", (otherUsers) => [
    ...otherUsers,
    {
        id: 3,
        username: "michael584",
        location: "Nigeria",
        loggedIn: false,
    },
])

// 可以变成

setStore("users", store.users.length, {
    id: 3,
    username: "michael584",
    location: "Nigeria",
    loggedIn: false,
})

范围标准

使用路径语法,您可以通过指定索引范围来指定要更新或修改的元素子集。您可以使用一个包含多个值的数组来完成此操作:

setStore("users", [1, 3], "loggedIn", false)

信息:

如果您的 store 是一个数组,您可以使用带有 fromto属性的对象指定索引范围:

const [store, setStore] = createStore([]) // store 是一个数组
...
setStore({ from: 1, to: store.length - 1 }, "loggedIn", false)

除此之外,还有一个by键名可以帮助您在数组中执行迭代更新。如果您想要规律更新元素,这非常有用。该键名可以定义索引增量的步长,类似于 for 循环

setStore({ from: 0, to: store.length, by: 2 }, "loggedIn", false)

动态赋值

路径语法还提供了一种使用函数而不是静态值在数组中设置值的方法。这些函数接收旧值作为参数,允许您根据现有值计算新值。这种动态方法对于复杂的转换特别有用。

setStore("users", 3, "loggedIn" , (loggedIn) => !loggedIn)

过滤值

在您想要根据特定条件更新数组中的元素的情况下,您可以传递函数作为参数。此函数将充当过滤器,允许您选择满足条件的元素。它接收旧值和索引作为参数,提供了进行条件更新的灵活性。

setStore("users", (user) => user.username.startsWith("t"), "loggedIn", false)

除了 .startsWith 之外,您还可以使用 .find 等其他数组方法来过滤所需的值。

修改对象

当使用 store setter 修改对象时,如果新值是一个对象,它将与现有值进行浅合并。这指的是现有对象的属性将与您正在设置的“新”对象的属性合并,用新对象的值更新任何相同的属性。

这意味着您可以直接对 store 进行更改,而无需扩展现有用户对象的属性。

setStore("users", 0, {
    id: 109,
})

// 相当于

setStore("users", 0, (user) => ({
    ...user,
    id: 109,
}))

Store 工具函数

使用 produce 更新 Store

Solid 没有使用 setter 直接修改 store,而是提供了 produce 工具函数。该工具函数提供了一种类似于处理可变 JavaScript 对象的方式处理数据。 produce 还提供了一种同时修改多个属性的方法,从而消除了调用多个 setter 的需要。

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

// 没有 produce
setStore("users", 0, "username", "newUsername")
setStore("users", 0, "location", "newLocation")

// 使用 produce
setStore(
    "users",
    0,
    produce((user) => {
        user.username = "newUsername"
        user.location = "newLocation"
    })
)

producesetStore 具有不同的功能。虽然两者都可以用来修改状态,但关键区别在于它们处理数据的方式。

produce 允许您使用状态的临时草稿,应用更改,然后生成 store 新的不可变版本。相比之下,setStore 提供了更直接的方式来直接更新 store,而不会创建新版本。

值得注意的是,produce 是专门为处理数组和对象而设计的。其他集合类型(例如 JavaScript SetsMaps )与此工具函数不兼容。

使用 reconcile 数据集成

当需要将新信息合并到现有 store 中时,reconcile 可能很有用。 reconcile 会判断新数据和现有数据之间的差异,只有当有发生变化的值时才发起更新,从而避免不必要的更新。

const { createStore, reconcile } from "solid-js/stores"

const [data, setData] = createStore({
    animals: ['cat', 'dog', 'bird', 'gorilla']
})

const newData = getNewData() // eg. contains ['cat', 'dog', 'bird', 'gorilla', 'koala']
setData('animals', reconcile(newData))

在这个例子中,store 将查找现有数据集和传入数据集之间的差异。因此,只有 'koala',这个新版本才会导致更新。

使用 unwrap 提取原始数据

当需要处理响应式上下文之外的数据时,unwrap工具函数提供了一种将 store 转换为标准对象的方法。这种转换有几个重要的目的。

首先,它提供了当前状态的快照,而无需响应式相关的处理开销。这在需要未更改的、非响应式数据视图的情况下非常有用。

此外,unwrap 提供了一种与常规 JavaScript 对象的第三方库或工具进行交互的方法。该工具函数充当桥梁,促进与外部组件的顺利集成,并简化将 store 合并到各种应用程序和工作流程中的过程。

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

const [data, setData] = createStore({
    animals: ["cat", "dog", "bird", "gorilla"],
})

const rawData = unwrap(data)

要了解有关如何在实践中使用 Stores 的更多信息,请访问复杂状态管理指南

Solid.js 中文文档

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

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

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