打字稿 - 确保泛型属性存在于具有描述性错误的泛型类型上

aar*_*ard 4 generics typescript

(打字稿新手警告)

我正在创建一个可重用的 reducer,它接受一个状态和一个动作并返回该状态,但仅限于接受在给定键处包含某种类型的状态。此键作为函数的参数传递。如果传递的状态对象不包含传递的键,则编译器应引发错误。

现在,我得到了这个工作。然而,在我看来,编译器生成的错误消息并不能充分描述问题。它没有说“类型上缺少 x 属性”,而是给出了其他错误,我将在下面详细说明。

类型

// FetchAction up here, not so relevant...

export type FetchState = {
  status: FetchAction,
  timestamp: Date
} | null

export type LoginState = {
  token: string | null,
  fetching: FetchState
};
Run Code Online (Sandbox Code Playgroud)

基础减速机

const intialState: LoginState = {
  token: null,
  fetching: null
}

const loginReducer: Reducer<LoginState> = (state = intialState, action) => {
  //...other operations 
  return fetchingReducer(state, action, 'fetching');
}
Run Code Online (Sandbox Code Playgroud)

方法一

type FetchContainingState<S, K extends keyof S> = {
  [F in keyof S]: F extends K ? FetchState : S[F];
};

export const fetchingReducer = <S extends FetchContainingState<S, K>, K extends keyof S>(state: S, action: Action, key: K): S => {
  // implementation
}
Run Code Online (Sandbox Code Playgroud)

这工作正常。如果我将函数调用更改为:( return fetchingReducer(state, action, 'fetchin');拼写错误fetching),则会出现此错误:

“LoginState”类型的参数不可分配给“FetchContainingState”类型的参数。属性“令牌”的类型不兼容。输入'字符串| null' 不可分配给类型 'FetchState'。类型 'string' 不可分配给类型 'FetchState'。

好吧,它给我一个错误很好。但是,它只是警告我关于"token",或对象上存在的任何其他属性。它没有给我任何关于它期望但不存在的属性的直接指示。

方法二

type EnsureFetchState<S, K extends keyof S> = S[K] extends FetchState ? S : never;

export const fetchingReducer = <S, K extends keyof S>(state: EnsureFetchState<S, K>, action: Action, key: K): S => {
   // implementation
}
Run Code Online (Sandbox Code Playgroud)

这也有效,当我将呼叫更改为return fetchingReducer(state, action, 'fetchin');(拼写错误"fetching")时,我得到:

“LoginState”类型的参数不可分配给“never”类型的参数。

更简洁,但对错误的描述更少。它对传递的参数可能有什么问题给出了更少的指示。

结论

在方法 1 中,我使用了映射类型,在方法 2 中,我使用了条件类型来确定我传递的state和的值key没有通过我们正在搜索的条件。但是,在这两种情况下,错误消息都没有真正描述真正的问题是什么。

我是 Typescript 中更高级类型的新手,所以可能有一种非常简单的方法可以做到这一点,或者我忽略了一个简单的概念。但愿如此!但无论如何,其要点是:如何以更惯用的方式对具有动态键的对象进行这种类型检查,或者以编译器生成更有益的错误消息的方式进行?

art*_*tem 6

在描述类型约束时,尽可能直接通常会有所帮助。S具有K以 type命名的属性的类型的约束FetchState不需要提及其他属性:

export const fetchingReducer = <S extends {[k in K]: FetchState}, K extends string>(state: S, action: Action, key: K): S => {
Run Code Online (Sandbox Code Playgroud)

这似乎产生了所需的错误消息,至少在这个示例代码中(我只是为缺少的类型编写了定义以使其完整):

export interface Action {
    a: string;
}
export interface FetchAction extends Action {
    f: string;
}
export type FetchState = {
  status: FetchAction,
  timestamp: Date
} | null
export type LoginState = {
  token: string | null,
  fetching: FetchState
};
const intialState: LoginState = {
  token: null,
  fetching: null
}
export type Reducer<S> = (s: S, a: Action) => S;

const loginReducer: Reducer<LoginState> = (state = intialState, action) => {

  fetchingReducer(state, action, 'fetching'); // Argument of type 'LoginState' 
        // is not assignable to parameter of type '{ fetching: FetchState; }'.
        // Property 'fetching' is missing in type 'LoginState'.


  fetchingReducer(state, action, 'token'); // Argument of type 'LoginState' is 
          // not assignable to parameter of type '{ token: FetchState; }'.
          // Types of property 'token' are incompatible.
          // Type 'string | null' is not assignable to type 'FetchState'.
          // Type 'string' is not assignable to type 'FetchState'.


  // OK
  return fetchingReducer(state, action, 'fetching')
}

export const fetchingReducer = <S extends {[k in K]: FetchState}, K extends string>(state: S, action: Action, key: K): S => {
  return {} as S;
}
Run Code Online (Sandbox Code Playgroud)