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/
接下来,在目录中创建一个新context.tsx
文件src
。
/*context.tsx*/
import React, { createContext } from 'react';
const AppContext = createContext({});
您可以使用任何您想要的值来简单地初始化上下文 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);
产品列表中的每个产品都有一个 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;
}
}
Reducer 函数接收两个参数,第一个参数是状态,是我们在使用useReducer
hook 时传递的,第二个参数是表示事件和一些将改变状态(动作)的数据的对象。
在本例中,我们创建了两个 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 };
有一个mainReducer
函数,它结合了我们将要使用的两个减速器(产品减速器和购物车减速器),每个减速器管理状态的选定部分。
此外,我们创建AppProvider
组件,并在其中,useReducer
钩子采用此mainReducer
和初始状态来返回state
和dispatch
。
我们将这些值传递到AppContext.Provider
,这样我们就可以使用钩子访问state
和。dispatch
useContext
接下来,为减速器和动作添加这些类型。
/*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;
}
}
我从这篇文章中采用了这种方法,基本上我们正在检查action.type
使用了哪个,并据此生成有效载荷的类型。
笔记
Discriminated unions
您可以采取的另一种方法是像这样使用。
type Action =
| { type: 'ADD' }
| { type: 'CREATE', create: object }
| { type: 'DELETE', id: string };
在前面的代码中,所有这些类型都有一个共同的属性,称为 type。TypeScript 会为可区分联合创建类型保护,并让我们根据所使用的类型来判断对象类型是否具有其他属性。
但在本教程中,我们对操作使用了两个常见属性type
和payload
,并且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 };
不要忘记用 包装你的主要组件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
创建一个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;
现在一切都是强类型的。
您可以在此处检查代码。