如何获得对象的形状,然后修改每个叶子属性的类型



例如,以以下接口为例:

interface SomeObject {
prop1: number;
prop2: string;
prop3: {
innerProp1: number[];
innerProp2: string[];
innerProp3: {
deeperProp1: string[];
deeperprop2: boolean;
},
innerProp4: {
[key: string]: any;
},
innerProp5: {
[key: string]: any;
}
}
}

我想创建一个接受任何对象形状的类型,然后返回相同的对象形状,但使用为"叶子"属性提供的类型,对象的每个属性都可以是可选的。类似以下内容:

type ModifyShapeType<Shape, NewType> = ???

例如,当与上面的界面进行使用时,我会获得相同对象形状的类型安全性,但提供的类型是:

const myObject: ModifyShapeType<SomeObject, boolean> = {
prop1: true;
prop2: true;
prop3: {
// innerProp1: true;
// innerProp2: false;
innerProp3: {
deeperProp1: true;
// deeperprop2: true;
},
innerProp4: true,
// innerProp5: false
}
};

我已经想出了下面的一个,但我想从形状中去掉原来的类型,用我想要的来代替它,如果可能的话,仍然保留属性读写的特殊性。

type ModifyShapeType<S, T> = Partial<Record<keyof S, Partial<S[keyof S] | T>>>;

这是一个TypeScript游戏场。

当前注意事项:

  1. 类型仍然是从原始对象类型推断出来的,事实上,它现在都是混合的
  2. 所有属性现在共享相同的类型(读丢失时的特殊性(,这也意味着不安全的写入(写丢失时的特定性(

这可能吗?

听起来您需要一个映射的递归类型。

要创建它,您需要遍历每个键,看看它是一个对象(分支(,还是其他值(叶(。如果是分支,则递归。如果是叶,则输出所需的值类型。决定定义叶的内容有点棘手,而且是特定于应用程序的(因为javascript中的每个值都有属性,而且可能有点像对象(。

因此,您将需要一个条件分支检测器类型和一个递归映射类型。

// Returns T if the T is a branch. Otherwise it returns `never`.
type IsBranch<T> = 
// Is T an object?
T extends { [k: string]: any }
// T is an object. Is it also an array?
? T extends any[]
// T is an object, but also is an array. This is a leaf.
? never
// T is an object, but is not also an array. This is a branch.
: T
// T is not an object. This is a leaf.
: never
// Recursively process each key.
// If it is a branch, process its keys and return the Partial of that branch.
// If it is a leaf, replace with the value type.
type ModifyShapeType<S, T> = S extends IsBranch<S> ?
Partial<{ [k in keyof S]: ModifyShapeType<S[k], T> }> :
T
const a: ModifyShapeType<{ a: number }, boolean> = { a: true }
const b: ModifyShapeType<{ a: number[] }, boolean> = { a: true }
const c: ModifyShapeType<{ a: { b: number } }, boolean> = { a: { b: true } }

游乐场

一个棘手的问题是数组类型看起来像数组,所以我们需要一个特殊的情况。

另一件棘手的事情是试图推导{ a: number }{ [k: string]: number }之间的差异。你似乎想把前者当作一根树枝,但把后者当作一片叶子。我不确定有没有办法做到这一点。可能需要对T[string]进行条件测试,看看它是否可以索引,但我还没有完全弄清楚。

与RecursivePartial非常相似:递归部分<T>在TypeScript 中

这应该考虑到数组子类型,并且不使用any:

/**
* Replaces leaf nodes of T with V
*/
export type RecursiveReplace<T,V> = {
[P in keyof T]: T[P] extends (infer U)[]
? RecursiveReplace<U,V>[]
: T[P] extends number | string | symbol | undefined
? V
: RecursiveReplace<T[P],V>;
};

最新更新