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

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

Open semlinker opened this issue 4 years ago • 49 comments

第三题

掌握 TS 这些工具类型,让你开发事半功倍 这篇文章中,阿宝哥介绍了 TS 内置的工具类型:Partial<T>,它的作用是将某个类型里的属性全部变为可选项 ?

interface Todo {
  title: string;
  description: string;
}

function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
  return { ...todo, ...fieldsToUpdate };
}

// lib.es5.d.ts
type Partial<T> = {
  [P in keyof T]?: T[P];
};

那么如何定义一个 SetOptional 工具类型,支持把给定的 keys 对应的属性变成可选的?对应的使用示例如下所示:

type Foo = {
	a: number;
	b?: string;
	c: boolean;
}

// 测试用例
type SomeOptional = SetOptional<Foo, 'a' | 'b'>;

// type SomeOptional = {
// 	a?: number; // 该属性已变成可选的
// 	b?: string; // 保持不变
// 	c: boolean; 
// }

在实现 SetOptional 工具类型之后,如果你感兴趣,可以继续实现 SetRequired 工具类型,利用它可以把指定的 keys 对应的属性变成必填的。对应的使用示例如下所示:

type Foo = {
	a?: number;
	b: string;
	c?: boolean;
}

// 测试用例
type SomeRequired = SetRequired<Foo, 'b' | 'c'>;
// type SomeRequired = {
// 	a?: number;
// 	b: string; // 保持不变
// 	c: boolean; // 该属性已变成必填
// }

请在下面评论你的答案

semlinker avatar Sep 14 '21 15:09 semlinker

type Foo = {
	a?: number;
	b: string;
	c?: boolean;
}

// 测试用例
type SomeRequired = SetRequired<Foo, 'b' | 'c'>;
// type SomeRequired = {
// 	a?: number;
// 	b: string; // 保持不变
// 	c: boolean; // 该属性已变成必填
// }
type Foo = {
	a?: number;
	b: string;
	c?: boolean;
};

// 测试用例
type SomeRequired = SetRequired<Foo, 'b' | 'c'>;
// type SomeRequired = {
// 	a?: number;
// 	b: string; // 保持不变
// 	c: boolean; // 该属性已变成必填
// }
type SetRequired<T, K extends keyof T> = {
	[P in keyof T as P extends K ? P : never]-?: T[P];
} &
	{
		[P in keyof T as P extends K ? never : P]: T[P];
	} extends infer R
	? { [K in keyof R]: R[K] }
	: unknown;

fengsai avatar Sep 15 '21 03:09 fengsai

type Foo = {
  a: number;
  b?: string;
  c: boolean;
};
// 交叉 、 `-?`去可选
type SetRequired<T, O> = {
  [K in keyof T as Extract<K, O>]-?: T[K];
} &
  {
    [K in keyof T as Exclude<K, O>]: T[K];
  };
type SetRequiredShow<T, O> = {
  [K in keyof SetRequired<T, O>]: SetRequired<T, O>[K];
};
type someRequired = SetRequiredShow<Foo, "a" | "b">;

sunboyZgz avatar Sep 15 '21 05:09 sunboyZgz

3.1 SetOptional

type Foo = {
	a: number;
	b?: string;
	c: boolean;
}

type Simplify<T> = {
  [P in keyof T]: T[P]
}

type SetOptional<T, K extends keyof T> = 
  Simplify<Partial<Pick<T, K>> & Pick<T, Exclude<keyof T, K>>>

// 测试用例
type SomeOptional = SetOptional<Foo, 'a' | 'b'>;
// type SomeOptional = {
// 	a?: number; // 该属性已变成可选的
// 	b?: string; // 保持不变
// 	c: boolean;
// }

在以上代码中,我们定义了一个 Simplify 工具类型,用来对交叉类型进行扁平化处理。具体的作用,你们可以实际体验一下。

3.2 SetRequired

type Foo = {
	a?: number;
	b: string;
	c?: boolean;
}

type Simplify<T> = {
  [P in keyof T]: T[P]
}

type SetRequired<T, K extends keyof T> = Simplify<Pick<T, Exclude<keyof T, K>> & Required<Pick<T, K>>>

// 测试用例
type SomeRequired = SetRequired<Foo, 'b' | 'c'>;
// type SomeRequired = {
// 	a?: number;
// 	b: string; // 保持不变
// 	c: boolean; // 该属性已变成必填
// }

semlinker avatar Sep 16 '21 01:09 semlinker

type SetOptional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
type SetOptionalOmit<T, K extends keyof T> = Pick<T, K> & Partial<Omit<T, K>>;
type SetRequired<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;
type SetRequiredOmit<T, K extends keyof T> = Pick<T, K> & Required<Omit<T, K>>;

SSSensational avatar Sep 18 '21 03:09 SSSensational

type Foo = {
  a: number;
  b?: string;
  c: boolean;
}

type Simplely<T> = {
  [P in keyof T]: T[P]
}

type SetOptional<T, K extends keyof T> = Simplely<{ [X in keyof Omit<T, K>]: T[X]; } & { [P in K]?: T[P] }>;
type SetRequired<T, K extends keyof T> = Simplely<{ [X in keyof Omit<T, K>]: T[X]; } & { [P in K]-?: T[P] }>;


// 测试用例
type SomeOptional = SetOptional<Foo, 'a' | 'b'>;
type SomeRequired = SetRequired<Foo, 'b' | 'c'>;

Mrlgm avatar Sep 18 '21 08:09 Mrlgm

type Simplify<T> = {
    [P in keyof T]: T[P]
}

type SetOptional<T, K extends keyof T> = Simplify<Partial<Pick<T, K>> & Omit<T, K>>;

type SetRequired<T, K extends keyof T> = Simplify<Required<Pick<T, K>> & Omit<T, K>>

zsqsmart avatar Sep 18 '21 10:09 zsqsmart

type Foo = {
    a: number
    b?: string
    c?: boolean
}


type SetOption<T, K extends keyof T> = {
    [P in K]?: T[P];
} & {
    [p in Exclude<keyof T,K>]: T[p]
}


type abc = SetOption<Foo, 'a' | 'b'>

谁知道为什么我这样写,类型abc中的c是必填的吗

realheng avatar Sep 18 '21 10:09 realheng

type Foo = {
    a: number
    b?: string
    c?: boolean
}


type SetOption<T, K extends keyof T> = {
    [P in K]?: T[P];
} & {
    [p in Exclude<keyof T,K>]: T[p]
}


type abc = SetOption<Foo, 'a' | 'b'>

谁知道为什么我这样写,类型abc中的c是必填的吗

你可以利用 Simplify 来处理一下: type Simplify<T> = { [K in keyof T]: T[K] }

type abc = Simplify<SetOption<Foo, 'a' | 'b'>>

semlinker avatar Sep 18 '21 10:09 semlinker

type Foo = {
	a?: number;
	b: string;
	c?: boolean;
}

// 测试用例
type SomeRequired = SetRequired<Foo, 'b' | 'c'>;
// type SomeRequired = {
// 	a?: number;
// 	b: string; // 保持不变
// 	c: boolean; // 该属性已变成必填
// }
type Foo = {
	a?: number;
	b: string;
	c?: boolean;
};

// 测试用例
type SomeRequired = SetRequired<Foo, 'b' | 'c'>;
// type SomeRequired = {
// 	a?: number;
// 	b: string; // 保持不变
// 	c: boolean; // 该属性已变成必填
// }
type SetRequired<T, K extends keyof T> = {
	[P in keyof T as P extends K ? P : never]-?: T[P];
} &
	{
		[P in keyof T as P extends K ? never : P]: T[P];
	} extends infer R
	? { [K in keyof R]: R[K] }
	: unknown;

根据引用思路,补一下 SetOptional 的实现:

type SetOptional<O extends object, P extends keyof O> = {
  [K in P]?: O[K];
} &
  {
    [K in keyof O as K extends P ? never : K]: O[K];
  } extends infer R
  ? { [K in keyof R]: R[K] }
  : unknown;

ccbabi avatar Sep 19 '21 02:09 ccbabi

type Foo = {
	a: number;
	b?: string;
	c: boolean;
}
type PickAll<T> = {
  [p in keyof T]: T[p]
}
type SetOptional<T, K extends keyof T> = PickAll<Partial<Pick<T, K>> & Pick<T, Exclude<keyof T, K>>>

// 测试用例
type SomeOptional = SetOptional<Foo, 'a' | 'b'>;

type SetRequired<T, K extends keyof T> = PickAll<Required<Pick<T, K>> & Pick<T, Exclude<keyof T, K>>>
// 测试用例
type SomeRequired = SetRequired<Foo, 'b' | 'c'>;

mingzhans avatar Sep 21 '21 14:09 mingzhans

本质上要自己实现 Partial, Required, Pick, Exclude, Omit 等简单工具类型, 然后组合出复杂类型

type Partial_<T, K extends keyof T = keyof T> = {
    [k in K]?: T[k]
}

type Required_<T, K extends keyof T = keyof T> = {
    [k in K]-?: T[k]
}

type Pick_<T, K extends keyof T> = {
    [k in K]: T[k]
}  

type Exclude_<T, U> = T extends U ? never : T

type Omit_<T, K extends keyof T> = Pick_<T, Exclude_<keyof T, K>>

// type SetOptional<T, K extends keyof T> = Partial_<Pick_<T, K>> & Omit_<T, K>
// type SetRequired<T, K extends keyof T> = Required_<Pick_<T, K>> & Omit_<T, K>
// 上面的写法,并不能直观的看到最终的结果,所以这里可以遍历一下所得结果的 keys

type SetOptional<T, K extends keyof T, S = Partial_<Pick_<T, K>> & Omit_<T, K>> = {
    [k in keyof S]: S[k]
}
type SetRequired<T, K extends keyof T, S = Required_<Pick_<T, K>> & Omit_<T, K>> = {
    [k in keyof S]: S[k]
}

// 测试用例
type Foo = {
  a?: number;
  b: string;
  c: boolean;
}

type SomeOptional1 = SetOptional<Foo, 'a' | 'c'>;
type SomeOptional2 = SetRequired<Foo, 'a' | 'b'>;

xiaoYuanDun avatar Sep 22 '21 10:09 xiaoYuanDun

分析:题目是,将参数中的属性变为可选,其他不变,我们分析得出结论

  1. 首先获取到要变为可选的参数,我们用 pick 获取到参数中的属性,然后用Partial 将他们全部变为可选。
  2. 使用 Pick 和 Exclude 将不变化的参数拿出来.
  3. 将两个结果交叉类型,然后扁平处理
//获取处理完的交叉类型
type setOptional <T , K extends keyof T> = 
Partial<Pick<T , k>> & Pick< T, Exclude<keyof T ,K>>
//数组扁平处理
type flattening <T> = 
<P in keyof T> : T[p]

//最终版本
type flattening <T> = 
<P in keyof T> : T[p]

type setOptional <T , K extends keyof T> = 
flattening<Partial<Pick<T , k>> & Pick< T, Exclude<keyof T ,K>>>

yzycool avatar Sep 24 '21 13:09 yzycool

type SetOptional<T, K extends keyof T > = Omit<T, K> & { [k in K]?: T[k] } 我这样对吗

ethnft avatar Sep 27 '21 01:09 ethnft

type Simplify<T> = {
	[k in keyof T]: T[k];
}

type SetOptional<T, K extends keyof T> = Simplify<{
  [k in K]?: T[k];
} & Pick<T, Exclude<keyof T, K>>>

type Foo = {
	a: number;
	b?: string;
	c: boolean;
}

// 测试用例
type SomeOptional = SetOptional<Foo, 'a' | 'b'>;

解题思路,思路比较简单,构造一个指定key的类型全部设置为可选,另外构造一个剩余其他属性的,保持原来的样子,之后二者做交叉,typescript里的交叉类型类似于js的Object.assign 会合并属性。不过在样式上看着还是 & 这么写的,最后通过一个 Simplify 来把他变成一个简单结构类型。

zhaoxiongfei avatar Oct 01 '21 11:10 zhaoxiongfei

type Simplify<T> = {
	[k in keyof T]: T[k];
}

type SetRequired<T, K extends keyof T> = Simplify<{
	[k in K]-?: T[k];
} & Pick<T, Exclude<keyof T, K>>>

type Foo = {
	a?: number;
	b: string;
	c?: boolean;
}

type T1 = Pick<Foo, 'a'>

// 测试用例
type SomeRequired = SetRequired<Foo, 'b' | 'c'>;

这个和setOption 唯一的区别就是 -? 和 ? 的区别。

zhaoxiongfei avatar Oct 01 '21 12:10 zhaoxiongfei

type Foo = {
    a: number
    b?: string
    c?: boolean
}


type SetOption<T, K extends keyof T> = {
    [P in K]?: T[P];
} & {
    [p in Exclude<keyof T,K>]: T[p]
}


type abc = SetOption<Foo, 'a' | 'b'>

谁知道为什么我这样写,类型abc中的c是必填的吗

你这样写改变了c的选项类型

1938690786 avatar Oct 25 '21 10:10 1938690786

直接用现有的工具类型可以吗?

type Foo = { a: number; b?: string; c: boolean; }

type SetOptional<T,U extends keyof T> = Partial<Pick<T,U>> type SetRequired<T,U extends keyof T> = Required<Pick<T,U>>

// 测试用例 type SomeOptional = SetOptional<Foo, 'a' | 'b'>; type SomeRequired = SetRequired<Foo,'a' | 'b'>

// type SomeOptional = { // a?: number; // 该属性已变成可选的 // b?: string; // 保持不变 // c: boolean; // }

xixisanmao avatar Nov 05 '21 09:11 xixisanmao

type Simplify<T> = { [K in keyof T]: T[K]; };

type SetOptional<T, K extends keyof T> = Simplify< Partial<Pick<T, K>> & Omit<T, K>

;

type SetRequired<T, K extends keyof T> = Simplify< Required<Pick<T, K>> & Omit<T, K>

;

yanglinxiao avatar Nov 18 '21 02:11 yanglinxiao

type Foo = { a: number; b?: string; c: boolean; }

type SetOptional<T, K extends keyof T> = { [P in K]?: T[P]; } & { [p in Exclude<keyof T,K>]: T[p] } // 测试用例 type SomeOptional = SetOptional<Foo, 'a' | 'b' >;

// type SomeOptional = { // a?: number; // 该属性已变成可选的 // b?: string; // 保持不变 // c: boolean; // }

pdc-cool avatar Dec 09 '21 08:12 pdc-cool

type Foo = { a?: number; b: string; c?: boolean; }

type Simplify<T> = { [P in keyof T]: T[P] }

type SetRequired<T, K extends keyof T> = Simplify<Pick<T, Exclude<keyof T, K>> & Required<Pick<T, K>>>

// 测试用例 type SomeRequired = SetRequired<Foo, 'b' | 'c'>;

pdc-cool avatar Dec 09 '21 09:12 pdc-cool

type SetOptional<T, K extends keyof T> = {
    [P in keyof (Partial<Pick<T, K>> & Pick<T, Exclude<keyof T, K>>)]: T[P]
}

type SetRequired<T, K extends keyof T> = {
    [P in keyof (Required<Pick<T, K>> & Pick<T, Exclude<keyof T, K>>)]: T[P]
}

测试通过

dolphin0618 avatar Dec 10 '21 07:12 dolphin0618

type Foo = {
	a: number;
	b?: string;
	c: boolean;
}

type Simplify<T> = {
  [P in keyof T]: T[P]
}
type SetOptional<T, K extends keyof T> = Partial<Pick<T, K>> & Pick<T, Exclude<keyof T, K>>
type SomeOptional = SetOptional<Foo, 'a' | 'b'>;
type Test = Simplify<SomeOptional>

// Simplify的作用:将交叉类型拍平,看到真实的类型结构(比较上边的SomeOptional和Test可见)
type A = {
  name: string
} & {
  age: number
}
type Test2 = Simplify<A>

waleiwalei avatar Dec 28 '21 05:12 waleiwalei

我的思路是把可选的字段与除可选字段外的剩余的字段各自提出去,最后组合,所以我用到了Omit:


// 3
type SomeOptional<T, P extends keyof T> = Partial<Pick<T, P>> & Omit<T, P>;

type Foo = {
	a: number;
	b?: string;
	c: boolean;
}

// 测试用例
type SomeOptional1 = SomeOptional<Foo, 'a' | 'b'>;

const a: SomeOptional1 = {c: true}

Ljp10086 avatar Dec 30 '21 07:12 Ljp10086

type SetOptional<T,K extends keyof T> = {[P in K] ?: T[P]} & Omit<T,K> type SetRequired<T,K extends keyof T> = { [P in K] -?: T[P] } & Omit<T,K>

type SetOptional<T,K extends keyof T> = Partial<Pick<T,K>> & Omit<T,K> type SetRequired<T,K extends keyof T>= Required<Pick<T,K>> & Omit<T,K>

zouyk avatar Feb 08 '22 09:02 zouyk

先来看看 SetOptional

思路很简单,把指定的属性改为可选,那么新类型人为分成两坨,一坨要改成可选的,另一坨保持原样,然后这两坨用&连接起来。

实现如下

type SetOptional<T, K extends keyof T> = { [P in K]?: T[P] } & Omit<T, K>

junbin123 avatar Feb 27 '22 07:02 junbin123

type A<T, K extends keyof T> = Partial<Pick<T, K>> & Pick<T, Exclude<keyof T, K>>

type SetOptional<T, K extends keyof T> = { [P in keyof A<T, K>]: A<T, K>[P] }

waleiwalei avatar Mar 11 '22 06:03 waleiwalei

type Foo = { a?: number; b: string; c?: boolean; }

// 测试用例 type SomeRequired = SetRequired<Foo, 'b' | 'c'>;

type B<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>> & Required<Pick<T, K>> type SetRequired<T, K extends keyof T> = Tool<B<T, K>>

waleiwalei avatar Mar 11 '22 06:03 waleiwalei

type Foo = {
	a?: number;
	b?: string;
	c: boolean;
}

type Restore<T> = {
  [P in keyof T]: T[P]
}

type SetRequired<T, P extends keyof T> = 
  Restore<Required<Pick<T, P>> & Pick<T, Exclude<keyof T, P>>>

// 测试用例
type SomeRequired = SetRequired<Foo, 'a' | 'b'>;

codeyourwayup avatar Mar 27 '22 05:03 codeyourwayup

type Foo = {
	a?: number;
	b?: string;
	c: boolean;
}

type Restore<T> = {
  [P in keyof T]: T[P]
}

type SetOptional<T, P extends keyof T> = 
  Restore<Partial<Pick<T, P>> & Pick<T, Exclude<keyof T, P>>>

// 测试用例
type SomeOptions = SetOptional<Foo, 'a' | 'b'>;

codeyourwayup avatar Mar 27 '22 05:03 codeyourwayup

type Foo = {
    a: number
    b?: string
    c?: boolean
}


type SetOption<T, K extends keyof T> = {
    [P in K]?: T[P];
} & {
    [p in Exclude<keyof T,K>]: T[p]
}


type abc = SetOption<Foo, 'a' | 'b'>

谁知道为什么我这样写,类型abc中的c是必填的吗

请问大佬知道为什么了吗

yzin-yang avatar Apr 05 '22 17:04 yzin-yang