Guides

状态管理

编辑此页面

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

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

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 函数并传入状态的初始值:

import { createSignal } from "solid-js";
const [count, setCount] = createSignal(0);

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

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

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

setCount((prev) => prev + 1);
console.log(count()); // 1

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


UI 中的渲染状态

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

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

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 也是响应式原语,在状态改变时执行副作用:

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 替换:

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 。

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>
</>
);
}
Console output
doubleCount called
doubleCount called
doubleCount called

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

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

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>
</>
);
}
Console output
doubleCountMemo called
doubleCount called
doubleCount called
doubleCount called

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

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


提升状态

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

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

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 函数,以允许子组件间接修改父组件的状态。


管理复杂状态

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

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

报告此页面问题