在 React 中管理状态

98 阅读10分钟

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 架构来创建不可变的数据存储。

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

优点

  • 提供可预测的、一致的状态管理,因为状态总是通过一个称为 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 是一个新添加的功能,主要用于管理服务器端数据的状态,如获取、缓存、同步和更新。

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

优点

  • 简化并优化了数据获取过程,因为它抽象了获取、缓存和更新数据的逻辑,并为查询和更改数据提供了一致的声明式 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 端点 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 是一个库,它利用可观察对象和代理的强大功能来创建可直接写入或读取的反应式可变数据源。

它能在数据发生变化时自动跟踪和更新 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 中的这些状态管理选项各有优缺点,您在选择时可能需要考虑项目规模、复杂性、需求、偏好和学习曲线等因素。

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

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