awesome-typescript
awesome-typescript copied to clipboard
「重学TS 2.0 」TS 练习题第四题
第四题
Pick<T, K extends keyof T>
的作用是将某个类型中的子属性挑出来,变成包含这个类型部分属性的子类型。
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Pick<Todo, "title" | "completed">;
const todo: TodoPreview = {
title: "Clean room",
completed: false
};
那么如何定义一个 ConditionalPick
工具类型,支持根据指定的 Condition
条件来生成新的类型,对应的使用示例如下:
interface Example {
a: string;
b: string | number;
c: () => void;
d: {};
}
// 测试用例:
type StringKeysOnly = ConditionalPick<Example, string>;
//=> {a: string}
请在下面评论你的答案
interface Example {
a: string;
e: number;
b: string | number;
c: () => void;
d: {};
f: string | number | boolean;
}
type ConditionalPick<V, T> = {
[K in keyof V as V[K] extends T ? K : never]: V[K];
};
type StringKeysOnly = ConditionalPick<Example, string | number>;
interface Example {
a: string;
b: string | number;
c: () => void;
d: {};
}
// extends 语句,如果左边是联合类型,会依次执行,并返回一个联合类型
type Util<T, K extends keyof T, U> = K extends any ? ( T[K] extends U ? K : never ) : never;
type ConditionalPick<T,U> = {
[P in Util<T, keyof T, U>]: T[P]
}
// 测试用例:
type StringKeysOnly = ConditionalPick<Example, string>;
//=> {a: string}
interface Example {
a: string;
b: string | number;
c: () => void;
d: {};
}
type ConditionalPick<T, K> = {
[P in keyof T as (T[P] extends K ? P : never)]: T[P]
}
// 测试用例:
type StringKeysOnly = ConditionalPick<Example, string>;
//=> {a: string}
interface Example { a: string; b: string | number; c: () => void; d: {}; } type ConditionalPick<T, K> = { [P in keyof T as (T[P] extends K ? P : never)]: T[P] } // 测试用例: type StringKeysOnly = ConditionalPick<Example, string>; //=> {a: string}
这个as用得神奇。 有相关文档吗
interface Example { a: string; b: string | number; c: () => void; d: {}; } type ConditionalPick<T, K> = { [P in keyof T as (T[P] extends K ? P : never)]: T[P] } // 测试用例: type StringKeysOnly = ConditionalPick<Example, string>; //=> {a: string}
这个as用得神奇。 有相关文档吗
https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-1.html#key-remapping-in-mapped-types
奇怪,你们这么用 as 不会报语法错误吗?我的版本是 4.4.2 的
interface Example { a: string; b: string | number; c: () => void; d: {}; } // extends 语句,如果左边是联合类型,会依次执行,并返回一个联合类型 type Util<T, K extends keyof T, U> = K extends any ? ( T[K] extends U ? K : never ) : never; type ConditionalPick<T,U> = { [P in Util<T, keyof T, U>]: T[P] } // 测试用例: type StringKeysOnly = ConditionalPick<Example, string>; //=> {a: string}
ConditionalPick<Example, string | number>; 返回如下符合预期吗 // => { a: string; b: string | number; }
interface Example { a: string; b: string | number; c: () => void; d: {}; } type ConditionalPick<T, K> = { [P in keyof T as (T[P] extends K ? P : never)]: T[P] } // 测试用例: type StringKeysOnly = ConditionalPick<Example, string>; //=> {a: string}
这个as用得神奇。 有相关文档吗
https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-1.html#key-remapping-in-mapped-types
ConditionalPick<Example, string|number|{}> 的预计输出结果不对?
interface Example { a: string; b: string | number; c: () => void; d: {}; }
type CondKeys<T, Cond> = {[P in keyof T]: T[P] extends Cond ? P : never}[keyof T] type ConditionalPick<T extends object, Cond extends T[keyof T]> = Pick<T, CondKeys<T, Cond>>
// 测试用例: type StringKeysOnly = ConditionalPick<Example, string>; //=> {a: string}
interface Example { a: string; b: string | number; c: () => void; d: {}; } type ConditionalPick<T, K> = { [P in keyof T as (T[P] extends K ? P : never)]: T[P] } // 测试用例: type StringKeysOnly = ConditionalPick<Example, string>; //=> {a: string}
这个as用得神奇。 有相关文档吗
https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-1.html#key-remapping-in-mapped-types
ConditionalPick<Example, string|number|{}> 的预计输出结果不对?
感觉最后对比的还是value的type
分析:题目要求我们可以过滤出给定类型的属性。我们的思路可以按照如下分析
- 遍历每一个属性,将他的类型约束于给定的类型,符合约束把该属性的类型赋值为属性名,不符合约束变成never
- 然后再把用pick 从 对象中 把不为never的属性拿出来
//首先我们定义一个拿出符合要求的key的方法
type ConditionalKeys<T, K> = {
[P in keyof T ] : T[P] extends K ? P: never
}[keyof T]
/**
结果为:{a:a;b:never:c:never:d:never}[a|b|c|d]=>
a|never|never|never=>
a
**/
//然后我们通过Pick将符合的key对应的属性拿出来
type ConditionalPick<T ,Conditional> = Pick< T , ConditionalKeys<T, Conditional>>
interface Example {
a: string;
b: string | number;
c: () => void;
d: {};
}
type ConditionalPick<T, V> = Pick<T, {
[K in keyof T]: T[K] extends V ? K : never;
}[keyof T]>
type StringKeysOnly = ConditionalPick<Example, string>
// 那么如何定义一个 ConditionalPick 工具类型,支持根据指定的 Condition 条件来生成新的类型,对应的使用示例如下:
interface Example {
a: string;
b: string | number;
c: () => void;
d: {};
}
type PickKeys<T, K> = {
[k in keyof T]: T[k] extends K ? k : never;
}[keyof T];
type ConditionalPick<T, K> = {
[k in PickKeys<T, K>]: T[k];
};
// 测试用例:
type StringKeysOnly = ConditionalPick<Example, string>
这个题的难度其实是找到值类型符合要求的key,也就是我这里的PickKeys 我这里利用 [k in keyof T] 之后去判断值的类型 T[k] extends K 满足的话,就是key,不满足就是never,之后利用 [keyof T] 获取全部的值,这样never就被忽略掉了。
type Flat<T extends any[]> = T[number];
type NaiveFlat<T extends any[]> = Flat<Flat<T>>;
// 测试用例:
type NaiveResult = NaiveFlat<[["a"], ["b", "c"], ["d"]]>;
// NaiveResult的结果: "a" | "b" | "c" | "d"
type DeepFlat<T extends any[]> = Extract<Flat<T>, any[]> extends never
? Flat<T>
: Exclude<Flat<T>, any[]> | DeepFlat<Extract<Flat<T>, any[]>>;
// 测试用例
type Deep = [["a"], ["b", "c"], [["d"]], [[[["e"]]]]];
type DeepTestResult = DeepFlat<Deep>;
// DeepTestResult: "a" | "b" | "c" | "d" | "e"
type ConditionalPick<T, P> = { [K in keyof T as (T[K] extends P ? K : never)]: T[K] };
type ConditionalPick<T, K> = { [P in keyof T as T[P] extends K ? T[P] extends K ? P : never : never]: T[P] }
interface Example { a: string; b: string | number; c: () => void; d: {}; }
// 测试用例: type StringKeysOnly = ConditionalPick<Example, string>; //=> {a: string} let test: StringKeysOnly = { a: '1' } export default {}
type ConditionalPick<T extends object, U> = { [P in keyof T as ([T[P]] extends [U] ? P : never)]: T[P]; }
type ConditionalPick<E, T> = {
[K in keyof E as E[K] extends T ? K : never]: E[K]
}
type IsExtendsType<T, U> = {
[K in keyof T]: T[K] extends U ? K : never
}[keyof T]
type ConditionalPick<T, U> = {
[K in IsExtendsType<T, U>]: T[K]
}
type Test = ConditionalPick<{
id: number
name: string
gender: string
age: number
}, string>
解题步骤
- 先用
keyof
拿到类型的所有key; - 再用
as
过滤这些key;
type ConditionalPick<T, V> = {
[P in keyof T as (T[P] extends V ? P : never)]: T[P]
}
// 遍历并as判断 interface Example { a: string; b: string | number; c: () => void; d: {}; }
// 测试用例: type StringKeysOnly = ConditionalPick<Example, string>; //=> {a: string}
type ConditionalPick<T, P> = { [K in keyof T as T[K] extends P ? K : never]: T[K] }
interface Example {
a: string;
b: string | number;
c: () => void;
d: {};
}
type ConditionalPick<T, P> = {
[K in keyof T as T[K] extends P ? K : never]: T[K]
}
type MappedTypeWithNewKeys<T> = {
[K in keyof T as `${Capitalize<string & K>}`]: T[K]
}
type ConvertedUpperKeyType= MappedTypeWithNewKeys<Example>
// 测试用例:
type StringKeysOnly = ConditionalPick<Example, string>;
//=> {a: string}
interface Example {
a: string;
b: string | number;
c: () => void;
d: {};
}
// 关键就是找到我们传入进去的类型
type ConditionalPick<T, K> ={
[P in keyof T as T[P] extends K ? P : never]: T[P]
}
// 测试用例:
type StringKeysOnly = ConditionalPick<Example, string>;
//=> {a: string}
type ConditionalPick<T, K> = {
[P in keyof T]: T[P] extends K ? T[P] : never
}
为啥不能直接把非对应类型的key置为never...
哦... 我的问题, 这样只是将其他key置为never了, 实际上还要赋值
type ConditionalPick<T, V> = {
[K in keyof T as (T[K] extends V ? K:never) ] :T[K]
}
// 楼上我没细看, 但是
大部分人的答案其实都有瑕疵
瑕疵的地方在于Typescript 当中, Function extends object为 true(Function 是 object/Record的子类型), 也就是如果将测试用例更改为提取
object
类型而不是'string'等类型的话, 这个时候 Function 类型也会被并入
// 瑕疵版本案例
type ConditionalPick<T, V> = {
[K in keyof T as (T[K] extends V ? K:never) ] :T[K]
}
// 测试用例:
type StringKeysOnly = ConditionalPick<Example, object>;
interface Example {
a: string;
b: string | number;
c: () => void;
d: {};
}
// 测试用例:
type StringKeysOnly = ConditionalPick<Example, object>;
//=> {a: string}
/*
type StringKeysOnly = {
c: () => void; //✨✨✨✨✨✨✨我们想要的是'object', 但是这里'Function'也被摄入了
d: {};
}
*/
所以其实我们应该还需要在as
索引类型重映射里面做更细致的处理来判断如果当筛选类型为object/Record
的情况
interface Example {
a: string;
b: string | number;
c: () => void;
d: {};
}
type ConditionalPick<T extends object,U extends T[keyof T]> = {
[
K in keyof T as T[K] extends U?
U extends object?
U extends Function ?
K
: T[K] extends Function? never : K
: K
: never
] : T[K]
}
// 测试用例:
type StringKeysOnly = ConditionalPick<Example, object>;
//结果
//type StringKeysOnly = {
d:{}
} ;
具体代码可以查看 https://www.typescriptlang.org/play?ts=4.5.0-beta#code/JYOwLgpgTgZghgYwgAgKIA84FsAOAbFAbwCgBIOALmQGcwpQBzAbjICMrb6QHkAfZEAFcsraC1IIqACgCUyALwA+ZADcA9sAAm4zVUIBfFvuJgAnjhQBhNSE3AwwG3DwAFYAgDWAHgAqyCOiQttTIaqwAVhAIYAA0AKr+gRDByD4A2h4QpmowqQC6yvLIJMilyGnEZVXIANLIoMiZ2bl+cCHpNXmJQZohcQD8yJXVIwkBPSFhkdH9wyPzyGNJKQBigiDRjiDIswt7ZTVz+-NUHV3jyb3IaxsONoMgECrQyFSHx6+1R1VUj89QRy6pzSnWIxmIEIA9JDkIBpW0Aq9GACldANHygCx-kzmFAAZTojBqWWoAHkQHhTApkNZbPYts43J4vBhsPgIDFQhEomBFCxoUpipQaLjuMYgA
interface Example {
a: string;
b: string | number;
c: () => void;
d: {};
}
type ConditionalPick<T, R> = {
[K in keyof T as (T[K] extends R ? K : never)]: T[K] ;
};
// 测试用例:
type StringKeysOnly = ConditionalPick<Example, string>;
interface Example {
a: string;
b: string | number;
c: () => void;
d: {};
}
// typescript4.1新特性:支持使用as符号对键做重映射
// 利用as重映射和never会被忽略的效果来做值的条件选择
type ConditionalPick<T, U> = {
[P in keyof T as T[P] extends U ? P : never]: T[P];
};
// 测试用例:
type StringKeysOnly = ConditionalPick<Example, string>;
type ConditionalPick<V, T> = {
[K in keyof V as V[K] extends T ? K : never]: V[K];
};
若对象V的属性值的类型可以赋值给V类型,返回给K 否则返回 never,表示不保留该属性名