举个例子:

type UpdateFieldValue<T extends Record<string, unknown>> = (key: keyof T, value: SomeType) => void

我希望SomeTypeT的属性(Key)的值类型,key由第一个函数参数提供.

所以如果T看起来是这样的:

{ age: number, name: string }

然后,在以下情况下:

UpdateFieldValue<{ age: number, name: string }>('name'...

TS应要求将string作为第二个参数传递,并且在以下情况下:

UpdateFieldValue<{ age: number, name: string }>('age'...

TS需要指定number作为第二个参数

每次我得到了一些工作,它似乎失败,只要我需要显式地传递T作为类型参数.谢谢.

推荐答案

通过使生成的函数也是泛型的,您可以基于T在两个参数之间建立关系:

type UpdateFieldValue<T extends Record<string, unknown>> = <K extends keyof T>(key: K, value: T[K]) => void

declare const foo: UpdateFieldValue<{ age: number, name: string }>

// Works
foo('age', 3);

// No good
foo('name', 3);

TypeScript Playground

这在第一个参数的类型已经缩小为文字类型的情况下非常有效,第一个参数的类型是TypeScrip用来推断泛型类型K并通过它的T[K].

但是,如果您try 在泛型类型设置为联合的情况下调用它,无论是显式还是通过推理,则可能会遇到麻烦:

type UpdateFieldValue<T extends Record<string, unknown>> = <K extends keyof T>(key: K, value: T[K]) => void

declare const foo: UpdateFieldValue<{ age: number, name: string }>

// Works
foo<'age' | 'name'>('age', 'string');

const bar = Math.random() > 0.5 ? 'age' : 'name';

// Also works
foo(bar, 3);

TypeScript Playground

如果这对您来说是个问题,我想我已经发现强制只允许字符串文字类型.但它需要使用一些复杂的实用程序类型来完成此任务.此外,对于某些类型(例如Record<string, boolean>),无论如何这可能不是首选的行为.但为了完整起见,我想将其包括在内.

它使用Equals类型和UnionToIntersection类型将参数的类型转换为其联合成员的交集,然后判断它是否没有更改.这应该只适用于文本类型,它可以被视为具有1个成员的联合,而对于never(实际上是具有0个成员的联合).

下面是该实现可能是什么样子:

/**
 * @see {@link https://github.com/microsoft/TypeScript/issues/27024#issuecomment-421529650}
 */
type Equals<A, B> = (
    <T>() => T extends A ? 1 : 2
) extends (
    <T>() => T extends B ? 1 : 2
) ? true : false;

/**
 * @see {@link https://stackoverflow.com/a/50375286/1710523}
 */
type UnionToIntersection<U> = (
    U extends unknown ? (arg: U) => void : never
) extends (arg: infer I) => void ? I : never;

type LiteralOnly<S> = Equals<S, UnionToIntersection<S>> extends true ? S : never;

type UpdateFieldValue<T extends { [key: string]: unknown; }> = <K extends keyof T>(key: LiteralOnly<K>, value: T[K]) => void;

declare const foo: UpdateFieldValue<{ age: number, name: string }>;

// No good
foo<'age' | 'name'>('age', 'string');
foo('age', 'string');

// Works
foo('age', 3);
foo('name', 'string');

const uncertainKey = Math.random() > 0.5 ? 'age' : 'name';

// No good
foo(uncertainKey, 3);

declare const bar: UpdateFieldValue<Record<string, boolean>>;

// Still not allowed, but perhaps should be
bar(uncertainKey, true);

// Works
bar('foo', true);

TypeScript Playground


另一种方法没有上面讨论的泛型解决方案中讨论的缺点,它是通过将参数类型定义为单个类型来链接它们.这可以使用扩展语法和元组类型来实现.

通过使用immediately indexed mapped type可以将该元组类型构造为区分并集,这应该允许您通过缩小函数中的任一参数类型来缩小函数中两个参数的类型.

这种方法的缺点是,IntelliSense不是很好,当传递无效参数时,不清楚出了什么问题.但如果你关心的是某件事是否被允许,这种方法似乎确实能带来更好的结果:

type UpdateFieldValue<T extends Record<string, unknown>> = (
    ...[key, value]: { [K in keyof T]: [K, T[K]] }[keyof T]
) => void

declare const foo: UpdateFieldValue<{ age: number, name: string }>

// No good
foo<'age' | 'name'>('age', 'string');
foo('age', 'string');

// Works
foo('age', 3);
foo('name', 'string');

const uncertainKey = Math.random() > 0.5 ? 'age' : 'name';

// No good
foo(uncertainKey, 3);

declare const bar: UpdateFieldValue<Record<string, boolean>>;

// Allowed
bar(uncertainKey, true);

// Works
bar('foo', true);

TypeScript Playground

Typescript相关问答推荐

TypScript中的算法运算式

如何传递基于TypScript中可变类型的类型?

Angular 过滤器形式基于某些条件的数组控制

在配置对象上映射时,类型脚本不正确地推断函数参数

根据上一个参数值查找参数类型,也返回类型

如何使用一个字段输入对象,其中每个键只能是数组中对象的id属性,而数组是另一个字段?

APIslice-CreateAPI的RTK打字错误

是否可以针对联合类型验证类型属性名称?

在另一个类型的函数中,编译器不会推断模板文字类型

通过泛型函数本身推断受约束的泛型参数的类型脚本问题

如何在省略一个参数的情况下从函数类型中提取参数类型?

从非文字数组(object.keys和array.map)派生联合类型

ANGLE NumberValueAccessor表单控件值更改订阅两次

是否可以通过映射类型将函数参数约束为预定义类型?

TypeScrip-如何自动推断UNION变量的类型

将接口映射到交叉口类型

如何扩展RxJS?

打字脚本错误:`不扩展[Type]`,而不是直接使用`[Type]`

Vite+Chrome扩展 list v3-;不能在模块外使用import语句;对于inpage脚本

Typescript循环函数引用