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

Posted

技术标签:

【中文标题】Typescript - 确保泛型属性存在于具有描述性错误的泛型类型上【英文标题】:Typescript - Ensure Generic Property Exists On Generic Type With Descriptive Error 【发布时间】:2019-05-29 13:12:43 【问题描述】:

(打字稿新手警告)

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

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

类型

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

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

export type LoginState = 
  token: string | null,
  fetching: FetchState
;

基础缩减器

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


const loginReducer: Reducer<LoginState> = (state = intialState, action) => 
  //...other operations 
  return fetchingReducer(state, action, 'fetching');

方法一

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

这可以正常工作。如果我将函数调用更改为: return fetchingReducer(state, action, 'fetchin');(拼写错误fetching),然后我得到这个错误:

“LoginState”类型的参数不能分配给类型参数 'FetchContainingState'。种类 属性“令牌”不兼容。 键入'字符串 | null' 不可分配给类型 'FetchState'。 类型“字符串”不可分配给类型“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

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

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

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

结论

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

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

【问题讨论】:

【参考方案1】:

在描述类型约束时,通常尽可能直接。对于具有名为K 的属性的类型S 的约束,类型为FetchState 不需要提及其他属性:

export const fetchingReducer = <S extends [k in K]: FetchState, K extends string>(state: S, action: Action, key: K): S => 

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

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;

【讨论】:

谢谢,我知道一定有一种简单的方法。我试图在 Typescript 中查找 in 关键字的资源……但是这么小的词很难用谷歌搜索,呵呵,它出现在每一页上!我不知道它可以这样使用,所以我很高兴看到它可以这样使用。这几乎就是我想要的理想,非常感谢。【参考方案2】:

错误的可读性,就像旁观者眼中的美丽。

在我看来,我能得到的最漂亮的错误是添加第二个重载,其中KPropertyKey。错误是由条件类型触发的,但是在key参数上添加了条件类型。第二个重载的需要来自这样一个事实,如果K extends keyof Skey 有一个错误K 将被推断为keyof S 而不是实际值。

对于错误部分,我使用带有描述性消息的字符串文字类型。如果您有一个名为 "This porperty is not of type FetchState" 的密钥,您可能对此有疑问,但这似乎不太可能。

type EnsureFetchState<S, K extends PropertyKey> = S extends Record<K, FetchState> ?  : "This porperty is not of type FetchState";
export function fetchingReducer<S, K extends keyof S>(state: S, action: Action, key: K & EnsureFetchState<S, K>): S
export function fetchingReducer <S, K extends PropertyKey>(state: S, action: Action, key: K & EnsureFetchState<S, K>): S
export function fetchingReducer <S, K extends keyof S>(state: S, action: Action, key: K & EnsureFetchState<S, K>): S 
   // implementation


//Argument of type '"fetchin"' is not assignable to parameter of type '"fetchin" & "This porperty is not of type FetchState"'. 
return fetchingReducer(state, action, 'fetchin'); 

【讨论】:

非常好,我真的很喜欢这个解决方案的独创性。另一个答案确实给了我一个更接近我正在寻找的结果,但我仍在将这种技术添加到我的礼品袋中。非常感谢! @aaronofleonard 不客气,arthem 的回答也不错 :)

以上是关于Typescript - 确保泛型属性存在于具有描述性错误的泛型类型上的主要内容,如果未能解决你的问题,请参考以下文章

带有泛型的 Typescript JSX - 参数隐式具有“任何”类型

使用泛型和合并对象的 Typescript 声明文件

打字稿:允许泛型类型仅是具有“字符串”属性的对象

Typescript 从具有泛型类型的对象索引调用函数

typescript :具有原始类型约束的泛型类型

TypeScript 中具有泛型类型参数的泛型类型的替代方案