问题描述
我正在尝试编写一个函数,该函数接受一个对象和一个(字符串)键,然后对该对象的属性进行操作。这很简单:
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>
之所以如此,是最后一个示例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
只是下约束。