TypeScript中useReducer结合Context API报类型错误如何解决
问题背景
尝试使用 Context API 配合 useReducer,结合 faker.js 实现商品数据渲染逻辑时,出现两类 TypeScript 类型报错,相关原代码与报错信息如下:
原代码片段
CartReducer 部分代码
import { IProduct } from "../context/Context"; export interface IState { products: IProduct; cart: []; } type Actions = | {type:"ADD_TO_CART", payload:number}| {type:"REMOVE_FROM_CART", payload:number}| {type: "CHANGE_CART_QTY", payload: number } export const cartReducer = (state:IState, action:Actions) =>{ switch (action.type) { case "ADD_TO_CART": console.log('ADD_TO_CART') break; default: return state; } }
Context 部分代码
import React, {useContext, useReducer } from "react"; import { faker } from '@faker-js/faker'; import { cartReducer, IState } from "../reducers/CartReducer"; export interface IProduct { id: string; name: string; price: string; image: string; insTock: number; fastdelivery: boolean; ratings: number; children: React.ReactNode; } const CartContext = React.createContext<IProduct | null>(null); const Context: React.FC <IProduct> = ({ children}) => { const products = [...Array(20)].map(() => ({ id: faker.datatype.uuid(), name: faker.commerce.productName(), price: faker.commerce.price(), image: faker.image.fashion(), inStock: faker.helpers.arrayElement([0, 3, 5, 6, 7]), fastDelivery: faker.datatype.boolean(), ratings: faker.helpers.arrayElement([1, 2, 3, 4, 5]), })); const initialState: IState = { products: products, cart: [] } const [state, dispatch] = useReducer(cartReducer, initialState); return ( <CartContext.Provider value={{state, dispatch}}> {children} </CartContext.Provider> ) } export default Context; export const CartState = () => { return useContext(CartContext) }
运行报错信息
- 类型校验错误:生成的products数组缺少IProduct类型要求的id、name、price、image等共计4项以上属性,无法匹配IProduct类型。
Type '{ id: string; name: string; price: string; image: string; inStock: number; fastDelivery: boolean; ratings: number; }[]' is missing the following properties from type 'IProduct': id, name, price, image, and 4 more.
- useReducer 重载匹配失败:第一类重载提示传入的cartReducer类型为
(state: IState, action: Actions) => IState | undefined,无法赋值给ReducerWithoutAction类型;第二类重载提示传入的initialState为IState类型,无法赋值给never类型。
问题原因
一共存在6处类型定义与逻辑错误:
- IProduct 接口定义错误:
- 字段名拼写不一致:接口写的是
insTock、fastdelivery,实际faker生成的字段是inStock、fastDelivery - 错误加入了
children字段:children是Context Provider组件的props属性,不属于商品数据结构
- 字段名拼写不一致:接口写的是
- IState 接口定义错误:
products字段类型应该是IProduct[](商品数组),原代码写的是单个IProduct类型,和生成的20条商品数组结构不匹配cart字段错误写为[]空元组类型,实际应该是购物车对应的数据数组类型
- cartReducer 逻辑缺失返回值:
ADD_TO_CART的case分支只打了日志没有return状态,TS推断该分支返回undefined,导致reducer返回类型不匹配 - Context 泛型定义错误:原代码createContext的泛型写的是
IProduct | null,但实际Provider传入的value是{state, dispatch}结构,类型完全不匹配 - Context 组件泛型错误:原代码给React.FC传入的泛型是
IProduct,但该组件的props只有children,不需要商品相关字段 - 循环依赖问题:CartReducer里从Context引入IProduct,Context里又从CartReducer引入IState,很容易引发类型解析异常
修正方案
1. 统一类型定义,避免循环依赖
调整接口字段,保证类型和实际数据结构一致:
import React, { Dispatch } from "react"; // 商品类型,字段名和faker生成逻辑保持一致,移除不属于商品的children字段 export interface IProduct { id: string; name: string; price: string; image: string; inStock: number; fastDelivery: boolean; ratings: number; qty?: number; } // 全局状态类型 export interface IState { products: IProduct[]; cart: IProduct[]; } // Action类型 export type Actions = | {type:"ADD_TO_CART", payload: IProduct} | {type:"REMOVE_FROM_CART", payload: string} | {type: "CHANGE_CART_QTY", payload: {id: string, qty: number} } // Context传入值的类型 export interface ICartContext { state: IState; dispatch: Dispatch<Actions> }
2. 修正CartReducer代码
补全所有分支的返回值,保证每个case都返回合法的state:
import { IState, Actions } from "./types"; export const cartReducer = (state:IState, action:Actions): IState =>{ switch (action.type) { case "ADD_TO_CART": return { ...state, cart: [...state.cart, {...action.payload, qty: 1}] }; case "REMOVE_FROM_CART": return { ...state, cart: state.cart.filter(item => item.id !== action.payload) }; case "CHANGE_CART_QTY": return { ...state, cart: state.cart.map(item => item.id === action.payload.id ? {...item, qty: action.payload.qty} : item) }; default: return state; } }
3. 修正Context代码
调整Context泛型、组件props类型,匹配实际传入的value结构:
import React, {useContext, useReducer } from "react"; import { faker } from '@faker-js/faker'; import { cartReducer } from "../reducers/CartReducer"; import { IProduct, IState, ICartContext } from "./types"; const CartContext = React.createContext<ICartContext | null>(null); const Context: React.FC<{children: React.ReactNode}> = ({ children}) => { const products: IProduct[] = [...Array(20)].map(() => ({ id: faker.datatype.uuid(), name: faker.commerce.productName(), price: faker.commerce.price(), image: faker.image.fashion(), inStock: faker.helpers.arrayElement([0, 3, 5, 6, 7]), fastDelivery: faker.datatype.boolean(), ratings: faker.helpers.arrayElement([1, 2, 3, 4, 5]), })); const initialState: IState = { products: products, cart: [] } const [state, dispatch] = useReducer(cartReducer, initialState); return ( <CartContext.Provider value={{state, dispatch}}> {children} </CartContext.Provider> ) } export default Context; export const CartState = () => { const context = useContext(CartContext); if (!context) throw new Error("CartState must be used within CartProvider"); return context; }
内容的提问来源于stack exchange,提问作者The Dead Man
相关产品推荐
相关产品推荐

