React19 状态管理方案与原理剖析
🧠 补充资料
- React 官方文档
- Redux 官方文档
- Zustand 官方文档
- Jotai 官方文档
- useReducer
- useState 与 useReducer 的同一出处
- useTransition 的定义
- 基于proxy的状态管理之valtio
- 基于proxy的不可变数据之immer
- mantine
📌 相关面试真题
用过哪些状态管理方案,怎么选择?
常用方案包括:
- React 内置:
useState
、useReducer
、useContext
- 第三方库:Redux(RTK)、Zustand、Jotai、MobX、Recoil、XState 等
React 自带的状态管理工具:
- useState:用于管理简单的组件内部状态。适合用于管理不需要跨组件共享的小型状态。
- useReducer:适合管理复杂的状态逻辑,尤其是状态的更新涉及多个子状态时。类似于 Redux 的工作方式。
Context API:
- 用于跨组件传递状态,适合较小规模的项目中或者只有少量全局状态时使用。优点是配置简单,缺点是在频繁更新时可能会导致性能问题。
Redux:
- 适合大型项目和状态复杂的应用。通过单一状态树和纯函数(reducer)来管理状态变化,提供了中间件机制(如 Redux Thunk、Redux Saga)来处理异步操作。
MobX:
- 通过使用观察者模式来自动追踪状态和组件的依赖关系,简化了状态管理。适合那些需要响应式数据管理和直接操作状态的项目。
Recoil:
- 由 Facebook 开发的新状态管理库,能够更好地与 React 的 Concurrent Mode 结合。适合需要细粒度状态管理和良好性能的项目。
Zustand:
- 轻量级状态管理库,提供简单直观的 API,适合中小型项目以及希望保持代码简洁的开发者。
选择状态管理方案的原则:
- 项目规模:小型项目或状态不复杂时,useState 和 Context API 足够;大型项目或者状态复杂时,Redux 或 MobX 是更好的选择。
- 状态复杂度:状态逻辑复杂且需要处理异步操作时,Redux 是较好的选择;状态简单且直观时,Zustand 或者 MobX 会更加方便。
- 性能需求:需要高性能的应用可以考虑 Recoil 或者 MobX,它们在处理频繁状态变化时表现更好。
- 团队熟悉度:选择团队熟悉且上手快的工具可以提高开发效率和代码质量。
选择依据:
维度 | 说明 |
---|---|
状态共享范围 | 组件局部 vs 全局共享 |
状态复杂度 | 简单数据 vs 状态派发逻辑 |
性能需求 | 是否频繁更新、大量依赖关系 |
团队协作 | 是否标准化,开发体验 |
Debug 工具链 | 是否有 DevTools、Redux 时间旅行 |
说说你对 Redux 的理解?
Redux 是一个可预测的状态容器,核心思想是单向数据流 + 纯函数处理状态,搭配中间件支持异步逻辑,强调可测试性与可追踪性。推荐使用 @reduxjs/toolkit
简化冗余代码
是一种用于 JavaScript 应用的状态管理工具,特别适用于 React 应用。它通过单一的状态树来管理整个应用的状态,使状态管理更加可预测和易于调试。
注意 :Redux 跟 React 本身并没有任何关系,Redux 和 React 的关系完全取决于 react-redux
。
核心概念
- 单一状态树
Redux 使用一个单一的状态树来存储应用的所有状态,这个状态树是一个不可变对象。这使得状态管理变得简单且直观,可以轻松地跟踪和调试应用的状态变化。 - 纯函数 reducer
状态的变化通过纯函数reducer
来处理。reducer
接收当前状态和动作(action
),返回新的状态。由于reducer
是纯函数,意味着相同的输入总是产生相同的输出,没有副作用,使得状态更新更加可预测和可测试。 - 动作(action)
动作是一个描述状态变化的普通 JavaScript 对象,通常包含一个type
属性和一些相关的数据。动作是触发状态变化的唯一方式。 - 中间件(middleware)
Redux 提供了中间件机制,允许在动作被发送到reducer
之前或之后执行一些逻辑。常见的中间件有Redux Thunk
和Redux Saga
,用于处理异步操作和副作用。 - 数据流
Redux 采用单向数据流,动作从组件派发(dispatch
),经过中间件处理后,到达reducer
,生成新的状态,然后更新到组件。这种单向数据流简化了数据的跟踪和调试。 - 工具支持
Redux 有强大的工具支持,如 Redux DevTools,可以方便地查看和调试应用的状态变化。
状态改变引发视图频繁更新,怎么优化?
宏观层面:
- 借助状态切分与拆分组件
- 状态局部化(只把共享状态抽出)
- 避免深层嵌套组件依赖全局状态
- 使用惰性加载或
Suspense
控制渲染
具体从以下几个方面入手:
- 避免不必要的状态更新
说明
只在必要时更新状态,避免无意义的重复更新。利用 React.memo
和 useCallback
等手段,避免因函数或对象变化导致子组件重复渲染。
示例
jsx
import React, { useState, useCallback } from 'react';
const Child = React.memo(({ onClick, count }) => {
console.log('Child render');
return <button onClick={onClick}>Count: {count}</button>;
});
const Parent = () => {
const [count, setCount] = useState(0);
const [text, setText] = useState('');
const increment = useCallback(() => setCount(c => c + 1), []);
return (
<>
<Child onClick={increment} count={count} />
<input value={text} onChange={e => setText(e.target.value)} />
</>
);
};
- 拆分组件
说明
将大组件拆成多个小组件,每个组件只管理自己的状态,状态变化只影响对应组件,减少整体渲染。
示例
jsx
import React from 'react';
const UserName = ({ name }) => {
console.log('UserName render');
return <div>{name}</div>;
};
const UserAge = ({ age }) => {
console.log('UserAge render');
return <div>{age}</div>;
};
const UserProfile = ({ user }) => (
<>
<UserName name={user.name} />
<UserAge age={user.age} />
</>
);
- 优化 React Context 使用
说明
避免将所有状态放入单个 Context,拆分成多个 Context,降低无关状态变化引发组件重新渲染的概率。
示例
jsx
import React, { createContext, useContext, useState } from 'react';
const UserContext = createContext();
const ThemeContext = createContext();
const App = () => {
const [user] = useState({ name: 'Alice' });
const [theme] = useState('dark');
return (
<UserContext.Provider value={user}>
<ThemeContext.Provider value={theme}>
<UserProfile />
<ThemeSwitcher />
</ThemeContext.Provider>
</UserContext.Provider>
);
};
const UserProfile = () => {
const user = useContext(UserContext);
console.log('UserProfile render');
return <div>{user.name}</div>;
};
const ThemeSwitcher = () => {
const theme = useContext(ThemeContext);
console.log('ThemeSwitcher render');
return <div>{theme}</div>;
};
- 优化 Redux 使用
说明
- 使用
reselect
创建 memoized selectors,减少重复计算。 - 使用 thunk 或 saga 处理异步,避免频繁状态更新。
- 保持 reducers 纯净高效,避免复杂计算。
示例
javascript
import { createSelector } from 'reselect';
const selectItems = state => state.items;
const selectFilter = state => state.filter;
export const selectFilteredItems = createSelector(
[selectItems, selectFilter],
(items, filter) => items.filter(item => item.includes(filter))
);
- 批量更新
说明
React 默认事件循环内会批量处理状态更新,减少渲染次数。对于异步或非 React 事件,可以手动使用批量更新。
示例
javascript
import ReactDOM from 'react-dom';
const asyncUpdate = () => {
setTimeout(() => {
ReactDOM.unstable_batchedUpdates(() => {
setState1(...);
setState2(...);
});
}, 1000);
};
- 避免过深的组件嵌套
说明
过深嵌套使状态传递复杂且性能差,尽量扁平化组件结构,减少嵌套层级。
示例
jsx
import React from 'react';
// 不推荐深层嵌套结构
// <App><Layout><Sidebar><Menu><Item /></Item></Menu></Sidebar></Layout></App>
// 推荐扁平组合
const App = () => (
<>
<Sidebar />
<Content />
</>
);
- 使用合适的状态管理工具
说明
根据项目复杂度选择合适状态管理方案。小型项目用 Context 或轻量级库,如 Zustand,避免复杂度过高。
示例
javascript
import create from 'zustand';
const useStore = create(set => ({
count: 0,
increment: () => set(state => ({ count: state.count + 1 })),
}));
const Counter = () => {
const count = useStore(state => state.count);
const increment = useStore(state => state.increment);
return <button onClick={increment}>{count}</button>;
};
- 细颗粒度更新
说明
使用支持细粒度订阅的状态管理库(如 Zustand),或 Immer 进行不可变状态局部更新,减少无关组件渲染。
示例(Zustand)
javascript
import create from 'zustand';
const useStore = create(set => ({
user: { name: 'Alice', age: 25 },
setName: name => set(state => ({ user: { ...state.user, name } })),
setAge: age => set(state => ({ user: { ...state.user, age } })),
}));
const UserName = () => {
const name = useStore(state => state.user.name);
return <div>{name}</div>;
};
const UserAge = () => {
const age = useStore(state => state.user.age);
return <div>{age}</div>;
};
如需更详细示例或结合具体项目优化方案,欢迎告诉我!
plain
✅ 基础状态管理方案
useState
useState 是React 提供的最基本的 Hook,用于在函数组件中添加状态管理。它返回一个状态变量和一个更新状态的函数。
使用场景
适用于组件内部的简单状态,如表单字段、开关、Tab 状态等。
示例代码
tsx
const Counter = () => {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
);
};
useReducer
useReducer 是 useState 的替代方案,适合用于管理更复杂的状态逻辑。它通过 reducer函数来管理状态,类似于 Redux。
如果我们组件内部状态足够过,那么状态会逐渐趋于复杂,这时,我们需要更好的编程范式来解决状态存储与更新。
相信之前有同学使用过 redux,react 单向数据流告诉了我们,状态的管理需要注意以下几点:
- 使用一个对象存储变量(state)
- 订阅模式实现对于该对象的变更响应处理(reducer)
- 定义更改对象变更的动作 (action)
- 订阅该对象的变更,完成状态到视图的映射 (ui = fx(state))
用一句话来概括:状态由 useReducer 借助 reducer 生发,状态的变更由dispach 发起,最终状态变更驱动视图更新
使用场景
适用于包含多个字段、复杂更新逻辑的状态对象(类似 Redux 思维)。
状态更新依赖于先前状态
示例代码
tsx
const reducer = (state, action) => {
switch (action.type) {
case 'increment': return { ...state, count: state.count + 1 };
case 'decrement': return { ...state, count: state.count - 1 };
default: return state;
}
};
const Counter = () => {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<div>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<span>{state.count}</span>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
</div>
);
};
useContext
使用场景
跨组件传递状态,适用于配置、主题等全局只读/低频更新场景
避免了通过多层组件传递 props。它通过 Context 对象提供全局状态管理
示例代码
tsx
const ThemeContext = createContext('light');
const ThemeButton = () => {
const theme = useContext(ThemeContext);
return <button className={theme}>Theme: {theme}</button>;
};
综合示例
场景描述
管理全局主题和局部计数,使用 useReducer + useContext
实现共享状态。
示例代码
tsx
const GlobalStateContext = createContext();
const reducer = (state, action) => {
switch (action.type) {
case 'toggleTheme':
return { ...state, theme: state.theme === 'light' ? 'dark' : 'light' };
default:
return state;
}
};
const App = () => {
const [state, dispatch] = useReducer(reducer, { theme: 'light' });
return (
<GlobalStateContext.Provider value={{ state, dispatch }}>
<Page />
</GlobalStateContext.Provider>
);
};
const Page = () => {
const { state, dispatch } = useContext(GlobalStateContext);
return (
<button onClick={() => dispatch({ type: 'toggleTheme' })}>
当前主题: {state.theme}
</button>
);
};
🧩 集中状态管理方案
Redux Toolkit
Redux是一个非常棒的状态管理库,他提出了单向数据流,中间件等概念,能很好地进行状态结构设计。前面存储变量的对象,我们给他一个确切的定义一一状态仓库(store),不同于对象操作的是:任何时候你都不能直接去更改状态仓库(store)中的值,而是需要使用纯函数进行状态修改。
什么是纯函数?(我们在第一章详细讲过函数式编程思想)
- 如果函数的调用参数相同,则永远返回相同的结果。它不依赖于程序执行期间函数外部任何状态或数据的变化,必须只依赖于其输入参数。
- 该函数不会产生任何可观察的副作用

用法
tsx
import { configureStore, createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: 0,
reducers: {
increment: (state) => state + 1,
},
});
export const { increment } = counterSlice.actions;
const store = configureStore({ reducer: { counter: counterSlice.reducer } });
原理
Redux 本质是基于 纯函数 reducer
维护一个全局不可变状态树,使用 dispatch(action)
触发状态流转。Redux Toolkit 将 action
和 reducer
模板自动生成,提升开发效率。
Zustand
用法
tsx
import { create } from 'zustand';
const useStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}));
Zustand 是一个轻量级的状态管理库,适用于 React 应用程序。它易于使用且具有最小的 API 表面,适合管理全局状态。下面是关于如何安装、创建 store 以及在 React 中使用它的步骤。
安装 Zustand
首先,通过 npm 或 yarn 安装 zustand
:
bash
npm install zustand
或
bash
yarn add zustand
创建 Store
接下来,我们创建一个简单的 store 来管理全局状态。例如,假设我们要管理一个计数器的状态。
javascript
// store.js
import create from 'zustand';
// 创建一个 store
const useStore = create((set) => ({
count: 0, // 初始状态
increment: () => set((state) => ({ count: state.count + 1 })), // 增加计数器
decrement: () => set((state) => ({ count: state.count - 1 })), // 减少计数器
reset: () => set({ count: 0 }), // 重置计数器
}));
export default useStore;
在这里:
create
函数用于定义 store。set
函数用于更新状态。- 我们定义了三个操作:
increment
、decrement
和reset
,它们可以修改count
的值。
使用 Store
以下是一个示例组件,展示了如何读取和更新状态:
javascript
// App.js
import React from 'react';
import useStore from './store';
const App = () => {
// 从 store 中提取状态和方法
const count = useStore((state) => state.count);
const increment = useStore((state) => state.increment);
const decrement = useStore((state) => state.decrement);
const reset = useStore((state) => state.reset);
return (
<div style={{ textAlign: 'center', marginTop: '50px' }}>
<h1>计数器: {count}</h1>
<button onClick={increment} style={{ margin: '10px' }}>
增加
</button>
<button onClick={decrement} style={{ margin: '10px' }}>
减少
</button>
<button onClick={reset} style={{ margin: '10px' }}>
重置
</button>
</div>
);
};
export default App;
为什么推荐不使用 const { count, increment, decrement } = useStore()?
**useStore(state => state.xxx)**
:细粒度订阅
- 每一项是独立订阅 **:如果
**count**
变化,只有依赖**count**
的组件重新渲染;**increment**
、**decrement**
不变时不会引发无意义更新。** - 状态变化只影响使用它的组件部分,性能更优。
🚫** **const { count, increment } = useStore()**
:整对象订阅**
- 每次
**store**
中任何字段变化,组件都会重新渲染,性能差。 - 使用这种写法等于订阅了整个状态树,破坏 Zustand 的细粒度优化。
原理
Zustand 利用 Proxy 拦截依赖访问,自动追踪状态读取点,仅触发相关组件更新,极简、性能优。
Jotai
用法
Jotai 是一个用于 React 的状态管理库,专注于极简和原子化的设计理念。它通过创建原子(atom
)来管理状态,并在组件之间共享这些状态。
下面是关于如何安装 Jotai、创建 atom
并使用它的完整代码示例:
1. 安装 Jotai
首先,你需要安装 jotai
包。可以通过 npm 或 yarn 进行安装:
bash
npm install jotai
# 或者
yarn add jotai
2. 创建 Atom
在 Jotai 中,atom
是状态的基本单位。你可以使用 atom
函数来定义一个可共享的状态。
以下是一个简单的例子:
javascript
// 引入必要的模块
import { atom } from 'jotai';
// 创建一个 atom,表示一个计数器的初始值
const counterAtom = atom(0);
在这里,counterAtom
是一个原子状态,默认值为 0
。
3. 使用 Atom
接下来,我们可以在 React 组件中使用这个 atom
来读取和更新状态。
完整代码示例
jsx
// 引入必要的模块
import React from 'react';
import { atom, useAtom } from 'jotai';
// 创建一个 atom,表示计数器的初始值
const counterAtom = atom(0);
// 创建一个 React 组件来使用这个 atom
function Counter() {
// 使用 useAtom 钩子来读取和更新 atom 状态
const [count, setCount] = useAtom(counterAtom);
return (
<div>
<h1>当前计数: {count}</h1>
<button onClick={() => setCount(count + 1)}>增加</button>
<button onClick={() => setCount(count - 1)}>减少</button>
</div>
);
}
// 渲染组件到页面
export default function App() {
return (
<div>
<h1>Jotai 示例</h1>
<Counter />
</div>
);
}
4. 关键点解析
atom
** 的作用**
atom
是 Jotai 的核心概念,代表一个最小的状态单元。它可以是任何值(如数字、字符串、对象等)。你可以在多个组件之间共享同一个atom
。useAtom
** 钩子**
useAtom
是 Jotai 提供的一个 React 钩子,用于读取和更新atom
的状态。类似于 React 的useState
,但它可以跨组件共享状态。- 性能优化
Jotai 的设计非常高效,只有当状态发生变化时,相关组件才会重新渲染。这使得它非常适合大型项目中的状态管理。
原理
Jotai 将状态定义为原子 atom
,每个 atom 是最小状态单元,更新时只影响消费它的组件,基于 fine-grained reactivity。
可以被多个组件共享。使用Jotai 时,通过 useAtom hook 来获取和更新状态。Jotai 的实现灵活且性能高,适合于复杂的应用。
Jotai 的实现思路借鉴了 recoil,他们都是通过原子思路组织状态,因此称之为 atom state,原子状态自底向上组合派生,这和 redux是完全相反的。
🧬 深入实现原理
Redux
Redux的实现基于三大原则
1.单一状态树:整个应用的状态存储在一个对象树中,只存在于唯一的store 中。
状态是只读的:改变状态的唯一方法是触发 action,action 是一个描述发生什么的普通对象。
使用纯函数来执行修改:编写 reducers 来描述 action 如何改变状态树。
reducer(state, action)
返回新状态- 中间件链构建如洋葱模型
subscribe
注册监听useSelector/useDispatch
将 React 与 store 连接replaceReducer
:替换当前 reducer
Zustand
Zustand 的实现基于 hooks,核心是 create 函数,它接受一个 setter 函数,返回一个自定义的 hook 用于状态管理。状态和动作都在这个 hook 内部定义。
Zustand 的状态是由一个全局对象管理的,每次状态变化时,会更新这个对象并触发相关组件的重新渲染。
- 状态存储为 JS 对象
- 使用订阅函数注册组件依赖
- 组件只在依赖字段变化时更新
- 无需 reducer,直接修改状态
Jotai
Jotai 的实现基于 Atom,每个 Atom 都是一个状态单元。通过 useAtom hook 来连接 Atom 和组件,从而实现状态的读写。Jotai的状态管理方式类似于 Recoil,但 API更加简洁。
Jotai 的 Atom 是一个独立的状态容器,可以被多个组件共享。每次 Atom 的状态变化时,相关组件会自动重新渲染。
- 每个 atom 是状态单元
- atom 可组合、异步、依赖其他 atom
- 基于 reactive graph 调度更新
- 支持 Suspense 及 SSR
Redux、Zustand 和 Jotai 各有优劣:
• Redux(redux-toolkit):适合大型应用,社区成熟,生态丰富,但使用较复杂。
• Zustand:轻量简单,适合中小型应用,使用简单灵活。
• Jotai:基于 Atom 的状态管理,API 简洁,性能优异,适合各种规模的应用。
📌 Redux 原理深度剖析
将 Redux 与 React 结合。
使用 usesyncExternalstore 订阅外部状态变化
Redux 实现步骤概览
- 定义
Action
/Reducer
类型 - 创建
Store
:维护状态、派发逻辑 - 支持
combineReducers
合并状态树 - 提供
applyMiddleware
支持异步与日志中间件 - 与 React 结合使用
Provider
+ 自定义useStore
Redux 整体实现代码(简化)
tsx
// action.ts
export const INCREMENT = 'INCREMENT';
// reducer.ts
const counterReducer = (state = 0, action) => {
switch (action.type) {
case INCREMENT:
return state + 1;
default:
return state;
}
};
// store.ts
const createStore = (reducer) => {
let state = reducer(undefined, {});
let listeners = [];
const dispatch = (action) => {
state = reducer(state, action);
listeners.forEach((l) => l());
};
const subscribe = (listener) => listeners.push(listener);
const getState = () => state;
return { dispatch, subscribe, getState };
};
Mantine 的状态 TS 实现
jsx
// MantineState.ts
import { useState, useCallback, useRef } from 'react';
// Core Types and Interfaces
export interface MantineState<T = any> {
/** 当前值 */
value: T;
/** 更新值,可传入新值或回调(prev => newValue) */
setValue: (value: T | ((prev: T) => T)) => void;
/** 重置为初始默认值 */
reset: () => void;
}
export interface UseStateOptions<T> {
/** 默认值,如果不传则 value 初始为 undefined */
defaultValue?: T;
/** 值变化时回调 */
onChange?: (value: T) => void;
}
/**
* 通用状态管理 Hook,用于控制/非控制组件状态。
*
* @param options.defaultValue 初始默认值
* @param options.onChange 值更新时回调
* @returns MantineState<T> 对象,包含 value、setValue、reset
*/
export function useMantineState<T>(options: UseStateOptions<T> = {}): MantineState<T> {
const { defaultValue, onChange } = options;
// 仅在首次 render 时记录 defaultValue,用于 reset
const defaultRef = useRef<T | undefined>(defaultValue);
// 内部状态
const [value, setInternalValue] = useState<T>(defaultRef.current as T);
// setValue: 支持接收新值或(prev => newValue)
const setValue = useCallback(
(next: T | ((prev: T) => T)) => {
setInternalValue((prev) => {
const computed = typeof next === 'function' ? (next as (prev: T) => T)(prev) : next;
if (onChange) {
onChange(computed);
}
return computed;
});
},
[onChange]
);
// reset: 重置为记录的 defaultValue(useRef 保持不变)
const reset = useCallback(() => {
const initial = defaultRef.current as T;
setInternalValue(initial);
if (onChange) {
onChange(initial);
}
}, [onChange]);
return { value, setValue, reset };
}
jsx
// CounterExample.tsx
import React from 'react';
import { Button, Group, Text } from '@mantine/core';
import { useMantineState } from './MantineState';
interface CounterExampleProps {
/** 如果传入 controlledValue,则作为受控模式,否则内部维护状态 */
controlledValue?: number;
/** 受控模式下,value 改变时触发 */
onControlledChange?: (value: number) => void;
}
export const CounterExample: React.FC<CounterExampleProps> = ({
controlledValue,
onControlledChange,
}) => {
/**
* 如果外部传入 controlledValue,则视为受控模式;
* 否则使用内部默认值 0 作为非受控模式。
*/
const isControlled = controlledValue !== undefined;
const [{ value, setValue, reset }, setState] = React.useState<{
value: number;
setValue: (val: number | ((prev: number) => number)) => void;
reset: () => void;
}>(() => ({
value: (controlledValue ?? 0) as number,
setValue: (v) => {
// do nothing initially, 因为受控时不应内部更新
},
reset: () => {
// do nothing initially
},
}));
/**
* 处理受控或非受控逻辑:
* - 受控:使用父组件传入的 controlledValue,并把 onControlledChange 传递给 onChange
* - 非受控:使用 useMantineState 管理内部状态
*/
React.useEffect(() => {
if (isControlled) {
// 受控时,将外部状态同步到内部读取
setState({
value: controlledValue as number,
setValue: (val) => {
const computed =
typeof val === 'function' ? (val as (prev: number) => number)(controlledValue as number) : val;
onControlledChange && onControlledChange(computed);
},
reset: () => {
onControlledChange && onControlledChange(0);
},
});
} else {
// 非受控时,使用内部 useMantineState
const { value: internalValue, setValue: internalSet, reset: internalReset } = useMantineState<number>({
defaultValue: 0,
});
setState({
value: internalValue,
setValue: internalSet,
reset: internalReset,
});
}
// 仅在受控标志或受控值变化时重新计算
}, [isControlled, controlledValue, onControlledChange]);
const { value: count, setValue: setCount, reset: resetCount } = { value: value as number, setValue: setValue as any, reset: reset as any };
return (
<Group direction="column" spacing="sm">
<Text size="xl">Count: {count}</Text>
<Group>
<Button onClick={() => setCount((prev) => prev + 1)}>Increment</Button>
<Button onClick={() => setCount((prev) => prev - 1)}>Decrement</Button>
<Button onClick={resetCount} variant="outline">
Reset
</Button>
</Group>
</Group>
);
};
jsx
// App.tsx
import React, { useState } from 'react';
import { MantineProvider, Container, Title, Switch, Group } from '@mantine/core';
import { CounterExample } from './CounterExample';
export const App: React.FC = () => {
// 演示受控 vs 非受控模式
const [controlledCount, setControlledCount] = useState<number>(5);
const [useControlled, setUseControlled] = useState<boolean>(false);
return (
<MantineProvider withGlobalStyles withNormalizeCSS>
<Container size="sm" mt="lg">
<Group position="apart" mb="md">
<Title order={2}>Mantine 状态管理示例</Title>
<Switch
label="Use Controlled Mode"
checked={useControlled}
onChange={(e) => setUseControlled(e.currentTarget.checked)}
/>
</Group>
{useControlled ? (
<CounterExample
controlledValue={controlledCount}
onControlledChange={(val) => setControlledCount(val)}
/>
) : (
<CounterExample />
)}
</Container>
</MantineProvider>
);
};
MantineState.ts
导出useMantineState
Hook,内部维护value
、setValue
、reset
三项核心功能,并支持外部onChange
回调。CounterExample.tsx
示例展示受控(Controlled)和非受控(Uncontrolled)两种模式下如何使用useMantineState
。如果父组件传入controlledValue
,则视为受控模式,值跟随父组件同步;否则,使用内部状态管理。App.tsx
通过一个Switch
切换来演示"受控模式"与"非受控模式"下计数器的使用差异。- 整套代码基于 TypeScript,并使用 Mantine 组件做 UI 展示。
🌐 其他状态库使用与原理浅析
状态库 | 特点 | 使用场景 | 优点 | 不足 | 原理 |
---|---|---|---|---|---|
Valtio | + 使用 Proxy 实现响应式状态共享 + 通过代理对象实现响应式状态管理。其核心思想是使状态对象本身成为响应式对象,当状态变化时自动触发相应的更新。 | + 适合中小型项目 + 需要简单、直观的响应式状态管理 | + 简单易用,学习曲线低 + 无需特殊的 API 或复杂的配置 + 支持深度响应式,可以自动追踪嵌套属性的变化 | + 在大型项目中可能不够灵活。 + 生态系统相对较小,社区支持和扩展性较弱 | 基于快照追踪变更 |
XState | + 状态机建模,适合流程控制 + 基于有限状态机和状态图(Statechart),提供了一种更结构化的方式来管理复杂的状态逻辑。其设计理念是通过状态和事件的组合来描述系统的行为。 | + 适合复杂状态管理和业务逻辑的项目 + 需要清晰的状态转移和状态机图示的场景 | + 强大的状态机和状态图支持,适合复杂状态逻辑 + 可视化工具,便于设计和调试。 + 支持并发状态、层次状态、历史状态等高级功能。 | + 学习曲线较陡,需要对状态机有一定理解 + 初期配置和集成相对复杂 | 状态图、事件驱动模型 |
MobX | + 响应式范式,类属性驱动视图 + MobX 基于观察者模式,提供了自动追踪和响应式的数据管理。其核心思想是通过可观察状态、计算属性和动作来实现状态管理的自动更新。 | + 适合需要响应式数据管理和自动化状态更新的项目 + 适合中大型项目 | + 强大的响应式和自动化能力 + 简单的 API,易于理解和使用 + 良好的性能,支持复杂的状态管理 | + 在大型项目中可能导致调试困难 + 对状态变化的自动追踪需要谨慎使用,可能导致意外的更新和性能问题 | Observable & Reaction |
Recoil | + 原子状态共享,Jotai 源起 + Recoil 是 Facebook 开发的一种状态管理库,专为 React设计。其设计理念是通过原子(atom)和选择器 (selector)来管理状态和派生状态,使状态管理更加模块化和高效。 | + 适合中大型 React项目 + 需要细粒度状态管理和高效性能的场景 | + 与 React 紧密集成,使用简单 + 支持状态的细粒度更新和派生 + 良好的性能,适合大型应用 | + 尚处于发展阶段,生态系统和社区支持相对较弱 + 在某些复杂场景下,可能需要额外的配置和处理 | Graph 依赖追踪 |
Jotai | + 精简 Recoil,更易用 + 原子化状态 Jotai 借鉴了原子设计思想,将状态拆分成多个"原子(atom)",每个 atom 代表应用中最小、最独立的状态单元。组件可以直接订阅某个原子,当该原子更新时,仅重新渲染依赖它的组件,从而实现最小化的重渲染范围。 + API简洁 + 可组合性 原子(atom)可以通过派生(derived)或组合(combination)的方式,衍生出新的状态逻辑。例如,多个基础 atom 可以组合成一个派生值 atom,也可以在派生 atom 内部调用异步逻辑。这种设计让复杂业务逻辑能够优雅地拆分,保持代码清晰。 + 并发、异步支持 Jotai 内置对异步 atom 的支持:可以直接在 atom 中返回一个 Promise,或使用 atomWithQuery 、atomWithSubscription 等官方扩展包处理异步数据流,这使得异步数据加载、缓存及更新变得非常自然。 |
+ 中小型 React 应用 如果项目规模不是非常庞大,只需要一个简单、直观的状态管理方案,Jotai 能快速上手,减少模板化代码。 + 组件库或设计系统 由于 Jotai 原子化的思想,组件库可以将各自状态拆分为很多小 atom,消费者只需按需订阅对应 atom,即可实现定制化逻辑和性能优化。 + 需要频繁局部更新的界面 在一个大页面中,如果只有极少局部组件会因为某些状态变化重渲染,Jotai 的细粒度订阅机制能显著减少不必要的渲染,提升性能。 + 复杂的衍生/组合状态 当业务逻辑需要根据多个基础状态计算中间值、缓存异步请求结果等,Jotai 的派生 atom(derived atom)和异步 atom(async atom)可让代码组织更清晰,易于维护。 | + 轻量 + API 简洁,仅包含 atom() 、useAtom() 、少量工具函数,学习曲线低。 + 不需要手动编写 Reducer、Action、Context Provider,也无需在顶层包一大堆 Provider。 + 细粒度更新 + 组件只会监听它依赖的 atom,一旦其他 atom 更新,不会触发当前组件重渲染,性能更优。 + 即便同一个 atom 被多个组件订阅,只有真正更新时才触发依赖组件更新。 + 灵活可组合 + 派生 atom 可以基于多个基础 atom 计算中间状态;也可以在派生 atom 内部执行异步操作(返回 Promise),自动支持 Suspense。 + 支持自定义 atomWithReducer、atomWithImmer、atomWithStorage 等各种扩展,让常见场景(如持久化、本地存储、按需加载)更便捷。 + 内置异步/并发支持 + 异步 atom(返回 Promise)会自动抛出 Promise,可被 React.Suspense 捕获。 + Jotai 已与 React 的并发模式兼容良好,例如在某些场景下可以无痛使用 Suspense、Transition 等功能。 + TypeScript 支持友好 + 使用 atom<Type>(initialValue) 可以自动推断类型;派生 atom 也会根据读取/写入签名得到正确的类型提示。 + 与 TS 配合,能在开发时获得更好的类型检查,减少隐式错误。 |
+ 生态相对年轻 + 依赖第三方包分散 + 并发更新逻辑需要小心 + 调试工具尚不完善 + 需要谨慎设计 atom 拆分 | Atom 层级、拓扑调度 |
Zustand | + 简洁状态容器 + Zustand 是一个轻量级的状态管理库,强调简洁和灵活性。其设计理念是通过简单的 API和灵活的配置来实现状态管理,使状态管理变得更加轻松和直观。 | + 适合中小型项目 + 需要简单、灵活的状态管理方案 | + 简单易用,学习曲线低 + 轻量级,无需复杂的配置和依赖 + 支持多种使用模式,包括中间件和持久化等 | + 在大型项目中可能不够强大 + 生态系统相对较小,社区支持和扩展性较弱 | Proxy+订阅更新 |
✅ 总结
React19 继续强化并发渲染与 Suspense 能力,对状态管理的要求也随之提高。选择方案时,需结合以下因素:
- 状态共享范围:局部 vs 全局
- 状态复杂度:字段多少,业务复杂性
- 性能要求:更新频率、UI 粒度
- 团队协作:是否规范化、文档丰富
✅ 结论:小项目优先使用
Zustand
/Jotai
,中大型项目使用Redux Toolkit
。核心原则:精简状态,优化更新点,提升可维护性与性能。