基于另一个Typescript通用参数的属性来约束它?

问题描述

我正在尝试编写一个函数,该函数接受一个对象和一个(字符串)键,然后对该对象的属性进行操作。这很简单:

function f<T extends any,K extends keyof T>(obj: T,key: K) {
  const prop = obj[key]; // prop is typed as T[K]
}

我想基于T[K]的类型,在编译时约束传递给调用的密钥。我尝试过:

function f<T extends any,key: T[K] extends number ? K : never) {
  obj[key] = 5; // error,"is not assignable to" etc
}

prop键入为T[T[K] extends number ? K : never],这对我来说就像应该折叠为number,但不是那样。

我的目标是确保在函数内部将obj[key]键入为number,并且将类似f({a: true},"a")之类的调用标记错误。这可能吗?我以为我可能需要将约束从函数参数声明移到泛型参数声明,但我无法弄清楚语法。


ETA再一次:Playground example-更新为尝试@reactgular在评论中建议的方法

type AssignableKeys<T,ValueType> = {
  [Key in keyof T]-?: ValueType extends T[Key] | undefined ? Key : never
}[keyof T];

type PickAssignable<T,ValueType> = Pick<T,AssignableKeys<T,ValueType>>;

type OnlyAssignable<T,ValueType> = {
  [Key in AssignableKeys<T,ValueType>]: ValueType
};

interface Foo {
  a: number;
  b: string;
  nine: 9;
  whatevs: any;
}

type FooNumberKeys = AssignableKeys<Foo,number>; // "a" | "whatevs"
type CanAssignNumber = PickAssignable<Foo,number>; //  { a: number; whatevs: any; }
type DefinitelyJustNumbers = OnlyAssignable<Foo,number>; //  { a: number; whatevs: number; }

function f1<T>(obj: OnlyAssignable<T,number>,key: keyof OnlyAssignable<T,number>) {
  obj[key] = Math.random(); // Assignment is typed correctly,good
}

function f2<T extends object,K extends keyof PickAssignable<T,number>>(obj: T,key: K) {
  obj[key] = Math.random(); // Uh oh,Type 'number' is not assignable to type 'T[K]'.(2322)
}

declare const foo: Foo;
f1(foo,"a"); // Allowed,good
f1(foo,"whatevs"); // Allowed,"nine"); // Uh oh,should error,but doesn't!
f1(foo,"b"); // Error,good

f2(foo,good
f2(foo,"nine"); // Error,good

在操场上,DefinitelyJustNumbers显示了工具提示{a: number; whatevs: number} -我可以将number分配给的任何内容都明确地键入为number。这样可以固定函数体内的赋值,但是无法检测到nine只是数字的一个子集,因此不应该被允许的事实。

CanAssignNumber显示的工具提示{a: number; whatevs: any},正确地排除了nine,因为它不能分配给number。看起来不错,但仍不能解决函数f2中的赋值问题。

解决方法

您应该从产生值类型为onActivityResult的键开始。

Intent

编辑:TS AFAIK无法实现您要执行的操作,有时这是有充分理由的。假设您有以下代码:

        ContentResolver contentResolver = activityMain.getContentResolver();
        String selection = DocumentsContract.Document.COLUMN_DOCUMENT_ID + " == ?";
        String[] selectionArgs = new String[]{docID};
        try (Cursor cursor = contentResolver.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,new String[]{
                        MediaStore.Audio.Media._ID,MediaStore.Audio.Media.DISPLAY_NAME,MediaStore.Audio.Media.TITLE,MediaStore.Audio.Media.ARTIST,MediaStore.Audio.Media.DATA
                },selection,selectionArgs,null)) {
            if (cursor != null) {
                while (cursor.moveToNext()) {
                    int idCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID);
                    int nameCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DISPLAY_NAME);
                    int titleCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE);
                    int artistCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST);
                    int dataCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA);
                    long id = cursor.getLong(idCol);
                    this.uriID = id;
                    String displayName = cursor.getString(nameCol);
                    String title = cursor.getString(titleCol);
                    String artist = cursor.getString(artistCol);
                    String data = cursor.getString(dataCol);
                    Uri uri1 = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,id);
                }
            }
        }

例如,在上述情况下,属性number的值是一个数字,但是它不是export type PickByValue<T,ValueType> = Pick< T,{ [Key in keyof T]-?: T[Key] extends ValueType ? Key : never }[keyof T] >; function f<T extends object,K extends keyof PickByValue<T,number>>(obj: T,key: K) : T[K] { return obj[key] } 类型,而是function f<T extends object,key: K) { obj[key] = 5; // Type 'number' is not assignable to type 'T[K]'. } const obj = {a: 9} as const; f(obj,"a") 类型。打字稿无法事先知道这一点。在其他情况下,我唯一想到的就是使用Type Assertions

,

只需使用类型断言,您所关注的应该是呼叫站点,该站点正确键入并在应有的位置给您错误。如果要在函数中分配特定的值,则无法真正正确地键入实现。

您可以使T[K]的结果扩展数字,例如但要为T的{​​{1}}添加约束,但是我们仍然无法为{{1 }}

Record<K,number>

Playground Link

之所以如此,是最后一个示例obj[key]type KeyOfType<T,ValueType> = { [Key in keyof T]-?: T[Key] extends ValueType | undefined ? Key : never }[keyof T] function f<T extends Record<K,number>,K extends KeyOfType<T,key: K,value: T[K]) { let r = obj[key]; r.toExponential(); // seems numberish,but it `T[K]` which does extend number,but might not be number obj[key] = obj[key] // T[K] is assignable to T[K] obj[key] = value; // even if it is a parameter obj[key] = 5; // still an error } declare const foo: Foo; f(foo,"a",1); // Allowed,good f(foo,"b",2); // Error,good const other: { a: 1 } = { a: 1 } f(other,1) // this will break the type of other because of obj[key] = 5 中的f(other,1)类型为a,它的类型确实扩展了other,因此1是对number的有效调用,但我们希望分配f(other,1)。这将破坏f的类型。这里的问题是,无法这样指定other[key] = 5的上约束other只是下约束。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...