Guides

复杂状态管理

编辑此页面

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

考虑这个例子:

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 的其他优点

更新后的示例:

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
};

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

报告此页面问题