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

前言

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

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

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

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

欢迎围观朋友圈、加入低调务实优秀中国好青年前端社群,一个人走得快,一群人走得远。

状态管理

状态管理是指处理和操作数据进而影响 Web 应用程序的行为和展示的过程。为了构建交互式和动态 Web 应用程序,状态管理是开发的一个重要方面。在 Solid 中,状态管理是通过使用响应式原语(reactive primitives)来帮助实现的。

让我们使用一个基础的计数器例子来展示这些状态管理概念:

jsx 复制代码
import { createSignal } from "solid-js";

function Counter() {
    const [count, setCount] = createSignal(0);

    const increment = () => {
        setCount((prev) => prev + 1);
    };

    return (
        <>
            <div>Current count: {count()}</div>

            <button onClick={increment}>Increment</button>

        </>
    );
}

状态管理有 3 个要素:

  1. 状态(State) ( count): 决定向用户展示什么内容的数据。
  2. 视图(View) ( <div>{count()}</div>): 向用户展示的状态的视觉表示。
  3. 交互(Actions) ( increment): 任何修改状态的事件。

这些元素共同创建了"单向数据流"。当交互修改状态时,视图会更新以展示当前状态。数据流简化了数据和用户交互的管理过程,从而提供了更具可预测性和可维护的应用程序。

管理基础状态

状态是应用程序的真实来源,用于确定向用户显示哪些内容。状态由 signal 表示,它是一个响应式原语,用于管理状态并向 UI 通知任何更改。

要创建一段状态,您可以使用 createSignal 函数并传入状态的初始值:

jsx 复制代码
import { createSignal } from "solid-js";

const [count, setCount] = createSignal(0);

要访问状态的当前值,可以调用 signal 的 getter 函数:

jsx 复制代码
console.log(count()); // 0

要更新状态,您可以使用 signal 的 setter 函数:

jsx 复制代码
setCount((prev) => prev + 1);

console.log(count()); // 1

通过 signal,您可以以简单直接的方式创建和管理状态。这使您可以专注于应用程序的逻辑,而不是状态管理的复杂性。此外,signal 是响应式的,这意味着只要在跟踪作用域内访问它,它就始终是最新的。

UI 中的渲染状态

要实现动态用户界面,UI 必须能够反映数据的当前状态。 UI 是用户状态的直观表示,并使用 JSX 进行渲染。 JSX 提供了一个跟踪作用域,使视图与状态保持同步。

重新访问前面介绍的 Counter 组件,使用 JSX 在返回内容中渲染 count 的当前状态:

jsx 复制代码
return (
    <>
        <div>Current count: {count()}</div>
        <button onClick={increment}>Increment</button>
    </>
);

为了渲染 count 的当前状态,使用 JSX 表达式 {count()} 。大括号表示该表达式是 JavaScript 表达式,圆括号表示它是函数调用。该表达式代表 count 的 getter 函数,将获取当前状态值。当状态更新时,UI 将重新渲染以反映新的状态值。

Solid 中的组件在初始化时仅运行一次。在初始渲染之后,如果对状态进行任何更改,则只会更新与 signal 更改直接关联的 DOM 部分。

仅更新 DOM 相关部分的能力是 Solid 的一项关键功能,可实现高性能且高效的 UI 更新。这称为细粒度响应式。通过减少整个组件或更大 DOM 范围的重新渲染,UI 将保持更加高效和及时响应。

对变化做出响应

当状态更新时,任何更新都会反映在 UI 中。但是,有时您可能希望在状态更改时执行其他操作。

例如,在 Counter 组件中,您可能希望向用户显示 count 的双倍值。这可以通过使用 effects 来实现,effects 也是响应式原语,在状态改变时执行副作用:

jsx 复制代码
import { createSignal, createEffect } from "solid-js";

function Counter() {
    const [count, setCount] = createSignal(0);
    const [doubleCount, setDoubleCount] = createSignal(0); // Initialize a new state for doubleCount

    const increment = () => {
        setCount((prev) => prev + 1);
    };

    createEffect(() => {
        setDoubleCount(count() * 2); // Update doubleCount whenever count changes
    });

    return (
        <>
            <div>Current count: {count()}</div>
            <div>Doubled count: {doubleCount()}</div> // Display the doubled count
            <button onClick={increment}>Increment</button>
        </>
    );
}

createEffect 设置一个函数,用于在状态修改时执行副作用。这里,副作用是指由状态更改触发的、影响本地环境之外的状态的操作或更新,例如修改全局变量或更新 DOM。

Counter 组件中,只要 count 状态发生变化,就可以使用 createEffect 函数来更新 doubleCount 状态。这使 doubleCount 状态与 count 状态保持同步,并通过 UI 向用户显示 count 的双倍值。

查看 Solid Playground 示例中 createEffect 中 doubleCount 的示例

派生状态

当您想要根据现有状态值计算新的状态值时,可以使用派生状态。当您想要向用户显示状态值的转换,但不想修改原始状态值或创建新的状态值时,这是一个有用的模式。

可以使用函数内的 signal 创建派生值,该 signal 可以称为派生 signal

这种方法可用于简化上面的 doubleCount 示例,其中额外的 signal 和 effect 可以用派生 signal 替换:

diff 复制代码
import { createSignal } from "solid-js"

function Counter() {
    const [count, setCount] = createSignal(0);
-    const [doubleCount, setDoubleCount] = createSignal(0);

    const increment = () => {
        setCount((prev) => prev + 1);
    };

-    createEffect(() => {
-        setDoubleCount(count() * 2); // Update doubleCount whenever count changes
-    });

+    const doubleCount = () => count() * 2

    return (
        <>
            <div>Current count: {count()}</div>
            <div>Doubled count: {doubleCount()}</div>
            <button onClick={increment}>Increment</button>
        </>
    );
}

此方法适用于简单的用例,但如果 doubleCount 在组件内多次使用或包含计算量大的计算,则可能会导致性能问题。

不仅每次 count 更改时,而且每次使用 doubleCount() 时,都会重新评估派生 signal 。

diff 复制代码
import { createSignal } from "solid-js"

function Counter() {
    const [count, setCount] = createSignal(0)

    const increment = () => {
        setCount(count() + 1)
    }

-    const doubleCount = () => count() * 2
+    const doubleCount = () => {
+        console.log('doubleCount called')
+        return count() * 2
+    }

    return (
        <>
            <div>Current count: {count()}</div>
            <div>Doubled count: {doubleCount()}</div>
+           <div>Doubled count: {doubleCount()}</div>
+            <div>Doubled count: {doubleCount()}</div>
            <button onClick={increment}>Increment</button>

        </>
    )
}

输出结果为:

plain 复制代码
doubleCount called
doubleCount called
doubleCount called

对于这种情况,您可以使用 Memos 来存储 doubleCount 的值,该值也称为记忆值或缓存值。使用 Memos 时,当 count 的值发生变化时,计算只会运行一次,并且可以多次访问,而无需为每次额外使用重新评估。

使用 createMemo 函数,您可以创建一个记忆值:

diff 复制代码
import { createSignal, createMemo } from "solid-js"

function Counter() {
    const [count, setCount] = createSignal(0)

    const increment = () => {
        setCount((prev) => prev + 1);
    };

    const doubleCount = () => {
        console.log('doubleCount called')
        return count() * 2
    }

+    const doubleCountMemo = createMemo(() => {
+        console.log('doubleCountMemo called')
+        return count() * 2
+    })

    return (
        <>
            <div>Current count: {count()}</div>
            <div>Doubled count: {doubleCount()}</div>
            <div>Doubled count: {doubleCount()}</div>
            <div>Doubled count: {doubleCount()}</div>
+           <div>Doubled count: {doubleCountMemo()}</div>
+           <div>Doubled count: {doubleCountMemo()}</div>
+           <div>Doubled count: {doubleCountMemo()}</div>
            <button onClick={increment}>Increment</button>

        </>
    );
}

输出结果为:

plain 复制代码
doubleCountMemo called
doubleCount called
doubleCount called
doubleCount called

多次访问时,doubleCountMemo 仅重新评估并记录一次。这与派生 signal doubleCount 不同,派生 signal 在每次访问时都会重新评估。

查看 Solid Playground 中比较派生 signal 和 memo 的类似示例

提升状态

当您想要在组件之间共享状态时,可以将状态提升到共同的祖先组件。虽然状态不与组件绑定,但您可能希望将多个组件链接在一起,以便访问和操作同一个状态。这可以使整个组件树保持同步,实现更可预测的状态管理。

例如,在 Counter 组件中,您可能希望通过单独的组件向用户显示 count 的双倍值:

jsx 复制代码
import { createSignal, createEffect, createMemo } from "solid-js";

function App() {
    const [count, setCount] = createSignal(0);
    const [doubleCount, setDoubleCount] = createSignal(0);
    const squaredCount = createMemo(() => count() * count());

    createEffect(() => {
        setDoubleCount(count() * 2);
    });

    return (
        <>
            <Counter count={count()} setCount={setCount} />
            <DisplayCounts
                count={count()}
                doubleCount={doubleCount()}
                squaredCount={squaredCount()}
            />
        </>
    );
}

function Counter(props) {
    const increment = () => {
        props.setCount((prev) => prev + 1);
    };

    return <button onClick={increment}>Increment</button>;
}

function DisplayCounts(props) {
    return (
        <div>
            <div>Current count: {props.count}</div>
            <div>Doubled count: {props.doubleCount}</div>
            <div>Squared count: {props.squaredCount}</div>
        </div>
    );
}

export default App;

要在 CounterDisplayCounts 组件之间共享 count 状态,您可以将状态提升到 App 组件。这允许 CounterDisplayCounts 函数访问同一状态,也允许 Counter 组件通过 setCount setter 函数更新状态。

当组件之间共享状态时,可以通过 ∑ 访问状态。从父组件传递下来的 props 值是只读的,这意味着子组件不能直接修改它们。但是,您可以从父组件传递 setter 函数,以允许子组件间接修改父组件的状态。

说明:

为了鼓励单向数据流,props 作为只读或不可变值从父组件传递到子组件。

然而, props 有特定的工具函数,提供修改 props 值的方法。

管理复杂状态

随着应用程序规模和复杂性的增加,提升状态可能变得难以管理。为了避免 prop drilling 的概念(即通过多个组件传递 props 的过程),Solid 提供 stores 以更具可扩展性和可维护性的方式管理状态。

要了解有关管理复杂状态的更多信息,请导航至复杂状态管理页面

系列文章

本篇已收录在掘金专栏 《Solid.js 中文文档》

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

通过文字建立交流本身就是一种缘分,欢迎围观朋友圈、加入低调务实优秀中国好青年前端社群,一个人走得快,一群人走得远。

相关推荐
古怪今人2 分钟前
前端同步发送HTTP请求 Ajax、Fetch和Axios实现HTTP同步请求
前端·http·ajax
时间sk8 分钟前
CSS——9.ID选择器
前端·javascript·css
_Jyann_12 分钟前
css如何选择元素的最后一个并设置样式
前端·javascript·css
开心工作室_kaic29 分钟前
springboot571基于协同过滤算法的私人诊所管理系统(论文+源码)_kaic
前端·数据库·vue.js·后端·html5
✎﹏ℳ๓₯㎕35 分钟前
vue使用keep-alive实现页面前进刷新,后退缓存
前端·vue.js·缓存
onejason2 小时前
如何利用PHP爬虫批量获取商品信息
前端·php
编程乐趣2 小时前
Eval-Expression.NET:动态执行C#脚本,类似Javascript的Eval函数功能
javascript·c#·.net
晨辰星662 小时前
Vue2中使用Echarts
前端·javascript·echarts
CodeToGym2 小时前
三分钟在你的react项目中引入tailwindcss
前端·react.js·前端框架·tailwindcss
时间sk2 小时前
CSS——10.类选择器
前端·css