当前位置: 首页 > news >正文

React进阶:状态管理选择题

React进阶:状态管理选择题

引言

随着React应用复杂度增加,选择合适的状态管理方案成为我们面临的关键决策。

状态管理本质上是解决"谁来存储数据"以及"如何更新和分发这些数据"的问题。在React生态中,随着应用规模扩大,不同组件间的状态共享和同步变得越来越复杂,这促使了众多状态管理库的诞生。

状态管理方案全景

React内置状态管理

React本身提供了多种内置状态管理机制,包括组件局部状态和Context API。这些原生解决方案是理解其他状态管理库的基础。

useState:组件局部状态

useState钩子提供了最简单的状态管理方式,适用于组件内部状态。它遵循不可变性原则,每次状态更新都会触发组件重新渲染。

function Counter() {// 局部组件状态const [count, setCount] = useState(0);return (<div><p>Count: {count}</p><button onClick={() => setCount(count + 1)}>+</button><button onClick={() => setCount(prevCount => prevCount - 1)}>-</button></div>);
}

useState的关键特性是简单直观,但当状态逻辑复杂时会变得难以维护。例如,当多个状态相互依赖时,使用多个useState调用会导致代码变得零散且难以追踪状态变化。

useReducer:复杂状态逻辑

useReducer提供了更结构化的状态管理方式,特别适合处理包含多个子值的复杂状态逻辑。它借鉴了Redux的设计理念,通过分发action来更新状态。

function complexCounter() {// 复杂状态逻辑const [state, dispatch] = useReducer((state, action) => {switch (action.type) {case 'increment': return {count: state.count + 1};case 'decrement': return {count: state.count - 1};case 'reset': return {count: 0};case 'set': return {count: action.payload};default: return state;}}, {count: 0});return (<div><p>Count: {state.count}</p><button onClick={() => dispatch({type: 'increment'})}>+</button><button onClick={() => dispatch({type: 'decrement'})}>-</button><button onClick={() => dispatch({type: 'reset'})}>Reset</button><button onClick={() => dispatch({type: 'set', payload: 100})}>Set to 100</button></div>);
}

useReducer的优势在于集中管理相关状态逻辑,使状态变化更可预测。然而,它仍然局限于单个组件或通过props传递,无法直接解决跨组件状态共享问题。

Context API:跨组件状态共享

Context API提供了一种在组件树中共享状态的方式,无需通过props层层传递。它是React内置的"依赖注入"系统。

// 创建上下文
const ThemeContext = React.createContext({theme: 'light',setTheme: () => {}
});function App() {const [theme, setTheme] = useState('light');// 提供上下文值给整个组件树return (<ThemeContext.Provider value={{theme, setTheme}}><Header /><Main /><Footer /></ThemeContext.Provider>);
}function ThemedButton() {// 消费上下文,无需通过props传递const {theme, setTheme} = useContext(ThemeContext);return (<button style={{background: theme === 'light' ? '#fff' : '#000', color: theme === 'light' ? '#000' : '#fff'}}onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>当前主题: {theme} (点击切换)</button>);
}

Context API解决了props drilling问题,但它本身并不是一个完整的状态管理解决方案。每当Provider的值变化时,所有消费该Context的组件都会重新渲染,这可能导致性能问题。此外,大型应用中使用多个Context会导致组件嵌套过深,降低代码可读性。

适用场景:组件树深度适中,状态共享需求简单的中小型应用。典型用例包括主题切换、用户偏好设置、多语言支持等全局配置。

优势

  • 零依赖,React官方支持,保证长期维护
  • 学习成本低,API简洁,与React组件模型一致
  • 无需额外库,减小打包体积,降低项目复杂度
  • 无需额外配置,可即时使用

局限性

  • Context触发的重渲染优化困难,可能导致性能问题
  • 多Context组合使用导致嵌套地狱,降低代码可读性
  • 缺乏专门的开发者工具支持,调试体验较差
  • 状态变化追踪困难,无法实现时间旅行调试
  • 难以实现状态持久化、中间件等高级功能

Redux生态系统

Redux作为React生态中最成熟的状态管理解决方案,通过单一数据源、不可变数据和纯函数reducer实现可预测的状态管理。现代Redux通过Redux Toolkit(RTK)大幅简化了开发体验。

Redux核心概念

Redux基于三个基本原则:

  1. 单一数据源:整个应用的状态存储在单个store的对象树中
  2. 状态只读:唯一改变状态的方法是触发action
  3. 使用纯函数进行修改:reducer是纯函数,接收先前的状态和action,返回新状态

传统Redux实现需要手动创建action creators、reducers和store,代码量较大:

// 定义action类型
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';// 创建action creators
const increment = () => ({ type: INCREMENT });
const decrement = () => ({ type: DECREMENT });// 创建reducer
const counterReducer = (state = { value: 0 }, action) => {switch (action.type) {case INCREMENT:return { ...state, value: state.value + 1 };case DECREMENT:return { ...state, value: state.value - 1 };default:return state;}
};// 创建store
const store = createStore(counterReducer);// 组件中使用
function Counter() {const count = useSelector(state => state.value);const dispatch = useDispatch();return (<div><p>Count: {count}</p><button onClick={() => dispatch(increment())}>+</button><button onClick={() => dispatch(decrement())}>-</button></div>);
}
Redux Toolkit:现代Redux开发

Redux Toolkit极大简化了Redux开发,减少了样板代码,提供了更现代化的API:

// 创建slice
import { createSlice, configureStore } from '@reduxjs/toolkit';const counterSlice = createSlice({name: 'counter',initialState: { value: 0 },reducers: {increment: state => {// RTK使用Immer库,允许"直接修改"状态// 实际上Immer会在底层确保不可变性state.value += 1;},decrement: state => {state.value -= 1;},incrementByAmount: (state, action) => {state.value += action.payload;}}
});// 导出action creators
export const { increment, decrement, incrementByAmount } = counterSlice.actions;// 创建store
const store = configureStore({reducer: {counter: counterSlice.reducer}
});// 组件中使用
function Counter() {// 使用选择器获取状态片段const count = useSelector(state => state.counter.value);const dispatch = useDispatch();return (<div><p>Count: {count}</p><button onClick={() => dispatch(increment())}>+</button><button onClick={() => dispatch(decrement())}>-</button><button onClick={() => dispatch(incrementByAmount(10))}>+10</button></div>);
}

RTK不仅简化了Redux的使用,还内置了多种开发工具和最佳实践,包括Immer(简化不可变更新)、Thunk(处理异步逻辑)和DevTools扩展支持。

异步操作处理

处理API请求等异步操作是状态管理的重要部分。Redux通过中间件处理异步逻辑,RTK提供了createAsyncThunk简化这一过程:

import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';// 创建异步thunk
export const fetchUserById = createAsyncThunk('users/fetchById',async (userId, { rejectWithValue }) => {try {const response = await fetch(`https://api.example.com/users/${userId}`);if (!response.ok) throw new Error('Server Error');return await response.json();} catch (error) {return rejectWithValue(error.message);}}
);const userSlice = createSlice({name: 'user',initialState: {data: null,status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'error: null},reducers: {},extraReducers: (builder) => {builder.addCase(fetchUserById.pending, (state) => {state.status = 'loading';}).addCase(fetchUserById.fulfilled, (state, action) => {state.status = 'succeeded';state.data = action.payload;}).addCase(fetchUserById.rejected, (state, action) => {state.status = 'failed';state.error = action.payload;});}
});// 组件中使用
function UserProfile({ userId }) {const dispatch = useDispatch();const { data: user, status, error } = useSelector(state => state.user);useEffect(() => {if (status === 'idle') {dispatch(fetchUserById(userId));}}, [userId, status, dispatch]);if (status === 'loading') return <div>Loading...</div>;if (status === 'failed') return <div>Error: {error}</div>;if (!user) return null;return (<div><h1>{user.name}</h1><p>Email: {user.email}</p></div>);
}

这种模式提供了一致的方式处理异步操作生命周期(开始、成功、失败),使状态管理更加可预测。

适用场景:大型企业级应用,复杂的状态逻辑,需要严格状态追踪的场景,多团队协作的项目。典型用例包括后台管理系统、复杂表单应用、大型电商平台等。

优势

  • 成熟的生态系统,大量中间件和第三方集成
  • 强大的开发者工具,时间旅行调试使问题定位更容易
  • 状态变化可预测,严格的单向数据流提高代码可维护性
  • RTK极大简化了模板代码,提升开发效率
  • 与React Router、React Query等库良好集成
  • 丰富的文档和社区支持,学习资源丰富
  • 适合大型团队协作,状态管理规范统一

局限性

  • 学习曲线相对陡峭,概念较多
  • 即使使用RTK,模板代码量仍高于其他轻量级方案
  • 小型应用可能显得过度设计
  • 引入额外的运行时开销和打包体积
  • 可能引入额外的性能考量,需要手动优化选择器

Zustand:轻量级状态管理

Zustand是一个极简的状态管理库,专注于简化API并保持高性能。它结合了Redux的可预测性和React hooks的简洁性,提供了更现代化的开发体验。

创建和使用Store

Zustand的核心API非常简洁,使用闭包创建store并通过hook访问状态:

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 }),incrementByAmount: (amount) => set(state => ({ count: state.count + amount }))
}));// 组件中使用
function Counter() {// 解构需要的状态和操作const { count, increment, decrement, reset, incrementByAmount } = useStore();// 也可以选择性获取部分状态,优化重渲染// const count = useStore(state => state.count);// const increment = useStore(state => state.increment);return (<div><p>Count: {count}</p><button onClick={increment}>+</button><button onClick={decrement}>-</button><button onClick={reset}>Reset</button><button onClick={() => incrementByAmount(10)}>+10</button></div>);
}

与Redux相比,Zustand省略了action types、reducers和dispatch等概念,直接通过函数更新状态。这种简化的API大大降低了学习成本和代码量。

中间件和持久化

尽管API简单,Zustand仍支持中间件系统,可以扩展store功能:

import create from 'zustand';
import { persist, devtools } from 'zustand/middleware';// 使用中间件
const useStore = create(devtools(                // 开发者工具支持persist(               // 状态持久化(set, get) => ({count: 0,increment: () => set(state => ({ count: state.count + 1 })),incrementAsync: () => {setTimeout(() => {// 可以使用get获取当前状态set({ count: get().count + 1 });}, 1000);}}),{name: 'counter-storage', // 持久化存储的键名getStorage: () => localStorage, // 使用localStorage}))
);

这种模块化的中间件设计允许灵活组合不同功能,同时保持API简洁。

状态切片和组合

对于大型应用,Zustand支持将状态分割为多个"切片",然后组合使用:

// 用户相关状态
const createUserSlice = (set) => ({user: null,loading: false,error: null,login: async (credentials) => {set({ loading: true });try {const user = await loginApi(credentials);set({ user, loading: false, error: null });} catch (error) {set({ error: error.message, loading: false });}},logout: () => set({ user: null })
});// 购物车相关状态
const createCartSlice = (set, get) => ({items: [],totalItems: 0,addItem: (item) => {set((state) => ({items: [...state.items, item],totalItems: state.totalItems + 1}));},removeItem: (itemId) => {set((state) => ({items: state.items.filter(item => item.id !== itemId),totalItems: state.totalItems - 1}));},clearCart: () => set({ items: [], totalItems: 0 })
});// 组合多个状态切片
const useStore = create((set, get) => ({...createUserSlice(set, get),...createCartSlice(set, get)
}));// 组件中使用
function ShoppingCart() {const { items, addItem, removeItem, user } = useStore();return (<div>{user ? (<><h2>{user.name}的购物车</h2><ul>{items.map(item => (<li key={item.id}>{item.name} - ${item.price}<button onClick={() => removeItem(item.id)}>移除</button></li>))}</ul></>) : (<p>请先登录</p>)}</div>);
}

这种组合方式可以在保持简洁API的同时,实现与Redux类似的代码组织结构。

适用场景:中小型应用,需要全局状态但希望保持简单API的项目,对Redux感到疲惫的团队。典型用例包括交互性应用、单页应用、工具类网站等。

优势

  • 极简API,几乎零配置,减少样板代码
  • 基于hook的直观使用方式,无需Provider包裹
  • 自动优化渲染,仅在使用的状态变化时更新
  • 支持中间件、持久化等扩展功能
  • 体积小(约3KB),学习成本低
  • 类型支持良好,TypeScript开发体验优秀
  • 可以逐步采用,无需重构整个应用

局限性

  • 缺乏时间旅行调试等高级调试功能(虽然有Redux DevTools集成)
  • 大型应用可能需要更严格的状态组织方式
  • 异步操作处理相对基础,缺少内置的异步状态管理模式
  • 不支持状态间复杂依赖关系的自动计算
  • 社区规模小于Redux,第三方扩展较少

Recoil:原子化状态管理

Recoil是Facebook推出的状态管理库,设计理念基于"原子状态"和"选择器",提供了细粒度的状态控制和状态派生能力。

原子(Atoms)

原子是Recoil中最小的状态单位,类似于React的useState,但可以在组件间共享:

import { atom, useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';// 定义原子状态
const countAtom = atom({key: 'countAtom', // 全局唯一的标识符default: 0,       // 默认值
});function Counter() {// 类似useState的用法const [count, setCount] = useRecoilState(countAtom);return (<div><p>Count: {count}</p><button onClick={() => setCount(count + 1)}>+</button></div>);
}function DisplayCount() {// 只读状态,不会触发不必要的重渲染const count = useRecoilValue(countAtom);return <div>Current count: {count}</div>;
}function CounterControls() {// 只获取setter函数,不订阅状态变化const setCount = useSetRecoilState(countAtom);return (<div><button onClick={() => setCount(0)}>Reset</button><button onClick={() => setCount(c => c + 5)}>+5</button></div>);
}

原子的关键特性是可以精确控制哪些组件订阅哪些状态,从而优化渲染性能。每个原子变化只会触发使用该原子的组件重新渲染。

选择器(Selectors)

选择器是从原子或其他选择器派生的计算状态,类似于Redux的选择器或Vue的计算属性:

import { atom, selector, useRecoilValue } from 'recoil';// 基础原子
const countAtom = atom({key: 'countAtom',default: 0,
});// 派生状态
const doubleCountSelector = selector({key: 'doubleCount',get: ({get}) => {const count = get(countAtom); // 获取依赖的原子状态return count * 2;},
});// 多atom依赖的复杂选择器
const todoListAtom = atom({key: 'todoList',default: [],
});const todoFilterAtom = atom({key: 'todoFilter',default: 'all', // 'all' | 'completed' | 'uncompleted'
});const filteredTodoListSelector = selector({key: 'filteredTodoList',get: ({get}) => {const filter = get(todoFilterAtom);const list = get(todoListAtom);switch (filter) {case 'completed':return list.filter(item => item.completed);case 'uncompleted':return list.filter(item => !item.completed);default:return list;}},
});function TodoStats() {const count = useRecoilValue(countAtom);const doubleCount = useRecoilValue(doubleCountSelector);const filteredTodos = useRecoilValue(filteredTodoListSelector);return (<div><p>Count: {count}</p><p>Double: {doubleCount}</p><p>Filtered todos: {filteredTodos.length}</p></div>);
}

选择器自动追踪依赖关系,当依赖的原子状态变化时,选择器会重新计算并通知使用该选择器的组件更新。

异步选择器

Recoil支持异步选择器,可以直接处理API请求等异步操作:

const userInfoQuery = selectorFamily({key: 'userInfo',get: (userId) => async () => {const response = await fetch(`https://api.example.com/users/${userId}`);if (!response.ok) throw new Error('Failed to fetch user');return await response.json();},
});function UserProfile({ userId }) {const userInfo = useRecoilValue(userInfoQuery(userId));return (<div><h1>{userInfo.name}</h1><p>Email: {userInfo.email}</p></div>);
}

使用Suspense和ErrorBoundary可以优雅处理加载和错误状态:

function App() {return (<RecoilRoot><ErrorBoundary fallback={<div>Error loading user</div>}><React.Suspense fallback={<div>Loading...</div>}><UserProfile userId="123" /></React.Suspense></ErrorBoundary></RecoilRoot>);
}

适用场景:需要细粒度状态控制与派生状态的复杂应用,特别是有大量相互依赖状态的场景。典型用例包括数据分析应用、复杂表单、实时协作工具等。

优势

  • 原子化状态设计,提供最细粒度的控制
  • 强大的派生状态(selector)能力,自动追踪依赖
  • 异步状态处理简洁,与React Suspense集成良好
  • Facebook官方支持,与React理念一致
  • 出色的状态依赖追踪,优化渲染性能
  • 基于钩子的简洁API,学习曲线平缓
  • 天然支持代码分割,适合大型应用

局限性

  • API相对复杂,概念较多
  • 需要RecoilRoot包裹,额外的设置步骤
  • 相比其他方案生态系统不够成熟
  • 文档和最佳实践相对缺乏
  • 持久化等高级功能需要额外实现
  • 尚未达到1.0版本,API可能变动

Jotai:轻量级原子状态

Jotai是受Recoil启发的轻量级原子状态管理库,提供了更简化的API和更小的体积。

基本使用

Jotai的基本API比Recoil更简洁:

import { atom, useAtom } from 'jotai';// 创建原子
const countAtom = atom(0);// 派生原子
const doubleAtom = atom(get => get(countAtom) * 2);// 可写的派生原子
const incrementAtom = atom(get => get(countAtom),(get, set, arg) => set(countAtom, get(countAtom) + 1)
);function Counter() {const [count, setCount] = useAtom(countAtom);const [doubleCount] = useAtom(doubleAtom);const [, increment] = useAtom(incrementAtom); // 只使用写入器return (<div><p>Count: {count}</p><p>Double: {doubleCount}</p><button onClick={() => setCount(c => c + 1)}>+</button><button onClick={increment}>Increment</button></div>);
}

Jotai的原子可以依赖其他原子,创建复杂的状态依赖图,同时保持API简洁。

异步原子

Jotai支持异步原子,可以直接处理Promise:

const userAtom = atom(async () => {const response = await fetch('https://api.example.com/user');return response.json();
});// 或者使用已有原子创建异步派生
const userIdAtom = atom('123');
const userByIdAtom = atom(async get => {const id = get(userIdAtom);const response = await fetch(`https://api.example.com/users/${id}`);return response.json();
});function UserProfile() {const [user] = useAtom(userByIdAtom);return (<div>{user.loading ? (<p>Loading...</p>) : (<><h1>{user.name}</h1><p>Email: {user.email}</p></>)}</div>);
}

与Recoil类似,Jotai也可以与React Suspense结合使用。

适用场景:需要Recoil类似功能但希望更轻量的应用,对状态颗粒度有高要求的项目。典型用例包括交互性强的UI组件、需要细粒度状态控制的小型应用等。

优势

  • 极简API,核心概念少
  • 体积小(约2.5KB),性能优秀
  • 无需Provider包裹(可选)
  • 良好的TypeScript支持
  • 原子化设计,优化渲染性能
  • 与React Suspense兼容
  • 支持与Immer集成,简化不可变更新

局限性

  • 相对较新,生态不够成熟
  • 大型应用状态组织能力有限
  • 文档和示例相对简单
  • 缺乏专门的调试工具
  • 社区支持不如Redux和Recoil

实际项目选型决策框架

项目规模与复杂度评估

选择状态管理方案首先需要考虑项目规模和复杂度,不同规模的项目有不同的最佳实践:

小型应用(组件数<20)

  • 推荐:React内置状态管理(useState + useContext)

    • 理由:无需额外依赖,学习成本低,对于简单应用足够用
    • 实施方案:创建几个针对性的Context,避免全局Context过大导致性能问题
  • 替代:Zustand(需要更好的开发体验)

    • 理由:API简单,几乎零配置,可以逐步采用
    • 实施方案:创建单一store,根据功能模块组织状态
// 小型应用使用Context的最佳实践
// 按照功能拆分多个Context,而不是使用单一全局Context
const AuthContext = React.createContext(null);
const ThemeContext = React.createContext(null);
const FeatureFlagsContext = React.createContext(null);function App() {const [user, setUser] = useState(null);const [theme, setTheme] = useState('light');const [features, setFeatures] = useState({newDashboard: false,betaFeatures: false});// 登录逻辑const login = async (credentials) => {// 实现登录逻辑const user = await loginApi(credentials);setUser(user);};const logout = () => setUser(null);return (<AuthContext.Provider value={{ user, login, logout }}><ThemeContext.Provider value={{ theme, setTheme }}><FeatureFlagsContext.Provider value={{ features, setFeatures }}><MainApp /></FeatureFlagsContext.Provider></ThemeContext.Provider></AuthContext.Provider>);
}

中型应用(组件数20-100)

  • 推荐:Zustand或Jotai

    • 理由:平衡了简洁性和功能性,学习成本适中
    • 实施方案:按功能域划分状态切片,避免单一巨大store
  • 替代:Redux Toolkit(预计未来会扩展)

    • 理由:为未来应用增长提供扩展性,规范化的状态管理
    • 实施方案:使用RTK,按照领域模型组织状态,避免过度设计
// 中型应用使用Zustand的状态组织最佳实践
// 1. 按功能划分状态切片
const createAuthSlice = (set, get) => ({user: null,loading: false,error: null,login: async (credentials) => {set({ loading: true, error: null });try {const response = await fetch('/api/login', {method: 'POST',body: JSON.stringify(credentials),headers: { 'Content-Type': 'application/json' }});if (!response.ok) throw new Error('Login failed');const user = await response.json();set({ user, loading: false });return user;} catch (error) {set({ error: error.message, loading: false });throw error;}},logout: () => set({ user: null })
});const createProductsSlice = (set, get) => ({products: [],loading: false,error: null,fetchProducts: async (category) => {set({ loading: true, error: null });try {const response = await fetch(`/api/products?category=${category}`);if (!response.ok) throw new Error('Failed to fetch products');const products = await response.json();set({ products, loading: false });} catch (error) {set({ error: error.message, loading: false });}}
});// 2. 组合所有切片
import create from 'zustand';
import { persist } from 'zustand/middleware';const useStore = create(persist((set, get) => ({...createAuthSlice(set, get),...createProductsSlice(set, get)}),{ name: 'app-store' } // 持久化到localStorage)
);// 3. 在组件中使用,只订阅需要的部分状态
function ProductList({ category }) {// 只订阅products相关状态,auth状态变化不会导致此组件重渲染const { products, loading, error, fetchProducts } = useStore(state => ({products: state.products,loading: state.loading,error: state.error,fetchProducts: state.fetchProducts}));useEffect(() => {fetchProducts(category);}, [category, fetchProducts]);if (loading) return <div>Loading products...</div>;if (error) return <div>Error: {error}</div>;return (<div className="product-list">{products.map(product => (<ProductCard key={product.id} product={product} />))}</div>);
}

大型应用(组件数>100)

  • 推荐:Redux Toolkit

    • 理由:成熟的生态系统,严格的状态更新规则,利于团队协作
    • 实施方案:按领域模型设计状态结构,规范化状态管理流程
  • 替代:Recoil(特别是对原子化状态有需求时)

    • 理由:细粒度状态控制,适合复杂UI和频繁局部更新
    • 实施方案:按功能设计原子族,利用选择器优化派生状态
// 大型应用使用Redux Toolkit的组织方式
// 1. 按领域模型划分状态切片
// auth/authSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';export const loginUser = createAsyncThunk('auth/login',async (credentials, { rejectWithValue }) => {try {const response = await fetch('/api/login', {method: 'POST',body: JSON.stringify(credentials),headers: { 'Content-Type': 'application/json' }});if (!response.ok) throw new Error('Login failed');return await response.json();} catch (error) {return rejectWithValue(error.message);}}
);const authSlice = createSlice({name: 'auth',initialState: {user: null,status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'error: null},reducers: {logout: (state) => {state.user = null;state.status = 'idle';}},extraReducers: (builder) => {builder.addCase(loginUser.pending, (state) => {state.status = 'loading';}).addCase(loginUser.fulfilled, (state, action) => {state.status = 'succeeded';state.user = action.payload;state.error = null;}).addCase(loginUser.rejected, (state, action) => {state.status = 'failed';state.error = action.payload;});}
});export const { logout } = authSlice.actions;
export default authSlice.reducer;// products/productsSlice.js, orders/ordersSlice.js 等按类似方式实现// 2. 组合所有slice到store
// store.js
import { configureStore } from '@reduxjs/toolkit';
import authReducer from './auth/authSlice';
import productsReducer from './products/productsSlice';
import ordersReducer from './orders/ordersSlice';export const store = configureStore({reducer: {auth: authReducer,products: productsReducer,orders: ordersReducer}
});// 3. 提供选择器函数优化组件重渲染
// auth/selectors.js
export const selectUser = state => state.auth.user;
export const selectAuthStatus = state => state.auth.status;
export const selectAuthError = state => state.auth.error;// 4. 组件中使用
import { useSelector, useDispatch } from 'react-redux';
import { loginUser } from './auth/authSlice';
import { selectUser, selectAuthStatus, selectAuthError } from './auth/selectors';function LoginPage() {const dispatch = useDispatch();const user = useSelector(selectUser);const status = useSelector(selectAuthStatus);const error = useSelector(selectAuthError);const handleSubmit = async (event) => {event.preventDefault();const credentials = {email: event.target.email.value,password: event.target.password.value};await dispatch(loginUser(credentials));};// 渲染逻辑...
}

团队因素考量

技术选型不仅取决于技术本身,还与团队组成和技术背景密切相关:

React新手团队

  • 推荐:内置状态管理 → Zustand
    • 理由:学习曲线平缓,API直观,减少认知负担
    • 过渡策略:先掌握React基础,再逐步引入状态管理
    • 避免:直接上手复杂的Redux或Recoil

有Redux经验团队

  • 推荐:继续使用Redux Toolkit
    • 理由:团队已有经验,迁移成本低,RTK提供更现代的开发体验
    • 现代化策略:从传统Redux迁移到RTK,利用新API简化代码
    • 考虑:在新模块尝试Zustand等轻量方案,对比开发体验

技术前沿团队

  • 推荐:尝试Jotai或Recoil等新兴方案
    • 理由:探索更现代的状态管理范式,提升开发体验
    • 策略:核心模块使用成熟方案,新功能尝试新技术
    • 平衡:创新与稳定性,避免过度追求新技术

开发体验与工具链集成

状态管理的选择还应考虑与现有工具链的集成体验:

TypeScript支持

  • Redux Toolkit、Zustand和Jotai对TypeScript支持良好
  • Recoil的类型推导在复杂场景可能需要额外类型标注

开发者工具

  • Redux DevTools支持最完善,提供时间旅行调试、action记录等
  • Zustand和Jotai可以集成Redux DevTools,但功能相对有限
  • Recoil有专门的开发者工具,但功能不如Redux DevTools成熟

热重载支持

  • 所有方案都支持React的热重载
  • Redux需要额外配置保持热重载时的状态
  • Zustand和Jotai通常无需额外配置

案例分析:电商平台状态管理

以典型电商平台为例,不同模块可能需要不同的状态管理方案,体现了实际项目中的混合策略:

用户认证模块

  • 选择:Redux Toolkit
  • 原因
    • 需要在整个应用中共享用户状态
    • 有复杂的中间件处理(JWT刷新、权限检查)
    • 状态变化需要追踪审计
    • 与路由守卫等功能集成
// Redux实现用户认证
const authSlice = createSlice({name: 'auth',initialState: {user: null,token: localStorage.getItem('token'),refreshToken: localStorage.getItem('refreshToken'),isLoading: false,error: null,permissions: []},reducers: {loginStart: state => {state.isLoading = true;},loginSuccess: (state, action) => {state.isLoading = false;state.user = action.payload.user;state.token = action.payload.token;state.refreshToken = action.payload.refreshToken;state.permissions = action.payload.permissions || [];state.error = null;// 保存到localStoragelocalStorage.setItem('token', action.payload.token);localStorage.setItem('refreshToken', action.payload.refreshToken);},loginFailure: (state, action) => {state.isLoading = false;state.error = action.payload;},logout: state => {state.user = null;state.token = null;state.refreshToken = null;state.permissions = [];// 清除localStoragelocalStorage.removeItem('token');localStorage.removeItem('refreshToken');},// 更新token(用于刷新token流程)updateToken: (state, action) => {state.token = action.payload;localStorage.setItem('token', action.payload);}}
});// Token刷新中间件
const refreshTokenMiddleware = store => next => async action => {const result = next(action);// 当接收到401错误时,尝试刷新tokenif (action.type === 'api/requestFailed' && action.payload?.status === 401 &&store.getState().auth.refreshToken) {try {const response = await fetch('/api/refresh-token', {method: 'POST',headers: { 'Content-Type': 'application/json' },body: JSON.stringify({ refreshToken: store.getState().auth.refreshToken })});if (response.ok) {const data = await response.json();// 更新tokenstore.dispatch(updateToken(data.token));// 重试失败的请求store.dispatch(action.meta.originalRequest);} else {// 刷新失败,登出用户store.dispatch(logout());}} catch (error) {store.dispatch(logout());}}return result;
};// 在configureStore中添加中间件
const store = configureStore({reducer: {auth: authSlice.reducer,// 其他reducer...},middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(refreshTokenMiddleware)
});

购物车模块

  • 选择:Zustand + 持久化
  • 原因
    • 需要频繁更新且有持久化需求
    • 逻辑相对独立,与其他状态耦合较少
    • 需要高性能更新,避免整个应用重渲染
    • 用户体验要求购物车在刷新页面后保持状态
// Zustand实现购物车
import create from 'zustand';
import { persist } from 'zustand/middleware';const useCartStore = create(persist((set, get) => ({items: [],totalItems: 0,totalPrice: 0,// 添加商品到购物车addItem: (product) => set(state => {const existingItem = state.items.find(item => item.id === product.id);if (existingItem) {const updatedItems = state.items.map(item => item.id === product.id ? {...item, quantity: item.quantity + 1} : item);return {items: updatedItems,totalItems: state.totalItems + 1,totalPrice: state.totalPrice + product.price};}return {items: [...state.items, {...product, quantity: 1}],totalItems: state.totalItems + 1,totalPrice: state.totalPrice + product.price};}),// 从购物车移除商品removeItem: (productId) => set(state => {const itemToRemove = state.items.find(item => item.id === productId);if (!itemToRemove) return state;return {items: state.items.filter(item => item.id !== productId),totalItems: state.totalItems - itemToRemove.quantity,totalPrice: state.totalPrice - (itemToRemove.price * itemToRemove.quantity)};}),// 更新商品数量updateQuantity: (productId, quantity) => set(state => {const item = state.items.find(item => item.id === productId);if (!item) return state;const quantityDiff = quantity - item.quantity;const priceDiff = item.price * quantityDiff;return {items: state.items.map(item =>item.id === productId? { ...item, quantity }: item),totalItems: state.totalItems + quantityDiff,totalPrice: state.totalPrice + priceDiff};}),// 清空购物车clearCart: () => set({items: [],totalItems: 0,totalPrice: 0}),// 应用优惠券applyDiscount: (discountPercent) => set(state => ({totalPrice: state.totalPrice * (1 - discountPercent / 100)}))}),{name: 'cart-storage', // localStorage的键名getStorage: () => localStorage, // 使用localStoragepartialize: state => ({ // 只持久化这些字段,忽略计算字段items: state.items,totalItems: state.totalItems,totalPrice: state.totalPrice})})
);// 使用购物车状态
function CartSummary() {const { totalItems, totalPrice } = useCartStore();return (<div className="cart-summary"><span>购物车: {totalItems} 件商品</span><span>总价: ¥{totalPrice.toFixed(2)}</span></div>);
}function CartDetails() {const { items, removeItem, updateQuantity, clearCart } = useCartStore();return (<div className="cart-details"><h2>购物车</h2>{items.length === 0 ? (<p>购物车为空</p>) : (<><ul>{items.map(item => (<li key={item.id}><img src={item.image} alt={item.name} /><div><h3>{item.name}</h3><p>¥{item.price.toFixed(2)} × {item.quantity}</p><div className="quantity-controls"><button onClick={() => updateQuantity(item.id, Math.max(1, item.quantity - 1))}>-</button><span>{item.quantity}</span><button onClick={() => updateQuantity(item.id, item.quantity + 1)}>+</button></div></div><button onClick={() => removeItem(item.id)}>删除</button></li>))}</ul><div className="cart-actions"><button onClick={clearCart}>清空购物车</button><button>结算</button></div></>)}</div>);
}

产品详情页

  • 选择:React Query + 局部useState
  • 原因
    • 主要是服务器状态(产品数据)
    • 局部UI状态相对简单(选中的变体、数量等)
    • 需要缓存和预取数据,减少重复请求
    • 自动处理加载和错误状态

中型应用(组件数20-100)

  • 推荐:Zustand或Jotai

    • 理由:平衡了简洁性和功能性,学习成本适中
    • 实施方案:按功能域划分状态切片,避免单一巨大store
  • 替代:Redux Toolkit(预计未来会扩展)

    • 理由:为未来应用增长提供扩展性,规范化的状态管理
    • 实施方案:使用RTK,按照领域模型组织状态,避免过度设计
// 1. 按功能划分状态切片
const createAuthSlice = (set, get) => ({user: null,loading: false,error: null,login: async (credentials) => {set({ loading: true, error: null });try {const response = await fetch('/api/login', {method: 'POST',body: JSON.stringify(credentials),headers: { 'Content-Type': 'application/json' }});if (!response.ok) throw new Error('Login failed');const user = await response.json();set({ user, loading: false });return user;} catch (error) {set({ error: error.message, loading: false });throw error;}},logout: () => set({ user: null })
});const createProductsSlice = (set, get) => ({products: [],loading: false,error: null,fetchProducts: async (category) => {set({ loading: true, error: null });try {const response = await fetch(`/api/products?category=${category}`);if (!response.ok) throw new Error('Failed to fetch products');const products = await response.json();set({ products, loading: false });} catch (error) {set({ error: error.message, loading: false });}}
});// 2. 组合所有切片
import create from 'zustand';
import { persist } from 'zustand/middleware';const useStore = create(persist((set, get) => ({...createAuthSlice(set, get),...createProductsSlice(set, get)}),{ name: 'app-store' } // 持久化到localStorage)
);

大型应用(组件数>100)

  • 推荐:Redux Toolkit

    • 理由:成熟的生态系统,严格的状态更新规则,利于团队协作
    • 实施方案:按领域模型设计状态结构,规范化状态管理流程
  • 替代:Recoil(特别是对原子化状态有需求时)

    • 理由:细粒度状态控制,适合复杂UI和频繁局部更新
    • 实施方案:按功能设计原子族,利用选择器优化派生状态
// 大型应用使用Redux Toolkit的组织方式
// auth/authSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';export const loginUser = createAsyncThunk('auth/login',async (credentials, { rejectWithValue }) => {try {const response = await fetch('/api/login', {method: 'POST',body: JSON.stringify(credentials),headers: { 'Content-Type': 'application/json' }});if (!response.ok) throw new Error('Login failed');return await response.json();} catch (error) {return rejectWithValue(error.message);}}
);const authSlice = createSlice({name: 'auth',initialState: {user: null,status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'error: null},reducers: {logout: (state) => {state.user = null;state.status = 'idle';}},extraReducers: (builder) => {builder.addCase(loginUser.pending, (state) => {state.status = 'loading';}).addCase(loginUser.fulfilled, (state, action) => {state.status = 'succeeded';state.user = action.payload;state.error = null;}).addCase(loginUser.rejected, (state, action) => {state.status = 'failed';state.error = action.payload;});}
});

团队因素考量

技术选型不仅取决于技术本身,还与团队组成和技术背景密切相关:

React新手团队

  • 推荐:内置状态管理 → Zustand
    • 理由:学习曲线平缓,API直观,减少认知负担
    • 过渡策略:先掌握React基础,再逐步引入状态管理
    • 避免:直接上手复杂的Redux或Recoil

有Redux经验团队

  • 推荐:继续使用Redux Toolkit
    • 理由:团队已有经验,迁移成本低,RTK提供更现代的开发体验
    • 现代化策略:从传统Redux迁移到RTK,利用新API简化代码
    • 考虑:在新模块尝试Zustand等轻量方案,对比开发体验

技术前沿团队

  • 推荐:尝试Jotai或Recoil等新兴方案
    • 理由:探索更现代的状态管理范式,提升开发体验
    • 策略:核心模块使用成熟方案,新功能尝试新技术
    • 平衡:创新与稳定性,避免过度追求新技术

案例分析:电商平台状态管理

以典型电商平台为例,不同模块可能需要不同的状态管理方案,体现了实际项目中的混合策略:

用户认证模块

  • 选择:Redux Toolkit
  • 原因
    • 需要在整个应用中共享用户状态
    • 有复杂的中间件处理(JWT刷新、权限检查)
    • 状态变化需要追踪审计
    • 与路由守卫等功能集成
// Redux实现用户认证
const authSlice = createSlice({name: 'auth',initialState: {user: null,token: localStorage.getItem('token'),refreshToken: localStorage.getItem('refreshToken'),isLoading: false,error: null,permissions: []},reducers: {loginStart: state => {state.isLoading = true;},loginSuccess: (state, action) => {state.isLoading = false;state.user = action.payload.user;state.token = action.payload.token;state.refreshToken = action.payload.refreshToken;state.permissions = action.payload.permissions || [];state.error = null;// 保存到localStoragelocalStorage.setItem('token', action.payload.token);localStorage.setItem('refreshToken', action.payload.refreshToken);},loginFailure: (state, action) => {state.isLoading = false;state.error = action.payload;},logout: state => {state.user = null;state.token = null;state.refreshToken = null;state.permissions = [];// 清除localStoragelocalStorage.removeItem('token');localStorage.removeItem('refreshToken');}}
});

购物车模块

  • 选择:Zustand + 持久化
  • 原因
    • 需要频繁更新且有持久化需求
    • 逻辑相对独立,与其他状态耦合较少
    • 需要高性能更新,避免整个应用重渲染
    • 用户体验要求购物车在刷新页面后保持状态
// Zustand实现购物车
import create from 'zustand';
import { persist } from 'zustand/middleware';const useCartStore = create(persist((set, get) => ({items: [],totalItems: 0,totalPrice: 0,// 添加商品到购物车addItem: (product) => set(state => {const existingItem = state.items.find(item => item.id === product.id);if (existingItem) {const updatedItems = state.items.map(item => item.id === product.id ? {...item, quantity: item.quantity + 1} : item);return {items: updatedItems,totalItems: state.totalItems + 1,totalPrice: state.totalPrice + product.price};}return {items: [...state.items, {...product, quantity: 1}],totalItems: state.totalItems + 1,totalPrice: state.totalPrice + product.price};}),// 从购物车移除商品removeItem: (productId) => set(state => {const itemToRemove = state.items.find(item => item.id === productId);if (!itemToRemove) return state;return {items: state.items.filter(item => item.id !== productId),totalItems: state.totalItems - itemToRemove.quantity,totalPrice: state.totalPrice - (itemToRemove.price * itemToRemove.quantity)};})}),{name: 'cart-storage', // localStorage的键名getStorage: () => localStorage // 使用localStorage})
);

产品详情页

  • 选择:React Query + 局部useState
  • 原因
    • 主要是服务器状态(产品数据)
    • 局部UI状态相对简单(选中的变体、数量等)
    • 需要缓存和预取数据,减少重复请求
    • 自动处理加载和错误状态
import { useQuery } from 'react-query';
import { useState } from 'react';function ProductDetail({ productId }) {// 服务器状态管理 - React Queryconst { data: product, isLoading, error } = useQuery(['product', productId],() => fetchProductById(productId),{staleTime: 5 * 60 * 1000, // 5分钟内数据不过期cacheTime: 60 * 60 * 1000 // 缓存1小时});// 局部UI状态 - React useStateconst [selectedVariant, setSelectedVariant] = useState(null);const [quantity, setQuantity] = useState(1);// 购物车状态(来自Zustand)const addItem = useCartStore(state => state.addItem);useEffect(() => {// 产品加载完成后,默认选择第一个变体if (product?.variants?.length > 0) {setSelectedVariant(product.variants[0]);}}, [product]);if (isLoading) return <LoadingSpinner />;if (error) return <ErrorMessage error={error} />;if (!product) return <NotFoundMessage />;const handleAddToCart = () => {if (!selectedVariant) return;addItem({id: `${product.id}-${selectedVariant.id}`,productId: product.id,variantId: selectedVariant.id,name: product.name,variantName: selectedVariant.name,price: selectedVariant.price,image: product.images[0]});// 显示添加成功提示toast.success('已添加到购物车');};return (<div className="product-detail"><div className="product-images"><ProductImageGallery images={product.images} /></div><div className="product-info"><h1>{product.name}</h1><div className="product-price">{selectedVariant ? (<span>¥{selectedVariant.price.toFixed(2)}</span>) : (<span>¥{product.price.toFixed(2)}</span>)}</div>{product.variants.length > 0 && (<div className="variant-selector"><h3>选择规格</h3><div className="variant-options">{product.variants.map(variant => (<buttonkey={variant.id}className={selectedVariant?.id === variant.id ? 'selected' : ''}onClick={() => setSelectedVariant(variant)}>{variant.name}</button>))}</div></div>)}<div className="quantity-selector"><h3>数量</h3><div className="quantity-control"><button onClick={() => setQuantity(Math.max(1, quantity - 1))}disabled={quantity <= 1}>-</button><span>{quantity}</span><button onClick={() => setQuantity(quantity + 1)}>+</button></div></div><button className="add-to-cart-button"onClick={handleAddToCart}disabled={!selectedVariant}>加入购物车</button></div><div className="product-description"><h2>商品详情</h2><div dangerouslySetInnerHTML={{ __html: product.description }} /></div></div>);
}

商品列表和筛选

  • 选择:Jotai
  • 原因
    • 需要细粒度的状态更新(价格范围、分类筛选等)
    • 页面组件较多,需要避免不必要的重渲染
    • 筛选条件需要在多个组件间共享
    • URL参数与状态需要同步
import { atom, useAtom } from 'jotai';
import { useUpdateEffect } from 'react-use';
import { useNavigate, useLocation } from 'react-router-dom';// 定义原子状态
const categoryAtom = atom('all');
const priceRangeAtom = atom({ min: 0, max: 10000 });
const sortByAtom = atom('popularity'); // 'popularity', 'price-low', 'price-high', 'newest'
const pageAtom = atom(1);
const pageSizeAtom = atom(20);// 派生状态 - URL查询参数
const queryParamsAtom = atom(get => {const category = get(categoryAtom);const { min, max } = get(priceRangeAtom);const sortBy = get(sortByAtom);const page = get(pageAtom);const pageSize = get(pageSizeAtom);const params = new URLSearchParams();if (category !== 'all') params.set('category', category);if (min > 0) params.set('min_price', min.toString());if (max < 10000) params.set('max_price', max.toString());if (sortBy !== 'popularity') params.set('sort', sortBy);if (page > 1) params.set('page', page.toString());if (pageSize !== 20) params.set('page_size', pageSize.toString());return params.toString();}
);function ProductListPage() {// Jotai状态const [category, setCategory] = useAtom(categoryAtom);const [priceRange, setPriceRange] = useAtom(priceRangeAtom);const [sortBy, setSortBy] = useAtom(sortByAtom);const [page, setPage] = useAtom(pageAtom);const [queryParams] = useAtom(queryParamsAtom);// 路由和URL参数const navigate = useNavigate();const location = useLocation();// 从URL同步状态useEffect(() => {const params = new URLSearchParams(location.search);const categoryParam = params.get('category');if (categoryParam) setCategory(categoryParam);const minPrice = params.get('min_price');const maxPrice = params.get('max_price');if (minPrice || maxPrice) {setPriceRange({min: minPrice ? parseInt(minPrice) : 0,max: maxPrice ? parseInt(maxPrice) : 10000});}const sortParam = params.get('sort');if (sortParam) setSortBy(sortParam);const pageParam = params.get('page');if (pageParam) setPage(parseInt(pageParam));}, []);// 同步状态到URLuseUpdateEffect(() => {navigate({ search: queryParams }, { replace: true });}, [queryParams, navigate]);// 使用React Query获取产品数据const { data, isLoading, error } = useQuery(['products', queryParams],() => fetchProducts(queryParams),{ keepPreviousData: true });return (<div className="product-list-page"><div className="filters-sidebar"><CategoryFilterselectedCategory={category}onChange={setCategory}/><PriceRangeFiltervalue={priceRange}onChange={setPriceRange}/><SortOptionsvalue={sortBy}onChange={setSortBy}/></div><div className="product-grid">{isLoading ? (<LoadingSpinner />) : error ? (<ErrorMessage error={error} />) : (<>{data.products.map(product => (<ProductCard key={product.id} product={product} />))}<PaginationcurrentPage={page}totalPages={data.totalPages}onPageChange={setPage}/></>)}</div></div>);
}// 过滤器组件
function CategoryFilter({ selectedCategory, onChange }) {const { data: categories } = useQuery('categories',fetchCategories);if (!categories) return null;return (<div className="filter-section"><h3>商品分类</h3><ul><li><label><inputtype="radio"checked={selectedCategory === 'all'}onChange={() => onChange('all')}/>全部商品</label></li>{categories.map(category => (<li key={category.id}><label><inputtype="radio"checked={selectedCategory === category.id}onChange={() => onChange(category.id)}/>{category.name}</label></li>))}</ul></div>);
}

总结与决策

选择合适的状态管理方案没有放之四海而皆准的答案,需要根据项目规模、团队经验、性能需求和未来可维护性综合考量。基于前文的分析,我们可以提炼出以下核心决策原则:

  1. 渐进式采用:从简单方案开始,随需求复杂度增加逐步引入强大工具。对于新项目,优先考虑内置状态管理,当确实需要全局状态时再引入专门的库。

  2. 混合策略:针对不同类型状态选择最适合的管理方式:

    • 服务器状态:优先考虑React Query/SWR
    • 全局UI状态:Redux/Zustand/Recoil
    • 局部UI状态:useState/useReducer
    • 表单状态:专用表单库
  3. 团队一致性:避免在一个项目中使用过多不同的状态管理库,造成代码风格不一致和维护困难。选择符合团队技术栈和经验水平的方案。

  4. 关注开发体验:选择有良好开发者工具支持的方案,能显著提高调试效率和代码质量。Redux DevTools和React DevTools是不可或缺的工具。

  5. 权衡取舍:在代码复杂度、性能、学习成本之间找到平衡点,不要盲目追求最新技术。每个方案都有其优缺点,没有完美解决方案。

  6. 考虑长期维护:评估库的社区活跃度、更新频率和长期支持情况,避免选择可能被废弃的技术。

  7. 关注性能影响:状态管理方案会直接影响应用性能,尤其是组件重渲染,应进行充分测试评估。

最终,我们会发现,最好的状态管理方案是能够让团队高效工作、保持代码可维护性、提供良好用户体验的方案。技术选型应该服务于业务目标,而非成为目标本身。

参考资源

官方文档

  • React 官方文档 - 状态管理
  • Redux Toolkit 文档
  • Zustand GitHub
  • Recoil 官方文档
  • Jotai 官方文档
  • React Query 文档

深入学习资料

  • 状态管理方案对比分析
  • 性能优化最佳实践

工具与扩展

  • Redux DevTools Extension
  • React Developer Tools
  • Immer.js - 简化不可变状态更新
  • Reselect - 选择器库,优化状态派生

社区资源

  • React Status 周刊
  • Redux GitHub 讨论区
  • React Reddit 社区
  • React 状态管理主题演讲

如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇

终身学习,共同成长。

咱们下一期见

💻

相关文章:

  • Java程序员视角- NIO 到 Epoll:深度解析 IO 多路复用原理及 Select/Poll/Epoll 对
  • 【Qt】构建目录设置
  • GLIDE论文阅读笔记与DDPM(Diffusion model)的原理推导
  • 论文阅读:CLIP:Learning Transferable Visual Models From Natural Language Supervision
  • 【图像处理入门】4. 图像增强技术——对比度与亮度的魔法调节
  • MongoDB账号密码笔记
  • MongoDB-6.0.24 主从复制搭建和扩容缩容详解
  • pycharm如何查看git历史版本变更信息
  • 如何爬取google应用商店的应用分类呢?
  • 前端限流如何实现,如何防止服务器过载
  • 开源模型应用落地-OpenAI Agents SDK-集成Qwen3-8B(一)
  • 《前端面试题:CSS对浏览器兼容性》
  • 【Linux内核】设备模型之udev技术详解
  • 前端(vue)学习笔记(CLASS 7):vuex
  • Unity UI 性能优化终极指南 — Image篇
  • AI健康小屋+微高压氧舱:科技如何重构我们的健康防线?
  • 《前端面试题:CSS预处理器(Sass、Less等)》
  • 开源量子模拟引擎:Quantum ESPRESSO本地部署教程,第一性原理计算轻松入门!
  • LINUX63 硬链接、软链接;FTP默认配置
  • AI与区块链:数据确权与模型共享的未来
  • 微信小程序做链接网站/浏阳廖主任打人案
  • 政府门户网站建设情况汇报材料/论坛推广案例
  • 娄底网站建设的话术/推广网
  • 北京好的做网站公司/百度网盘登录入口
  • 做汽配批发做那个网站比较好/seo运营工作内容
  • 企业网站关于我们/深圳网站seo