Typescript-确保泛型类型上存在泛型属性,但存在描述性错误

Typescript-确保泛型类型上存在泛型属性,但存在描述性错误,typescript,generics,Typescript,Generics,(键入脚本新手警告) 我正在创建一个可重用的reducer,它接受一个状态和一个操作并返回状态,但仅限于接受在给定键处包含特定类型的状态。此键作为函数的参数传递。如果传递的状态对象不包含传递的键,编译器应引发错误 现在,我让它工作了但是,据我估计,编译器生成的错误消息并不能充分描述问题。它并没有说“类型上缺少x属性”,而是给出了其他错误,我将在下面详细介绍 类型 底座减速器 当我将调用更改为return fetchingReducer(state,action,'fetchin')时,这也可以工

(键入脚本新手警告)

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

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

类型 底座减速器 当我将调用更改为
return fetchingReducer(state,action,'fetchin')时,这也可以工作(拼写错误的
“fetching”
),我得到:

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

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

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


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

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

export const fetchingReducer = <S extends {[k in K]: FetchState}, K extends string>(state: S, action: Action, key: K): S => {
export const fetchingReducer=(状态:S,操作:action,键:K):S=>{
这似乎会产生所需的错误消息,至少在这个示例代码中是这样(我刚刚为缺少的类型编写了定义,以使其完整):

导出接口操作{
a:弦;
}
导出接口FetchAction扩展操作{
f:字符串;
}
导出类型获取状态={
状态:行动,
时间戳:日期
}|空
导出类型登录状态={
标记:字符串| null,
获取:获取状态
};
常量初始状态:登录状态={
令牌:null,
获取:空
}
导出类型减速器=(s:s,a:Action)=>s;
const loginReducer:Reducer=(状态=初始状态,操作)=>{
fetchingReducer(状态,操作,'fetching');//类型为'LoginState'的参数
//不可分配给类型为“{fetching:FetchState;}”的参数。
//类型“LoginState”中缺少属性“fetching”。
fetchingReducer(状态,动作,'token');//类型为'LoginState'的参数为
//不可分配给类型为“{token:FetchState;}”的参数。
//属性“令牌”的类型不兼容。
//类型“string | null”不可分配给类型“FetchState”。
//类型“string”不可分配给类型“FetchState”。
//嗯
返回fetchingReducer(状态、操作“fetching”)
}
export const fetchingReducer=(状态:S,操作:action,键:K):S=>{
返回{}作为S;
}

错误的可读性,就像情人眼里出西施一样

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

对于错误部分,我使用带有描述性消息的字符串文字类型。如果您有一个名为
“This porperty不是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'); 
type EnsureFetchState=S扩展记录?{}:“此属性不是FetchState类型”;
导出函数fetchingReducer(状态:S,操作:action,键:K&EnsureFetchState):S
导出函数fetchingReducer(状态:S,操作:action,键:K&EnsureFetchState):S
导出函数fetchingReducer(状态:S,操作:action,键:K&EnsureFetchState):S{
//实施
}
//类型为“fetchin”的参数不能分配给类型为“fetchin”和“此属性不是FetchState类型”的参数。
返回fetchingReducer(状态、操作“fetchin”);

非常好,我真的很喜欢这个解决方案的独创性。另一个答案确实让我的结果更接近我想要的结果,但我仍在将此技术添加到我的糖果袋中。非常感谢!@Aaronoflonard欢迎您。阿尔塞姆的答案也很好:)谢谢,我知道一定有一种简单的方法可以做到这一点。我正在尝试要在Typescript中找到
中的
关键字的资源…但是这样一个小词很难用谷歌搜索,因为它出现在每个页面上!我不知道它可以这样使用,所以我很高兴看到它可以。这正是我理想中要找的,非常感谢。
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
}
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
}
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;
}
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');