React19 状态管理方案与原理剖析

React19 状态管理方案与原理剖析


🧠 补充资料


📌 相关面试真题

用过哪些状态管理方案,怎么选择?

常用方案包括:

  • React 内置:useStateuseReduceruseContext
  • 第三方库: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 ThunkRedux Saga,用于处理异步操作和副作用。
  • 数据流
    Redux 采用单向数据流,动作从组件派发(dispatch),经过中间件处理后,到达 reducer,生成新的状态,然后更新到组件。这种单向数据流简化了数据的跟踪和调试。
  • 工具支持
    Redux 有强大的工具支持,如 Redux DevTools,可以方便地查看和调试应用的状态变化。

状态改变引发视图频繁更新,怎么优化?

宏观层面:

  • 借助状态切分与拆分组件
  • 状态局部化(只把共享状态抽出)
  • 避免深层嵌套组件依赖全局状态
  • 使用惰性加载或 Suspense 控制渲染

具体从以下几个方面入手:

  1. 避免不必要的状态更新

说明

只在必要时更新状态,避免无意义的重复更新。利用 React.memouseCallback 等手段,避免因函数或对象变化导致子组件重复渲染。

示例

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

  1. 拆分组件

说明

将大组件拆成多个小组件,每个组件只管理自己的状态,状态变化只影响对应组件,减少整体渲染。

示例

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

  1. 优化 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>;
};

  1. 优化 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))
);

  1. 批量更新

说明

React 默认事件循环内会批量处理状态更新,减少渲染次数。对于异步或非 React 事件,可以手动使用批量更新。

示例

javascript 复制代码
import ReactDOM from 'react-dom';

const asyncUpdate = () => {
  setTimeout(() => {
    ReactDOM.unstable_batchedUpdates(() => {
      setState1(...);
      setState2(...);
    });
  }, 1000);
};

  1. 避免过深的组件嵌套

说明

过深嵌套使状态传递复杂且性能差,尽量扁平化组件结构,减少嵌套层级。

示例

jsx 复制代码
import React from 'react';

// 不推荐深层嵌套结构
// <App><Layout><Sidebar><Menu><Item /></Item></Menu></Sidebar></Layout></App>
// 推荐扁平组合
const App = () => (
  <>
    <Sidebar />
    <Content />
  </>
);

  1. 使用合适的状态管理工具

说明

根据项目复杂度选择合适状态管理方案。小型项目用 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>;
};

  1. 细颗粒度更新

说明

使用支持细粒度订阅的状态管理库(如 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 单向数据流告诉了我们,状态的管理需要注意以下几点:

  1. 使用一个对象存储变量(state)
  2. 订阅模式实现对于该对象的变更响应处理(reducer)
  3. 定义更改对象变更的动作 (action)
  4. 订阅该对象的变更,完成状态到视图的映射 (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)中的值,而是需要使用纯函数进行状态修改。

什么是纯函数?(我们在第一章详细讲过函数式编程思想)

  1. 如果函数的调用参数相同,则永远返回相同的结果。它不依赖于程序执行期间函数外部任何状态或数据的变化,必须只依赖于其输入参数。
  2. 该函数不会产生任何可观察的副作用
用法
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 将 actionreducer 模板自动生成,提升开发效率。


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 函数用于更新状态。
  • 我们定义了三个操作:incrementdecrementreset,它们可以修改 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. 关键点解析

  1. atom** 的作用**
    atom 是 Jotai 的核心概念,代表一个最小的状态单元。它可以是任何值(如数字、字符串、对象等)。你可以在多个组件之间共享同一个 atom
  2. useAtom** 钩子**
    useAtom 是 Jotai 提供的一个 React 钩子,用于读取和更新 atom 的状态。类似于 React 的 useState,但它可以跨组件共享状态。
  3. 性能优化
    Jotai 的设计非常高效,只有当状态发生变化时,相关组件才会重新渲染。这使得它非常适合大型项目中的状态管理。
原理

Jotai 将状态定义为原子 atom,每个 atom 是最小状态单元,更新时只影响消费它的组件,基于 fine-grained reactivity。

可以被多个组件共享。使用Jotai 时,通过 useAtom hook 来获取和更新状态。Jotai 的实现灵活且性能高,适合于复杂的应用。

Jotai 的实现思路借鉴了 recoil,他们都是通过原子思路组织状态,因此称之为 atom state,原子状态自底向上组合派生,这和 redux是完全相反的。


🧬 深入实现原理

Redux

Redux的实现基于三大原则

1.单一状态树:整个应用的状态存储在一个对象树中,只存在于唯一的store 中。

  1. 状态是只读的:改变状态的唯一方法是触发 action,action 是一个描述发生什么的普通对象。

  2. 使用纯函数来执行修改:编写 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 实现步骤概览

  1. 定义 Action / Reducer 类型
  2. 创建 Store:维护状态、派发逻辑
  3. 支持 combineReducers 合并状态树
  4. 提供 applyMiddleware 支持异步与日志中间件
  5. 与 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,内部维护 valuesetValuereset 三项核心功能,并支持外部 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,或使用 atomWithQueryatomWithSubscription 等官方扩展包处理异步数据流,这使得异步数据加载、缓存及更新变得非常自然。 + 中小型 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

核心原则:精简状态,优化更新点,提升可维护性与性能。

相关推荐
henujolly18 分钟前
网络资源缓存
前端
yuren_xia3 小时前
Spring Boot中保存前端上传的图片
前端·spring boot·后端
普通网友4 小时前
Web前端常用面试题,九年程序人生 工作总结,Web开发必看
前端·程序人生·职场和发展
站在风口的猪11086 小时前
《前端面试题:CSS对浏览器兼容性》
前端·css·html·css3·html5
青莳吖7 小时前
使用 SseEmitter 实现 Spring Boot 后端的流式传输和前端的数据接收
前端·spring boot·后端
CodeCraft Studio8 小时前
PDF处理控件Aspose.PDF教程:在 C# 中更改 PDF 页面大小
前端·pdf·c#
拉不动的猪8 小时前
TS常规面试题1
前端·javascript·面试
再学一点就睡8 小时前
实用为王!前端日常工具清单(调试 / 开发 / 协作工具全梳理)
前端·资讯·如何当个好爸爸
穗余9 小时前
NodeJS全栈开发面试题讲解——P5前端能力(React/Vue + API调用)
javascript·vue.js·react.js
Jadon_z9 小时前
vue2 项目中 npm run dev 运行98% after emitting CopyPlugin 卡死
前端·npm