状态管理是react中必备的一项利器,能够自己实现一个,可以加深理解,提升自身实力,相关代码地址
下面实现一个简洁的状态管理库,如图所示
该实现包括一个 createStore
函数用于创建 store
对象,以及 useStore
自定义钩子用于连接 React
组件到 store
。
import { useEffect,useState } from "react";
// State类型
type State<T> = T | ((pre: T) => T) ;
// Store对象类型
type Store<T> = {
getState: () => T;
setState: (state: State<T>) => void;
subscribe: (listener: () => void) => () => void;
};
// createStore函数,用于创建store对象
export function createStore<T>(initialState: T): Store<T> {
let state: T = initialState;
let listeners: Array<() => void> = [];
// 获取当前state
function getState(): T {
return state;
}
// 更新state
function setState(newState: State<T | Function>) {
// 如果新的state是一个函数,那么执行该函数并将执行结果作为新的state
if (typeof newState === 'function') {
state = (newState as (prev: T) => T)(getState());
} else {
state = newState;
}
// 触发所有的监听器
listeners.forEach((listener) => listener());
}
// 订阅state的变化
function subscribe(listener: () => void): () => void {
listeners.push(listener);
// 返回一个函数,用于取消订阅
return () => {
listeners = listeners.filter((l) => l !== listener);
};
}
return { getState, setState, subscribe };
}
// 自定义钩子,用于连接React组件到store
export function useStore<T>(store: Store<T>): T {
const [state, setState] = useState(store.getState());
useEffect(() => {
// 每当store发生变化时,更新组件状态
return store.subscribe(() => {
setState(store.getState());
});
}, [store]);
return state;
}
用法
import { createStore, useStore } from "../store";
// 创建 store 对象
const counterStore = createStore({
count: 0,
});
// 定义 actions
const actions = {
increment: () => {
counterStore.setState((prevState) => ({ count: prevState.count + 1 }));
},
decrement: () => {
counterStore.setState((prevState) => ({ count: prevState.count - 1 }));
},
};
// 使用 useStore 钩子连接组件到 store
export function Counter() {
const { count } = useStore(counterStore);
return (
<div>
<p>Count: {count}</p>
<button onClick={actions.increment}>+</button>
<button onClick={actions.decrement}>-</button>
</div>
);
}
在这个示例中,我们首先使用 createStore
函数创建了一个名为 counterStore
的 store
对象,并传递了一个初始状态对象。接下来,我们定义了两个名为 increment
和 decrement
的 actions
,它们分别用于将 count
属性加一和减一。
最后,我们使用 useStore
钩子将组件连接到 counterStore
对象,并获取当前的 count
状态。我们在组件中渲染了当前的 count
值,并添加了两个按钮,用于调用 increment
和 decrement
方法。当我们点击这些按钮时,组件将更新并显示更新后的 count
值。
以下是一个更复杂的使用示例,展示如何在 Todo
应用程序中使用上述状态管理器实现状态管理。
首先,我们定义一个 todo 的状态类型:
type Todo = {
id: number;
text: string;
completed: boolean;
};
type TodoState = {
todos: Todo[];
};
接下来,我们使用 createStore
函数创建一个名为 todoStore 的 store
对象,并传递初始状态对象 { todos: [] }
:
const todoStore = createStore<TodoState>({ todos: [] });
我们可以使用 setState 方法来添加、更新和删除 todo:
const addTodo = (text: string) => {
const newTodo: Todo = {
id: Date.now(),
text,
completed: false,
};
todoStore.setState((prevState) => ({
todos: [...prevState.todos, newTodo],
}));
};
const updateTodo = (id: number, updates: Partial<Todo>) => {
todoStore.setState((prevState) => {
const updatedTodos = prevState.todos.map((todo) =>
todo.id === id ? { ...todo, ...updates } : todo
);
return {
todos: updatedTodos,
};
});
};
const deleteTodo = (id: number) => {
todoStore.setState((prevState) => ({
todos: prevState.todos.filter((todo) => todo.id !== id),
}));
};
我们还可以定义一个名为 useTodos 的自定义钩子,用于将 todoStore 与组件连接起来:
const useTodos = () => {
const state = useStore(todoStore);
const add = useCallback(addTodo, []);
const update = useCallback(updateTodo, []);
const remove = useCallback(deleteTodo, []);
return { todos: state.todos, add, update, remove };
};
在组件中,我们可以使用 useTodos 钩子获取当前的 todos 数组以及 add、update 和 remove 方法,以添加、更新和删除 todo
function TodoList() {
const { todos, add, update, remove } = useTodos();
const [newTodoText, setNewTodoText] = useState("");
const handleAddTodo = (e: React.FormEvent) => {
e.preventDefault();
if (newTodoText.trim()) {
add(newTodoText);
setNewTodoText("");
}
};
return (
<div>
<h1>Todos</h1>
<form onSubmit={handleAddTodo}>
<input
type="text"
placeholder="Add a todo"
value={newTodoText}
onChange={(e) => setNewTodoText(e.target.value)}
/>
<button type="submit">Add</button>
</form>
<ul>
{todos.map((todo) => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange={(e) => update(todo.id, { completed: e.target.checked })}
/>
<span>{todo.text}</span>
<button onClick={() => remove(todo.id)}>Delete</button>
</li>
))}
</ul>
</div>
);
}
进一步扩展
-
添加中间件支持:可以通过添加中间件来增强状态管理器的功能,例如记录状态变更、实现异步 action、执行副作用等。
-
支持更多的 store 对象:可以扩展状态管理器,以支持多个 store 对象,并允许使用者在不同的组件中选择不同的 store 对象。
-
支持更复杂的状态结构:可以扩展状态管理器,以支持更复杂的状态结构,例如树形结构或嵌套结构。这可能需要引入新的 API,例如获取子状态或递归设置状态。
-
添加状态持久化支持:可以添加状态持久化支持,以允许状态在不同会话或页面之间进行保存和恢复。
-
添加时间旅行支持:可以添加时间旅行支持,以允许用户回溯和前进到不同的状态版本,并实现撤销和重做操作。
-
添加调试支持:可以添加调试支持,以允许开发者查看状态变化并进行调试。例如,可以实现一个开发者工具,用于显示状态树、操作日志和当前状态的快照。
支持中间件
要扩展代码以支持中间件,需要对 createStore
函数进行简单修改。我们可以向 createStore
函数中添加一个参数middleware
,用于传递中间件函数。中间件函数接收一个 store
对象作为参数,并返回一个新的 store
对象。下面是修改后的代码:
import { useEffect, useState } from "react";
// State类型
export type State<T> = T | ((pre: T) => T);
// Store对象类型
export type Store<T> = {
getState: () => T;
setState: (state: State<T>) => void;
subscribe: (listener: () => void) => () => void;
};
// createStore函数,用于创建store对象
export function createStore<T>(
initialState: T,
middleware?: (store: Store<T>) => Store<T>
): Store<T> {
let state: T = initialState;
let listeners: Array<() => void> = [];
// 获取当前state
function getState(): T {
return state;
}
// 更新state
function setState(newState: State<T>) {
// 如果新的state是一个函数,那么执行该函数并将执行结果作为新的state
if (typeof newState === "function") {
// TODO: remove as
state = (newState as (prev: T) => T)(getState());
} else {
state = newState;
}
// 触发所有的监听器
listeners.forEach((listener) => listener());
}
// 订阅state的变化
function subscribe(listener: () => void): () => void {
listeners.push(listener);
// 返回一个函数,用于取消订阅
return () => {
listeners = listeners.filter((l) => l !== listener);
};
}
// store对象
let store: Store<T> = { getState, setState, subscribe };
// 如果有中间件,应用中间件
if (middleware) {
store = middleware(store);
}
return store;
}
// 自定义钩子,用于连接React组件到store
export function useStore<T>(store: Store<T>): T {
const [state, setState] = useState(store.getState());
useEffect(() => {
// 每当store发生变化时,更新组件状态
return store.subscribe(() => {
setState(store.getState());
});
}, [store]);
return state;
}
中间件用例LogCounter
,打印新旧状态
import { createStore, State, Store, useStore } from "../store";
// 定义一个中间件
function loggingMiddleware<T>(store: Store<T>): Store<T> {
const { getState, setState, subscribe } = store;
return {
getState,
setState: (newState: State<T>) => {
console.log("Old state:", getState());
setState(newState);
console.log("New state:", getState());
},
subscribe,
};
}
// 创建 store 对象
const counterStore = createStore(
{
count: 0,
},
loggingMiddleware
);
// 定义 actions
const actions = {
increment: () => {
counterStore.setState((prevState) => ({ count: prevState.count + 1 }));
},
decrement: () => {
counterStore.setState((prevState) => ({ count: prevState.count - 1 }));
},
};
// 使用 useStore 钩子连接组件到 store
export function LogCounter() {
const { count } = useStore(counterStore);
return (
<div>
<p>Count: {count}</p>
<button onClick={actions.increment}>+</button>
<button onClick={actions.decrement}>-</button>
</div>
);
}
支持传入多个middlewares
中间件实现,关键利用reduceRight
方法
import { useEffect, useState } from "react";
// State类型
export type State<T> = T | ((pre: T) => T);
// Store对象类型
export type Store<T> = {
getState: () => T;
setState: (state: State<T>) => void;
subscribe: (listener: () => void) => () => void;
};
// 中间件工具类型
export type MiddlewareNext<T> = (state: State<T>) => void;
type Middleware<T> = (next: MiddlewareNext<T>) => MiddlewareNext<T>;
// createStore函数,用于创建store对象
export function createStore<T>(
initialState: T,
middlewares: Middleware<T>[] = []
): Store<T> {
let state: T = initialState;
let listeners: Array<() => void> = [];
// 获取当前state
function getState(): T {
return state;
}
// 更新state
function setState(newState: State<T>) {
// 如果新的state是一个函数,那么执行该函数并将执行结果作为新的state
if (typeof newState === "function") {
// TODO: remove as
state = (newState as (prev: T) => T)(getState());
} else {
state = newState;
}
// 触发所有的监听器
listeners.forEach((listener) => listener());
}
// 订阅state的变化
function subscribe(listener: () => void): () => void {
listeners.push(listener);
// 返回一个函数,用于取消订阅
return () => {
listeners = listeners.filter((l) => l !== listener);
};
}
// 组合中间件处理函数,得到更新后的 setState 函数
const enhancedSetState = middlewares.reduceRight(
(next, middleware) => middleware(next),
setState
);
return { getState, setState: enhancedSetState, subscribe };
}
// 自定义钩子,用于连接React组件到store
export function useStore<T>(store: Store<T>): T {
const [state, setState] = useState(store.getState());
useEffect(() => {
// 每当store发生变化时,更新组件状态
return store.subscribe(() => {
setState(store.getState());
});
}, [store]);
return state;
}
多个中间件从左到右依次执行的例子 LogTodoList
import { useState } from "react";
import { useCallback } from "react";
import { createStore, MiddlewareNext, State, useStore } from "../store";
type Todo = {
id: number;
text: string;
completed: boolean;
};
type TodoState = {
todos: Todo[];
};
// 日志中间件
function loggerMiddleware<T>(next: MiddlewareNext<T>): MiddlewareNext<T> {
return (state: State<T>) => {
console.log("before update:", todoStore.getState());
next(state);
console.log("after update:", todoStore.getState());
};
}
// 计数中间件
function countMiddleware<T>(next: MiddlewareNext<T>): MiddlewareNext<T> {
let count = 0;
return (state: State<T>) => {
count++;
console.log("update count:", count);
next(state);
};
}
const todoStore = createStore<TodoState>({ todos: [] }, [
loggerMiddleware,
countMiddleware,
]);
const addTodo = (text: string) => {
const newTodo: Todo = {
id: Date.now(),
text,
completed: false,
};
todoStore.setState((prevState) => ({
todos: [...prevState.todos, newTodo],
}));
};
const updateTodo = (id: number, updates: Partial<Todo>) => {
todoStore.setState((prevState) => {
const updatedTodos = prevState.todos.map((todo) =>
todo.id === id ? { ...todo, ...updates } : todo
);
return {
todos: updatedTodos,
};
});
};
const deleteTodo = (id: number) => {
todoStore.setState((prevState) => ({
todos: prevState.todos.filter((todo) => todo.id !== id),
}));
};
const useTodos = () => {
const state = useStore(todoStore);
const add = useCallback(addTodo, []);
const update = useCallback(updateTodo, []);
const remove = useCallback(deleteTodo, []);
return { todos: state.todos || [], add, update, remove };
};
export function LogTodoList() {
const { todos, add, update, remove } = useTodos();
const [newTodoText, setNewTodoText] = useState("");
const handleAddTodo = (e: React.FormEvent) => {
e.preventDefault();
if (newTodoText.trim()) {
add(newTodoText);
setNewTodoText("");
}
};
return (
<div>
<form onSubmit={handleAddTodo}>
<input
type="text"
placeholder="Add a todo"
value={newTodoText}
onChange={(e) => setNewTodoText(e.target.value)}
/>
<button type="submit">Add</button>
</form>
<ul>
{todos.map((todo) => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange={(e) => update(todo.id, { completed: e.target.checked })}
/>
<span>{todo.text}</span>
<button onClick={() => remove(todo.id)}>Delete</button>
</li>
))}
</ul>
</div>
);
}