(打字稿新手警告)
我正在创建一个可重用的化简器,它接受一个状态和一个动作并返回该状态,但仅限于接受在给定键处包含某种类型的状态。该键作为函数的参数传递。如果传递的状态对象不包含传递的密钥,则编译器应引发错误。
现在,我已经开始工作了。 但是,据我估计,编译器生成的错误消息不足以描述问题。它并没有说“ 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'。种类 属性“令牌”不兼容。 输入'string | “ 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中,我使用了条件类型来确定我传递的state
和key
的值未满足我们正在搜索的条件。但是,在这两种情况下,错误消息都没有真正描述什么是真正的问题。
我是Typescript中具有更高级类型的新手,所以可能有一种非常简单的方法来执行此操作,或者是一个我忽略的简单概念。但愿如此!但是无论如何,要旨是:如何用惯用方式对具有动态键的对象进行这种类型的检查,或者以一种使编译器生成更有益的错误消息的方式进行检查?
答案 0 :(得分:2)
在描述类型约束时,通常有助于尽可能直接。类型为S
且属性为K
且类型为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, 'fetcing'); // Argument of type 'LoginState'
// is not assignable to parameter of type '{ fetcing: FetchState; }'.
// Property 'fetcing' 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;
}
这似乎会产生所需的错误消息,至少使用以下示例代码(我只是为缺少的类型组成了定义以使其完整):
[output1, output2] = some_func()
答案 1 :(得分:2)
错误的可读性,就像情人眼中的情人一样。
我认为,我能得到的最漂亮的错误是添加第二个重载,其中K
是PropertyKey
。该错误是由条件类型触发的,但条件类型已添加到key
参数上。第二次重载的需要来自这样一个事实:如果K extends keyof S
并且在key
上有错误,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');