查看原文
其他

【第3212期】在 React 中管理状态的 4 种方法

飘飘 前端早读课 2024-03-14

前言

本文介绍了在 React 中管理状态的四种方法:Context API、Redux、React Query 和 MobX。对这四种方法进行了比较,评估了它们在简易性、性能、可扩展性和兼容性方面的优缺点。同时提供了每种方法的简单实现示例,帮助读者了解如何在项目中应用这些状态管理技术。今日前端早读课文章由 @飘飘翻译分享。

正文从这开始~~

探索 2024 年的 Context API、Redux、React Query 和 MobX。

如果您正在使用 React,那么您肯定使用过状态。事实上,我敢保证,如果你打开一个 React 组件,至少会看到一个 useState 的实现。

管理组件的动态部分需要状态。但有时,你的状态并不只存在于一个组件中,而是存在于一棵组件树中。

例如,如果您的主题是浅色或深色,您就需要向所有组件传达当前的主题,以便相应地更新颜色主题。

一种方法是将主题作为 prop 传递给所有组件,但这可能会导致 prop drilling。例如,有些组件可能不会使用您的主题。因此,在这种情况下,您需要采用直观的方法来管理状态,使代码更简洁、可扩展。

因此,本文将比较 React 中四种流行的状态管理方法:Context API、Redux、React Query 和 MobX,并将根据以下标准进行评估:

  • 简单性:设置和使用该选项有多容易?

  • 性能:选项对数据渲染和获取的优化程度如何?

  • 可扩展性:该选项处理复杂和大规模应用的能力如何?

  • 兼容性:该选项与其他 React 功能或库的兼容性如何?

Context API

Context API 是 React 中的一种内置状态管理技术,可让您在组件树中创建和使用全局数据,而无需手动向下传递 prop。

它使用简单,不需要任何外部依赖,但可能不适合复杂的状态管理场景,因为它不提供任何性能优化、缓存或数据获取功能。

有了 React Context,你就拥有了两样东西:

  • 消费者:它可以让你访问一组选定的子组件的状态。

  • 提供者(Provider):它可让您创建和管理上下文,并保存在组件树中传递的状态。

优点

  • 原生于 React,不会给应用程序增加任何额外的复杂性或捆绑包大小。

  • 易于设置和使用,因为您只需创建一个 context 对象、一个提供者组件和一个消费者组件或钩子。

  • 可与其他状态管理技术(如 useState、useReducer 或自定义钩子)相结合,以处理不同类型的数据。

缺点

  • 可能不适合复杂的状态管理场景,因为它不提供任何性能优化、缓存或数据获取功能。你可能需要额外的钩子或自定义逻辑来处理这些方面。

  • 可能难以调试或测试,因为开发工具或组件树无法轻松访问上下文数据。

  • 但是,如果您有兴趣采用 context API,这里有一个简单的实现:

// ThemeContext.tsx
import React, { createContext, useContext, useState, ReactNode } from react;

// Define the type for our context state
type Theme = 'light' | 'dark';

// Define the type for our context, including the theme and setTheme function
interface ThemeContextType {
theme: Theme;
setTheme: (theme: Theme) => void;
}

// Create the context with a default value
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);

// Define the type for the provider's props, including children
interface ThemeProviderProps {
children: ReactNode;
}

// Create the ThemeProvider component
export const ThemeProvider: React.FC<ThemeProviderProps> = ({ children }) => {
const [theme, setTheme] = useState<Theme>('light'); // Default theme is light

// Value to be passed to the provider
const value = { theme, setTheme };

return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
};

// Custom hook to use the theme context
export const useTheme = (): ThemeContextType => {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return context;
};

如果你看一下实现过程,首先会用主题定义一个 "主题 context"。然后创建一个提供程序,接受一组 Children。

注:这些 Children 将能够访问从主题上下文传递的状态,因为它已封装在提供程序中。

最后,你可以通过封装提供程序在应用程序中使用主题。在本例中,我为 "App" 组件封装了主题,这意味着渲染的任何 React 组件都可以使用该主题。

// App.tsx or index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import { ThemeProvider } from './ThemeContext'; // Adjust the path as necessary
import App from './App'; // Your main App component

ReactDOM.render(
<ThemeProvider>
<App />
</ThemeProvider>,
document.getElementById('root')
);

Redux

Redux 是一个行业标准的状态管理库,它利用 flux 架构来创建不可变的数据存储。

【第3139期】Reac状态管理比较与原理实现. Redux,Zustand,Jotai,Recoil, MobX,Valtio

它采用的技术是将数据存储在一个单一的存储中,而应用程序将依赖该存储来获取数据(作为单一真实源)。它还拥有强大的开发工具支持、时间旅行调试以及丰富的中间件和实用程序生态系统,但同时也伴随着大量模板代码、陡峭的学习曲线和冗长的语法。

优点

  • 提供可预测的、一致的状态管理,因为状态总是通过一个称为 reducer 的纯函数,从上一个状态和派发的操作中派生出来的。

  • 便于调试和测试,因为状态和动作都是可序列化的,可以使用 devtools 或 Redux 工具包进行检查和操作。

  • 由于状态是集中化和模块化的,逻辑与用户界面组件是分离的,因此便于扩展和维护。

缺点

  • 存在大量模板代码、陡峭的学习曲线和冗长的语法。你可能需要使用 Redux Toolkit、Saga、Thunk、Reselect 或 Immer 等其他库来简化和增强 Redux 体验。

  • 可能会带来性能问题,因为状态存储在单个对象中,随着时间的推移可能会变得越来越大、越来越复杂,如果优化不当,可能会导致组件不必要地重新渲染。

  • 可能不是简单或本地状态管理的最佳选择,因为它会给你的应用增加额外的复杂性和开销,而且可能无法利用某些 React 功能或最佳实践,例如钩子、功能组件或不变性。

如果您热衷于在项目中使用 React Redux,那么您需要具备以下四点:

  • 动作:JavaScript 对象,代表改变应用程序状态的意图,也是将数据导入存储的唯一方法。

  • 调度器:Redux 存储中可用的函数,用于调度动作。当动作被分派后,Redux 会将该动作传递给还原器以计算新状态。

  • Reducers:将当前状态和动作作为参数并返回应用程序下一状态的纯函数。

  • Store:状态是所有应用数据的唯一来源。

如果你热衷于实现 Redux,这里有一个简单的实现方法:

// ThemeToggleRedux.tsx
import React from 'react';
import { createStore } from 'redux';
import { Provider, useSelector, useDispatch, TypedUseSelectorHook } from 'react-redux';

// Define action types
const SET_THEME = 'SET_THEME';

// Define action creators
const setTheme = (theme: 'light' | 'dark') => ({
type: SET_THEME,
payload: theme,
});

// Define the initial state type
interface ThemeState {
theme: 'light' | 'dark';
}

// Initial state
const initialState: ThemeState = {
theme: 'light',
};

// Reducer
const themeReducer = (state = initialState, action: { type: string; payload: 'light' | 'dark' }) => {
switch (action.type) {
case SET_THEME:
return { ...state, theme: action.payload };
default:
return state;
}
};

// Create store
const store = createStore(themeReducer);

// Typed useSelector hook
const useTypedSelector: TypedUseSelectorHook<ThemeState> = useSelector;

// ThemeToggleComponent
const ThemeToggleComponent: React.FC = () => {
const theme = useTypedSelector((state) => state.theme);
const dispatch = useDispatch();

return (
<div>
<p>Current theme is {theme}.</p>
<button onClick={() => dispatch(setTheme(theme === 'light' ? 'dark' : 'light'))}>
Toggle Theme
</button>
</div>
);
};

// App Component with Redux Provider
const AppWithRedux: React.FC = () => (
<Provider store={store}>
<ThemeToggleComponent />
</Provider>
);

export default AppWithRedux;

React Query

React Query 是一个新添加的功能,主要用于管理服务器端数据的状态,如获取、缓存、同步和更新。

【第2710期】React Query的实用技巧

它提供了一组自定义钩子,可让您轻松查询和更改数据,同时处理加载、错误和陈旧状态。它还提供后台获取、分页、乐观更新和自动重新获取等功能,但不能处理本地或用户界面状态,因此您可能需要使用其他解决方案来处理这方面的问题。

优点

  • 简化并优化了数据获取过程,因为它抽象了获取、缓存和更新数据的逻辑,并为查询和更改数据提供了一致的声明式 API。

  • 改善用户体验和性能,因为它会自动缓存数据并在需要时重新抓取,避免不必要的请求,并向用户显示最新数据。

  • 能与 Redux 或 Context API 等其他状态管理库很好地集成,因为它只处理服务器端数据,而将本地或用户界面状态留给其他解决方案处理。

缺点

  • 无法处理本地或用户界面状态,因此你可能需要使用其他解决方案来处理这方面的问题。

  • 可能与某些旧版浏览器或环境不兼容,因为它依赖于某些现代 JavaScript 功能,如 async/await、fetch 或 AbortController。

如果您热衷于使用 React Query,这里有一个简单的实现:

首先,您需要设置 React Query。它不需要很多模板就可以开始使用。您需要做的就是用 QueryClientProvider 封装您的组件树,并创建一个 QueryClient 实例。

// App.tsx
import React from 'react';
import { QueryClient, QueryClientProvider } from 'react-query';
import ExampleComponent from './ExampleComponent'; // This will be our component using React Query

// Create a client
const queryClient = new QueryClient();

export default function App() {
return (
<QueryClientProvider client={queryClient}>
<ExampleComponent />
</QueryClientProvider>
);
}

接下来,让我们在组件中使用 React Query 从 API 获取数据。为此,我们将使用 useQuery 钩子。该钩子用于在 React 组件中获取、缓存和更新数据。

为了便于演示,假设我们有一个 API 端点 https://api.example.com/data,它返回一些我们想要显示的数据。

// ExampleComponent.tsx
import React from 'react';
import { useQuery } from 'react-query';

// Dummy function to fetch data from an API
const fetchData = async () => {
const response = await fetch('https://api.example.com/data');
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
};

export default function ExampleComponent() {
// Use the useQuery hook to fetch data
const { data, error, isLoading } = useQuery('dataKey', fetchData);

if (isLoading) return <div>Loading...</div>;
if (error instanceof Error) return <div>An error occurred: {error.message}</div>;

return (
<div>
<h1>Data</h1>
{/* Render your data here */}
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
}

这里发生了三件事:

  • QueryClient 和 QueryClientProvider:它们用于设置 React 查询环境。QueryClient 负责管理查询和突变,而 QueryClientProvider 则向任何嵌套组件提供 React 查询功能。

  • useQuery 钩子:该钩子用于在组件中获取数据。它要求每个查询都有一个唯一的 key(用于缓存和管理数据),并要求一个函数返回一个解析数据的 promise。

  • 获取和渲染数据 useQuery 钩子提供了多个状态变量,如 data、isLoading 和 error,可用于根据数据获取状态处理用户界面状态。

MobX

MobX 是一个库,它利用可观察对象和代理的强大功能来创建可直接写入或读取的反应式可变数据源。

【第1781期】MobX 简明教程

它能在数据发生变化时自动跟踪和更新 UI 组件,而无需任何显式操作或还原器。它还支持 TypeScript、自动类型推断和 devtools 集成,但可能与某些 React 功能或最佳实践不兼容,例如钩子、功能组件或不变性。

优点

  • 提供简单直观的状态管理,因为状态只是普通的 JavaScript 对象、数组或基元,可以直接更改,而用户界面组件只是函数或类,可以直接访问状态。

  • 使用细粒度的依赖关系跟踪系统和批量更新机制,只更新依赖于已更改数据的组件,因此可实现高性能和最低限度的重新渲染。

  • 便于快速开发和原型设计,因为它不需要任何模板代码、复杂的设置或严格的规则,允许你编写更少的代码,将更多精力放在逻辑和用户界面上。

缺点

  • 某些 React 功能或最佳实践(如钩子、功能组件或不变性)可能无法很好地与 MobX 配合使用。此外,由于 MobX 使用了一些高级 JavaScript 功能(如装饰器、代理或生成器),因此某些检查或测试工具在使用 MobX 时可能会出现问题。

  • 它可能会导致错误或意外行为,因为状态可以从任何地方发生变化,用户界面组件也可以随时更新。此外,MobX 还可能导致调试或测试困难,因为状态和用户界面不易区分或检查。

要使用 MobX,您需要遵循以下步骤:

  • 定义一个 Store:创建一个表示 Store 的类,包括可观察的属性和修改这些属性的操作。

  • 创建存储实例:实例化 store,以便将其提供给 React 组件树。

  • 在 React 组件中使用 Store:利用 MobX 观察器函数,让您的 React 组件 store 的变化做出反应。

下面是一个使用 MobX 实现主题的简单示例:

// ThemeToggleMobX.tsx
import React from 'react';
import { makeAutoObservable } from 'mobx';
import { observer } from 'mobx-react-lite';

// Step 1: Define the Store
class ThemeStore {
theme: 'light' | 'dark' = 'light'; // Initial theme

constructor() {
makeAutoObservable(this);
}

// Action to toggle the theme
toggleTheme = () => {
this.theme = this.theme === 'light' ? 'dark' : 'light';
};
}

// Step 2: Create an instance of the store
const themeStore = new ThemeStore();

// Step 3: Define a React component that uses the store
const ThemeToggleComponent: React.FC = observer(() => {
return (
<div>
<p>Current theme is {themeStore.theme}.</p>
<button onClick={themeStore.toggleTheme}>Toggle Theme</button>
</div>
);
});

// App Component
const AppWithMobX: React.FC = () => (
<div>
<ThemeToggleComponent />
</div>
);

export default AppWithMobX;

这里发生了三件事:

  • MobX Store(ThemeStore):该类包含主题可观察属性和一个 toggleTheme 操作 makeAutoObservable 调用会自动将所有属性标记为可观察属性,将动作标记为动作,从而使状态管理变得简单高效。

  • 观察者组件(ThemeToggleComponent):该组件由 mobx-react-lite 的观察者函数封装。当观察到的数据(本例中为主题属性)发生变化时,它会自动重新呈现。因此,当主题被切换时,该组件会更新以反映当前主题。

  • 使用方法 AppWithMobX 组件只需渲染 ThemeToggleComponent。由于 ThemeToggleComponent 会被观察到,因此它会对 MobX 商店中的变化做出反应。

结论

正如您所看到的,React 中的这些状态管理选项各有优缺点,您在选择时可能需要考虑项目规模、复杂性、需求、偏好和学习曲线等因素。

您可能还想尝试各种组合或替代方案,以找到最适合您的用例。

希望这篇文章对您有所帮助。

关于本文
译者:@飘飘
作者:@Binara Prabhanga
原文:https://blog.bitsrc.io/ways-to-manage-state-in-react-in-2024-6a22a5f5974e

这期前端早读课
对你有帮助,帮” 
 “一下,
期待下一期,帮”
 在看” 一下 。

继续滑动看下一个
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存