awesome-typescript icon indicating copy to clipboard operation
awesome-typescript copied to clipboard

「重学TS 2.0 」TS 练习题第四题

Open semlinker opened this issue 2 years ago • 33 comments

第四题

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}

请在下面评论你的答案

semlinker avatar Sep 14 '21 15:09 semlinker

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>;

sunboyZgz avatar Sep 15 '21 06:09 sunboyZgz

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}

moronghui avatar Sep 18 '21 07:09 moronghui

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}

Mrlgm avatar Sep 18 '21 09:09 Mrlgm

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用得神奇。 有相关文档吗

McDaddy avatar Sep 18 '21 11:09 McDaddy

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

Col0ring avatar Sep 20 '21 19:09 Col0ring

奇怪,你们这么用 as 不会报语法错误吗?我的版本是 4.4.2 的 image

brilliantGuo avatar Sep 21 '21 09:09 brilliantGuo

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; }

safarishi avatar Sep 22 '21 13:09 safarishi

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|{}> 的预计输出结果不对?

duriann avatar Sep 23 '21 11:09 duriann

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}

emwanwei163 avatar Sep 25 '21 14:09 emwanwei163

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

pagnkelly avatar Sep 26 '21 07:09 pagnkelly

分析:题目要求我们可以过滤出给定类型的属性。我们的思路可以按照如下分析

  1. 遍历每一个属性,将他的类型约束于给定的类型,符合约束把该属性的类型赋值为属性名,不符合约束变成never
  2. 然后再把用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>>

yzycool avatar Sep 27 '21 15:09 yzycool

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>

SUPERSUJ avatar Sep 28 '21 08:09 SUPERSUJ

// 那么如何定义一个 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就被忽略掉了。

zhaoxiongfei avatar Oct 01 '21 13:10 zhaoxiongfei

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"

Gengar-666 avatar Oct 12 '21 08:10 Gengar-666

type ConditionalPick<T, P> = { [K in keyof T as (T[K] extends P ? K : never)]: T[K] };

yanglinxiao avatar Nov 18 '21 02:11 yanglinxiao

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 {}

a572251465 avatar Dec 01 '21 05:12 a572251465

type ConditionalPick<T extends object, U> = { [P in keyof T as ([T[P]] extends [U] ? P : never)]: T[P]; }

liulise avatar Dec 03 '21 09:12 liulise

type ConditionalPick<E, T> = {
    [K in keyof E as E[K] extends T ? K : never]: E[K]
}

dolphin0618 avatar Dec 10 '21 07:12 dolphin0618

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>

ShawDuChen avatar Dec 28 '21 09:12 ShawDuChen

解题步骤

  1. 先用keyof拿到类型的所有key;
  2. 再用as过滤这些key;
type ConditionalPick<T, V> = {
  [P in keyof T as (T[P] extends V ? P : never)]: T[P]
}

junbin123 avatar Feb 28 '22 00:02 junbin123

// 遍历并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] }

waleiwalei avatar Mar 11 '22 06:03 waleiwalei

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}

codeyourwayup avatar Mar 27 '22 06:03 codeyourwayup

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}

YJCCreateAHistory avatar May 24 '22 09:05 YJCCreateAHistory

type ConditionalPick<T, K> = {
  [P in keyof T]: T[P] extends K ? T[P] : never
}

为啥不能直接把非对应类型的key置为never...

ChangerHe avatar May 26 '22 15:05 ChangerHe

哦... 我的问题, 这样只是将其他key置为never了, 实际上还要赋值

ChangerHe avatar May 26 '22 15:05 ChangerHe

type ConditionalPick<T, V> = {
	[K in keyof T as (T[K] extends V ? K:never) ] :T[K]
}

xiliangzhou6216 avatar Jun 02 '22 08:06 xiliangzhou6216

// 楼上我没细看, 但是

大部分人的答案其实都有瑕疵

瑕疵的地方在于

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

heweijian4933 avatar Jun 02 '22 10:06 heweijian4933

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>;

fishcoderman avatar Aug 25 '22 15:08 fishcoderman

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>;

SmileHeart1996 avatar Sep 14 '22 09:09 SmileHeart1996

type ConditionalPick<V, T> = {
  [K in keyof V as V[K] extends T ? K : never]: V[K];
};

若对象V的属性值的类型可以赋值给V类型,返回给K 否则返回 never,表示不保留该属性名

gtpfx avatar Jul 18 '23 06:07 gtpfx