useReducer TypeScript React Context 与 useReducer 和 Typescript。

2025-05-24

useReducer TypeScript React Context 与 useReducer 和 Typescript。

只是代码吗?

React 应用程序中有很多处理状态的选项。显然,你可以用它来处理setState一些简单的逻辑,但如果你需要管理复杂的状态怎么办?

也许您会使用 Redux 或 MobX 来处理这种情况,但也可以选择使用 React Context,并且您不必安装其他依赖项。

让我们看看如何使用 Context API 和 Typescript 管理复杂状态。

在本教程中,我们将构建一个带有购物车计数器的产品列表。

首先,使用创建一个新的 React 项目create-react-app

npx create-react-app my-app --template typescript
cd my-app/
Enter fullscreen mode Exit fullscreen mode

接下来,在目录中创建一个新context.tsx文件src

/*context.tsx*/

import React, { createContext } from 'react';

const AppContext = createContext({});
Enter fullscreen mode Exit fullscreen mode

您可以使用任何您想要的值来简单地初始化上下文 API,在本例中,我使用一个空对象。

现在让我们创建一个初始状态,其中包含一个空的产品列表,购物车计数器为零。此外,我们还需要为此添加一些类型。

/*context.tsx*/

import React, { createContext } from 'react';

type ProductType = {
  id: number;
  name: string;
  price: number;
}

type InitialStateType = {
  products: ProductType[];
  shoppingCart: number;
}

const initialState = {
  products: [],
  shoppingCart: 0,
}

const AppContext = createContext<InitialStateType>(initialState);
Enter fullscreen mode Exit fullscreen mode

产品列表中的每个产品都有一个 ID、名称和价格。

现在我们将使用 Reducer 和 Action 来创建和删除产品,并将购物车计数器加一。首先,创建一个名为 的新文件reducers.ts

/*reducers.ts*/

export const productReducer = (state, action) => {
  switch (action.type) {
    case 'CREATE_PRODUCT':
      return [
        ...state,
        {
          id: action.payload.id,
          name: action.payload.name,
          price: action.payload.price,
        }
      ]
    case 'DELETE_PRODUCT':
      return [
        ...state.filter(product => product.id !== action.payload.id),
      ]
    default:
      return state;
  }
}

export const shoppingCartReducer = (state, action) => {
  switch (action.type) {
    case 'ADD_PRODUCT':
      return state + 1;
  }
}
Enter fullscreen mode Exit fullscreen mode

Reducer 函数接收两个参数,第一个参数是状态,是我们在使用useReducerhook 时传递的,第二个参数是表示事件和一些将改变状态(动作)的数据的对象。

在本例中,我们创建了两个 Reducer,一个用于产品,另一个用于购物车。在产品 Reducer 上,我们添加了两个 Action,一个用于创建新产品,另一个用于删除任何产品。对于购物车 Reducer,我们添加的唯一 Action 是每次添加新产品时增加计数器。

如你所见,创建产品时,我们传入 id、name 和价格,并返回当前状态和新对象。删除产品时,我们只需要 id,返回的是状态,但不包含包含该 id 的产品。

现在让我们更改上下文文件以导入这些 reducer 函数。

/*context.tsx*/

import React, { createContext, useReducer } from 'react';
import { productReducer, shoppingCartReducer } from './reducers';

type ProductType = {
  id: number;
  name: string;
  price: number;
}

type InitialStateType = {
  products: ProductType[];
  shoppingCart: number;
}

const intialState = {
  products: [],
  shoppingCart: 0,
}

const AppContext = createContext<{
  state: InitialStateType;
  dispatch: React.Dispatch<any>;
}>({
  state: initialState,
  dispatch: () => null
});

const mainReducer = ({ products, shoppingCart }, action) => ({
  products: productReducer(products, action),
  shoppingCart: shoppingCartReducer(shoppingCart, action),
});

const AppProvider: React.FC = ({ children }) => {
  const [state, dispatch] = useReducer(mainReducer, initialState);

  return (
    <AppContext.Provider value={{state, dispatch}}>
      {children}
    </AppContext.Provider>
  )
}

export { AppContext, AppProvider };
Enter fullscreen mode Exit fullscreen mode

有一个mainReducer函数,它结合了我们将要使用的两个减速器(产品减速器和购物车减速器),每个减速器管理状态的选定部分。

此外,我们创建AppProvider组件,并在其中,useReducer钩子采用此mainReducer和初始状态来返回statedispatch

我们将这些值传递到AppContext.Provider,这样我们就可以使用钩子访问statedispatchuseContext

接下来,为减速器和动作添加这些类型。

/*reducers.ts*/

type ActionMap<M extends { [index: string]: any }> = {
  [Key in keyof M]: M[Key] extends undefined
    ? {
        type: Key;
      }
    : {
        type: Key;
        payload: M[Key];
      }
};

export enum Types {
  Create = 'CREATE_PRODUCT',
  Delete = 'DELETE_PRODUCT',
  Add = 'ADD_PRODUCT',
}

// Product

type ProductType = {
  id: number;
  name: string;
  price: number;
}

type ProductPayload = {
  [Types.Create] : {
    id: number;
    name: string;
    price: number;
  };
  [Types.Delete]: {
    id: number;
  }
}

export type ProductActions = ActionMap<ProductPayload>[keyof ActionMap<ProductPayload>];

export const productReducer = (state: ProductType[], action: ProductActions | ShoppingCartActions) => {
  switch (action.type) {
    case Types.Create:
      return [
        ...state,
        {
          id: action.payload.id,
          name: action.payload.name,
          price: action.payload.price,
        }
      ]
    case Types.Delete:
      return [
        ...state.filter(product => product.id !== action.payload.id),
      ]
    default:
      return state;
  }
}

// ShoppingCart

type ShoppingCartPayload = {
  [Types.Add]: undefined;
}

export type ShoppingCartActions = ActionMap<ShoppingCartPayload>[keyof ActionMap<ShoppingCartPayload>];

export const shoppingCartReducer = (state: number, action: ProductActions | ShoppingCartActions) => {
  switch (action.type) {
    case Types.Add:
      return state + 1;
    default:
      return state;
  }
}
Enter fullscreen mode Exit fullscreen mode

我从这篇文章中采用了这种方法,基本上我们正在检查action.type使用了哪个,并据此生成有效载荷的类型。


笔记

Discriminated unions您可以采取的另一种方法是像这样使用。

type Action =
 | { type: 'ADD' }
 | { type: 'CREATE', create: object }
 | { type: 'DELETE', id: string };
Enter fullscreen mode Exit fullscreen mode

在前面的代码中,所有这些类型都有一个共同的属性,称为 type。TypeScript 会为可区分联合创建类型保护,并让我们根据所使用的类型来判断对象类型是否具有其他属性。

但在本教程中,我们对操作使用了两个常见属性typepayload,并且payload对象类型根据而变化type,因此可区分联合类型将不起作用。


现在,让我们将我们定义的类型导入到context文件中。

/*context.tsx*/

import React, { createContext, useReducer, Dispatch } from 'react';
import { productReducer, shoppingCartReducer, ProductActions, ShoppingCartActions } from './reducers';

type ProductType = {
  id: number;
  name: string;
  price: number;
}

type InitialStateType = {
  products: ProductType[];
  shoppingCart: number;
}

const initialState = {
  products: [],
  shoppingCart: 0,
}

const AppContext = createContext<{
  state: InitialStateType;
  dispatch: Dispatch<ProductActions | ShoppingCartActions>;
}>({
  state: initialState,
  dispatch: () => null
});

const mainReducer = ({ products, shoppingCart }: InitialStateType, action: ProductActions | ShoppingCartActions) => ({
  products: productReducer(products, action),
  shoppingCart: shoppingCartReducer(shoppingCart, action),
});


const AppProvider: React.FC = ({ children }) => {
  const [state, dispatch] = useReducer(mainReducer, initialState);

  return (
    <AppContext.Provider value={{state, dispatch}}>
      {children}
    </AppContext.Provider>
  )
}

export { AppProvider, AppContext };
Enter fullscreen mode Exit fullscreen mode

不要忘记用 包装你的主要组件AppProvider

/* App.tsx */

import React from 'react';
import { AppProvider } from './context';
import Products from './products';

const App = () => {
  <AppProvider>
    // your stuff
    <Products />
  </AppProvider>
}

export default App
Enter fullscreen mode Exit fullscreen mode

创建一个Products组件并在其中添加以下代码。

/* Products.tsx */

import React, { useContext } from 'react';
import { AppContext } from './context';
import { Types } from './reducers';

const Products = () => {
  const { state, dispatch } = useContex(AppContext);

  return (
    <div>
      <button onClick={() => {
        dispatch({
          type: Types.Add,
        })
      }}>
        click
        </button>
      {state.shoppingCart}
    </div>
  )
}

export default Products;
Enter fullscreen mode Exit fullscreen mode

现在一切都是强类型的。

您可以在此处检查代码

来源。

https://medium.com/hackernoon/finally-the-typescript-redux-hooks-events-blog-you-were-looking-for-c4663d823b01

文章来源:https://dev.to/elisealcala/react-context-with-usereducer-and-typescript-4obm
PREV
为没有设计背景的开发人员提供初学者提示。
NEXT
如何使用 4 种 Solid 方法在 CSS 中将项目居中对齐。