【问题标题】:How to set the type of property based on the parameter passed in typescript?如何根据 typescript 中传递的参数设置属性的类型?
【发布时间】:2020-01-08 20:04:27
【问题描述】:

我正在创建一个反应应用程序并使用打字稿。我想要一个具有多个属性的 globalState。我还需要使用useReducer。我为reducer 函数的action 创建了一个接口。该接口接受一个参数N,它将是name。现在我想根据传递的N参数设置data的类型。

我几乎已经使用三元运算符创建了它。

export interface IGlobalState {
   loading: boolean;
   userData: IUserData;
}
interface IUserData {
   loggedIn: boolean;
   name: string;
}

type GlobalStateActionNames = "setLoading" | "setUserData"

export interface IGlobalStateAction<N extends GlobalStateActionNames = GlobalStateActionNames> {
   data: N extends "setLoading"
      ? boolean
      : N extends "setUserData"
      ? IUserData
      : any;
   name: N;
}

export const GlobalStateReducer = (
   state: IGlobalState,
   {name, data}: IGlobalStateAction
): IGlobalState => {

    switch(name){
        case "setLoading": return {...state, loading: data};
        default: return state;
    }
}; 

只有一个问题。 reducer 中的 data 参数将扩展所有可能是数据值的类型。因此,当我将 loading 设置为 data 时,它会出错。

类型 'boolean | IUserData' 不可分配给类型 'boolean'

我有两个问题。

  • 如何解决此错误?
  • 有没有更好的方法来实现上述功能?

注意:我知道我可以使用case "setLoading": return {...state, loading: data as boolean}; 解决这个问题。但是 globalState 会有很多其他的 props。所以我不想对所有的价值观都这样做。

【问题讨论】:

    标签: javascript typescript react-hooks


    【解决方案1】:

    抱歉,我是通过电话完成的,但你可以查看我的仓库,我有你要找的东西

    https://github.com/EnetoJara/-eneto-react-init

    
    export const DO_SOME = ”DO_SOME”;
    export type DO_SOME  = typeof DO_SOME;
    
    interface AppAction<T,P> {
    
       type: T;
       payload?: P;
    
    }
    
    export function actionCreator(someParam; ParamType): AppAction<DO_SOME, ParamType> {
    
      return {
         type: DO_SOME,
         payload: someParam
    }
    
    
    

    还记得你可以在他们的功能上做这样的事情

    
    function nameOfFunction<T, R> (param: T): Promise<R> {
    
       return httpCal(whatever).then((res: R) => red);
    

    T = 类型 R = 结果 遗传学就是他们的称呼

    然后只是使用它让我们想象一下

    const a = nameOfFunction<UserModel, HttpCode> (user).then((res: HttpCode)=>res);
    

    【讨论】:

      【解决方案2】:

      问题是你的IGlobalStateAction,要根据需要使用它,你需要在里面传递N generic,因为这是有条件工作的唯一方法。由于您无法确定将哪个操​​作传递给 reducer,因此无法在此级别传递 N。您可以通过类型保护此类型或将其更改为标准联合来解决此问题,它可能不太通用,但无需任何条件类型即可工作。

      使用联合类型而不是条件

      // pay attention, type is now simple union
      export type IGlobalStateAction = 
        | {
          name: "setLoading",
          data: boolean
        }
        | {
          name: 'setUserData',
          data: IUserData
        }
      
      export const GlobalStateReducer = (
         state: IGlobalState,
         action: IGlobalStateAction
      ): IGlobalState => {
      
          switch(action.name){
              case "setLoading": return {...state, loading: action.data}; // correctly infers bool
              default: return state;
          }
      }; 
      
      

      使用带有类型保护的条件类型

      第二种解决方案是,如果您想保留条件类型 - 具有类型保护。考虑:

      // here your original type
      
      // type guard narrowing the type
      const isActionWithName = <N extends GlobalStateActionNames>
      (a: IGlobalStateAction, n: N): a is IGlobalStateAction<N> => a.name === n;
      
      export const GlobalStateReducer = (
         state: IGlobalState,
         action: IGlobalStateAction
      ): IGlobalState => {
        // using type guard
        if (isActionWithName(action, 'setLoading')) {
          return {...state, loading: action.data};    
        }
        return state;
      }; 
      
      

      这里发生了什么 - isActionWithName 函数检查名称并将正确的泛型放入 IGlobalStateAction (可以在这里发现 - a is IGlobalStateAction&lt;N&gt;)。在检查时,我们可以确定它到底是哪个名称,以及它如何影响data 类型。

      【讨论】:

        猜你喜欢
        • 2016-03-08
        • 2021-04-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-07-28
        • 1970-01-01
        • 2021-07-08
        • 2021-07-09
        相关资源
        最近更新 更多