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