reducer 中的类型问题,使用 useReducer 的 typescript

Posted

技术标签:

【中文标题】reducer 中的类型问题,使用 useReducer 的 typescript【英文标题】:Type problems in reducer , typescript with useReducer 【发布时间】:2021-11-13 19:23:26 【问题描述】:

我在使用 TypeScript 时遇到类型问题,在使用 React 钩子 useReducer 时,控制台会引发以下错误:

函数缺少结束返回语句并且返回类型不包括“未定义”。 TS2366

5 | const Reducer = (state: InitialStateType, action: Action): InitialStateType =>

在执行操作之前,我尝试将类型分配给提交句柄返回的值。问题似乎与我的服务或减速器无关。我真的希望你能指导我......

reducerSensor.ts

import  InitialStateType  from "../interfaces/interfaces"
import  Action  from "../types/types";

const Reducer = (state: InitialStateType, action: Action) : InitialStateType => 
    switch (action.type) 
        case 'REQUEST_GET': 
            return 
                ...state,
                loading: true,
            ;
        
        case 'SUCCESS_GET': 
            return 
                ...state,
                loading: false,
                sensors: state.sensors.concat(action.payload)
            ;
        
        case 'ERROR_GET': 
            return 
                ...state,
                loading: false,
                error: action.error,
            ;
         
        case 'REQUEST_CREATE' : 
            return 
                ...state,
                loading: true,
            ;
        
        case 'SUCCESS_CREATE' : 
            return 
                ...state,
                loading: false,
                sensors: [...state.sensors, action.payload]
            ;
        
        case 'ERROR_CREATE': 
            return 
                ...state,
                loading: false,
                error: action.error,
            ;
        
        default:
            throw new Error('Error in the change of state');
    
;

export default Reducer;

ListSensor.ts

import  FC ,useEffect , useReducer from 'react'
import '../../styles/ListSensors.css'
import FormSensors from '../FormSensors/FormSensors'
import * as sensorServices from '../../services/sensorServices'
import Reducer from '../../Reducers/reducerSensor'
import  InitialStateType, Sensor  from '../../interfaces/interfaces'

const ListSensors: FC<htmlDataListElement>= () => 

const initState: InitialStateType = 
    sensors: [],
    loading: true,
    error: false


const [state, dispatch] = useReducer(Reducer, initState )

const  sensors, loading, error  = state;

useEffect(() => 
    dispatch( type: 'REQUEST_GET' );
    const loadSensors = async () => 
        const res = await sensorServices.getSensors();
        if (res.status === 200) 
            dispatch( type: 'SUCCESS_GET', payload: res.data );
            return;
        
        dispatch( type: 'ERROR_GET', error: res.data);
    ;
    loadSensors();
, []);

const handleCreate = (objectSensor : Sensor)  => 
    dispatch( type: 'REQUEST_CREATE' );
        const createSensor = async () => 
            const res = await sensorServices.createNewSensor(objectSensor);
            if (res.status === 200) 
                dispatch( type: 'SUCCESS_CREATE', payload: res.data );
                return;
            
    dispatch( type: 'ERROR_CREATE', error: res.data);
        ;
   createSensor();


    return (
        <div>
             loading ? (
                <p>loading...</p>
            ) : error ? (
                <p>error</p>
            ) : (
            <div className='datatable-container'>
                <table className="datatable">
                    <thead>
                        <tr>
                            <th>Status</th>
                            <th>Name</th>
                            <th>Ubication (lat / lng)</th>
                            <th>MinVal</th>
                            <th>MaxVal</th>
                            <th>Events</th>
                            <th>Options</th>         
                        </tr>
                    </thead>
                    <tbody>
                        <FormSensors  event=handleCreate/>     
                        
                            sensors && sensors.length > 0 ? sensors.map((sensor: Sensor) => (
                                <tr key=sensor._id>
                                    <td><span className=sensor.active ? "active" : "inactive"></span></td>
                                    <td>sensor.name</td>
                                    <td>sensor.ubication[0]° lat , sensor.ubication[1]° lng</td>
                                    <td>sensor.minVal°</td>
                                    <td>sensor.maxVal°</td>
                                    <td><button  className="button button-event"><i className="material-icons">sensors</i></button></td>
                                    <td> <button className="button button-delete"><i className="material-icons">delete</i></button> <button className="button button-update"><i className="material-icons">edit</i></button></td>   
                                </tr>
                            )
                          )  : null
                        
                    </tbody>
                </table>
            </div>
            )
    </div>
    )


export default ListSensors

SensorServices.ts

import axios from "axios";
import  Sensor  from "../interfaces/interfaces";

const API = "http://localhost:3001";

export const getSensors = async () => 
  return await axios.get(`$API/sensors`);
;

export const createNewSensor = async (sensor: Sensor) => 
    return await axios.post(`$API/sensor`, sensor);
  ;
  
  export const deleteSensorById = async (id: string) => 
    return await axios.delete(`$API/sensors/$id`);
  ;
  
  export const updateSensor = async (id: string, sensor: Sensor) => 
    return await axios.put(`$API/sensors/$id`, sensor);
  ;

types.ts

import  ChangeEvent  from 'react';
import  Sensor  from '../interfaces/interfaces';

export type Action =
     type: 'REQUEST_GET'
|    type: 'SUCCESS_GET', payload: Sensor 
|    type: 'ERROR_GET' , error: string 
|    type: 'ERROR_CREATE' , error: string 
|    type: 'REQUEST_CREATE' 
|    type: 'SUCCESS_CREATE', payload: Sensor 

export type InputChange = ChangeEvent<HTMLInputElement | HTMLTextAreaElement>;

export type SelectChange = ChangeEvent<HTMLSelectElement >

interfaces.ts

export interface Sensor 
    _id?: string;
    name: string;
    ubication: number[]
    maxVal: number;
    minVal: number;
    active: boolean;
    createdAt?: string | Date;
    updatedAt?: string | Date;
  

  export interface  SensorForm 
    name: string;
    lat: string;
    lng: string;
    active: boolean;
  
  export interface InitialStateType  
    sensors: Sensor[],
    loading: Boolean,
    error: Boolean | String 
  

【问题讨论】:

尝试在 ts 操场上分享最小可重现示例。会更容易回答 【参考方案1】:

作为最佳实践,尝试添加default case,它返回未触及状态(不一定需要抛出错误)。

import  InitialStateType  from "../interfaces/interfaces"
import  Action  from "../types/types";

const Reducer = (state: InitialStateType, action: Action) : InitialStateType => 
    switch (action.type) 
        case 'REQUEST_GET': 
            return 
                ...state,
                loading: true,
            ;
        
        case 'SUCCESS_GET': 
            return 
                ...state,
                loading: false,
                sensors: state.sensors.concat(action.payload)
            ;
        
        case 'ERROR_GET': 
            return 
                ...state,
                loading: false,
                error: action.error,
            ;
         
        case 'REQUEST_CREATE' : 
            return 
                ...state,
                loading: true,
            ;
        
        case 'SUCCESS_CREATE' : 
            return 
                ...state,
                loading: false,
                sensors: [...state.sensors, action.payload]
            ;
        
        case 'ERROR_CREATE': 
            return 
                ...state,
                loading: false,
                error: action.error,
            ;
        
        default:
            return state;
    
;

export default Reducer;

【讨论】:

【参考方案2】:

在:

const Reducer = (state: InitialStateType, action: Action) : InitialStateType => 
    switch (action.type) 
        case 'REQUEST_GET': 
            return  ...state, loading: true, ;
        
        case 'SUCCESS_GET': 
            return 
                ...state,
                loading: false,
                sensors: state.sensors.concat(action.payload)
            ;
        
        case 'ERROR_GET': 
            return ...state, loading: false, error: action.error, ;
         
        case 'REQUEST_CREATE' : 
            return  ...state, loading: true, ;
        
        case 'SUCCESS_CREATE' : 
            return  ...state, loading: false, sensors: [...state.sensors, action.payload] ;
        
        case 'ERROR_CREATE': 
            return 
                ...state,
                loading: false,
                error: action.error,
            ;
        
        // ADD A DEFAULT CLAUSE HERE <---------------
    
;

您的switch 应该有一个default 子句。否则 TypeScript 推断它可能会返回 undefined,因为如果 action.type 与任何 case 都不匹配,就会发生这种情况。

如果您没有该场景的default 值,您可以简单地使用throw new Error()

        case 'ERROR_CREATE': 
            return 
                ...state,
                loading: false,
                error: action.error,
            ;
        
        default:
            throw new Error('some meaningful message (optional)');
    
;

编辑:

类型以这种方式工作得很好。见typescript playground demo。

【讨论】:

不工作 :( ,同样的错误 你到底做了什么?您可以发布Reducer 的更新代码吗? 是的!外观已更新 尝试重新启动您的 tsc 或 IDE。有些东西必须被缓存/不编译,因为打字工作,检查我刚刚编辑的答案底部的链接

以上是关于reducer 中的类型问题,使用 useReducer 的 typescript的主要内容,如果未能解决你的问题,请参考以下文章

lambda 表达式中的返回类型,Reduce 函数

使用 Redux 如何阻止 reducer 中的 switch 增长?

Redux - reducer 与动作的关系

使用 reduce 操作对象数组,缺少正确的类型

Python - 尝试使用numpy.mean时“无法使用灵活类型执行reduce”

Swift - 使用 map/reduce/flatmap 将数组字典减少为相同类型的单个数组