「重学TS 2.0 」TS 练习题第三题
第三题
在 掌握 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; // 该属性已变成必填
// }
请在下面评论你的答案
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;
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">;
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; // 该属性已变成必填
// }
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>>;
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'>;
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>>
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是必填的吗
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'>>
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;
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'>;
本质上要自己实现 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'>;
分析:题目是,将参数中的属性变为可选,其他不变,我们分析得出结论
- 首先获取到要变为可选的参数,我们用 pick 获取到参数中的属性,然后用Partial 将他们全部变为可选。
- 使用 Pick 和 Exclude 将不变化的参数拿出来.
- 将两个结果交叉类型,然后扁平处理
//获取处理完的交叉类型
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>>>
type SetOptional<T, K extends keyof T > = Omit<T, K> & { [k in K]?: T[k] } 我这样对吗
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 来把他变成一个简单结构类型。
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 唯一的区别就是 -? 和 ? 的区别。
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的选项类型
直接用现有的工具类型可以吗?
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; // }
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>
;
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; // }
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 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]
}
测试通过
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>
我的思路是把可选的字段与除可选字段外的剩余的字段各自提出去,最后组合,所以我用到了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}
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>
先来看看 SetOptional
思路很简单,把指定的属性改为可选,那么新类型人为分成两坨,一坨要改成可选的,另一坨保持原样,然后这两坨用&连接起来。
实现如下
type SetOptional<T, K extends keyof T> = { [P in K]?: T[P] } & Omit<T, K>
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] }
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>>
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'>;
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'>;
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是必填的吗
请问大佬知道为什么了吗