TS Playground(阅读原文,链接跳转)
/**
*
* 导出了一个 defineConfig 的函数给开发者,其中有 a 和 b 字段是二选一的, foo 是可选的
* 完成 UserConfig
*
*/
interface UserConfig {}
function defineConfig(t: UserConfig) {
//
}
defineConfig({ a: true })
defineConfig({ b: true })
defineConfig({ a: true, b: true })
defineConfig({ a: true, c: true })
发散一下: 支持自定义互斥字段, 以及多组互斥字段
体操的最终目的是为了用户写ts的时候像js一样, 使用的时候尽量少感知类型的存在。
一个简单例子, 在使用的时候完全不需要写类型
interface TreeNode<T> {
value: T;
left?: TreeNode<T>;
right?: TreeNode<T>;
}
function toList<T>(root: TreeNode<T>) {
const list: T[] = [];
function dfs(node: TreeNode<T>) {
if (!node) {
return;
}
if (node.left) {
dfs(node.left);
}
if (node.right) {
dfs(node.right);
}
list.push(node.value);
}
dfs(root);
return list;
}
declare function createTree<T>(list: T[]): TreeNode<T>;
const numNode = createTree([1]);
const strNode = createTree(["1"]);
const numList = toList(numNode);
const strList = toList(strNode);
对于这种多字段互斥的场景其实是违反三范式的, 类型是对数据的抽象, 数据最后在保存的时候基本上都是一个二维表, 如果两个字段互斥, 说明该表有冗余, 应该合并. 否则只能用ts自己实现类似One-hot编码的东西来解决。
https://segmentfault.com/a/1190000013695030
简单场景推荐重载, 直观, 错误提示友好, 且可以避免undefined的问题
/**
*
* 导出了一个 defineConfig 的函数给开发者,其中有 a 和 b 字段是二选一的, foo 是可选的
* 完成 UserConfig
*
*/
interface CommonConfig {
foo?: string;
}
interface JsConfig extends CommonConfig {
a: boolean;
b?: never;
}
interface TsConfig extends CommonConfig {
b: boolean;
a?: never;
}
type UserConfig = JsConfig | TsConfig
function defineConfig(t: UserConfig) {
//
}
// ok
defineConfig({ a: true })
defineConfig({ a: true, foo: 'aa' })
defineConfig({ b: true })
// error
defineConfig({ a: true, b: true })
defineConfig({ a: true, c: true })
/**
*
* 导出了一个 defineConfig 的函数给开发者,其中有 a 和 b 字段是二选一的, foo 是可选的
* 完成 UserConfig
*
*/
interface CommonConfig {
foo?: string;
}
interface JsConfig extends CommonConfig {
a: boolean;
}
interface TsConfig extends CommonConfig {
b: boolean;
}
type UserConfig = JsConfig | TsConfig
function defineConfig(c: JsConfig): void;
function defineConfig(c: TsConfig): void;
function defineConfig(t: UserConfig) {
//
}
// ok
defineConfig({ a: true })
defineConfig({ a: true, foo: 'aa' })
defineConfig({ b: true })
// error
defineConfig({ a: true, b: true })
defineConfig({ a: true, c: true })
将类型的key划分为两组, 一组使用原始值, 一组使用never, 然后用&拼装为一个新类型
/**
*
* 导出了一个 defineConfig 的函数给开发者,其中有 a 和 b 字段是二选一的, foo 是可选的
* 完成 UserConfig
*
*/
type UserConfig = {
foo?:string,
a:boolean,
b:boolean,
c: boolean;
}
{a:boolean} & {foo?: string} & {b:never, c : never}
{b:boolean} & {foo?: string};
{c:boolean} & {foo?: string};
type SetKeyNever<T, K extends keyof T> = {
[x in K]?: never;
};
// type z = SetKeyNever<UserConfig, 'a' | 'b'>
type z = Pick<UserConfig, 'a' | 'b'>
type x = Exclude<'a' | 'v', 'a'>
type JustOne<T, K extends (keyof T)[] = [], Y extends keyof T = K[number]> = {
[x in Y]: Pick<T, Exclude<keyof T, Exclude<Y, x>>> &
SetKeyNever<T, Exclude<Y, x>>;
}[Y];
type n = ['a', 'b','c'][number]
function defineConfig(t: JustOne<UserConfig, ['a', 'b','c']>) {
//
}
// ok
defineConfig({ a: true })
defineConfig({ a: true, foo: 'aa' })
defineConfig({ b: true })
// error
defineConfig({ a: true, b: undefined })
defineConfig({ a: true, c: true })
type z1 = JustOne<UserConfig, ['a', 'b','c']>
type MergeIntersection<T> = T extends object ? {
[K in keyof T]: T[K] extends object ? MergeIntersection<T[K]> : T[K]
} : T
// 可视化
type p = MergeIntersection<z1>
/**
*
* 导出了一个 defineConfig 的函数给开发者,其中有 a 和 b 字段是二选一的, foo 是可选的
* 完成 UserConfig
*
*/
// type XOR<T, A, B> = T extends A ? A : B;
// type foo = { [key: string]: never, a: boolean, };
type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };
type XOR<T, U> = (T | U) extends object ? (Without<T, U> & U) | (Without<U, T> & T) : T | U;
type UserConfig = XOR<{ a: boolean}, { b: boolean }> & { foo?: string }
function defineConfig(t: UserConfig) {
//
}
// ok
defineConfig({ a: true })
defineConfig({ a: true, foo: 'aa' })
defineConfig({ b: true })
// error
defineConfig({ a: true, b: true })
defineConfig({ a: true, c: true })
// type MergeIntersection<T> = T extends object ? {
// [K in keyof T]: T[K] extends object ? MergeIntersection<T[K]> : T[K]
// } : T
declare function f1(c: { a: boolean }): void;
declare function f2(c: { a: boolean } | { c: boolean }): void;
// error
f1({ a: true, c: true });
// ok
f2({ a: true, c: true });
type A = { a: boolean };
// error
const y: A = { a: true, b: true };
const z = { a: true, b: true } as const;
// ok
const x: A = z;
非常适合做笛卡尔积
type EventName = "click" | "mousedown" | "mouseup";
type HandleType = "on" | "off";
type EventFn = `${HandleType}${Capitalize<EventName>}`;
https://blog.gplane.win/posts/conditional-type-in-ts.html
type Option = {
token?: boolean;
};
declare function f<T extends Option>(
c?: T
): T["token"] extends true ? { token: number[] } : string;
f({ token: true }).token.sort();
f({ token: false }).token.sort();
f().token.sort();
https://juejin.cn/post/7025851349103280164
https://www.zhihu.com/question/47452733
interface A {
a: { a: number };
b: { b: string };
c: { c: boolean };
}
type AddKind<A> = {
[K in keyof A]: A[K] & { kind: K };
};
type Intermediate = AddKind<A>;
type IntermediateWithKind = Intermediate[keyof Intermediate]
https://zhuanlan.zhihu.com/p/58704376
- END -