ice*_*itz 3 typescript mapped-types variadic-tuple-types
我正在尝试创建一个辅助函数,它采用 JSON 等嵌套对象,并允许在任意深度制作嵌套值的深层副本。我了解可变元组类型,并且可以让它们仅用于传递元组 - 但我不知道如何将它们“映射”到任意深度的嵌套选择(甚至可能不可能)。这是我想出的最好的方法 - 但仍然仅限于需要为 GetNestedValue 创建尽可能多的重载,因为我愿意支持。我理解各种错误,我只是想不出任何方法来满足编译器并在返回值上获得类型完成。
// K is arbitrary length how to express N accessors deep? in TS without a loop?
type GetNestedValue<K extends string[], O extends any> = O[K[0]][K[1]][K[2]];
function getNestedItem<Keys extends string[], Obj>(
obj: Obj, ...keys: readonly [...Keys]
): GetNestedValue<Keys, Obj> extends undefined ? undefined : GetNestedValue<Keys, Obj> {
let level: any = obj;
for (const key of keys) {
if (level !== undefined) {
level = level[key];
} else {
return;
}
}
// this will return deepClone(level);
return level;
}
const obj = {one: 1, two: {three: {four: 4}}};
// I'd prefer 'known' shapes of obj here block form entering invalid keys.
const a = getNestedItem(obj, 'one', 'two');
// here - when arbitrarily trying to grab stuff from unknown inputs - I don't want
// a warning, rather the user would just need to check `if (b !== undefined)`
const b = getNestedItem(obj as any, 'one', 'two');
Run Code Online (Sandbox Code Playgroud)
链接到游乐场
我首先会说:虽然这是一个有趣的思想实验,但由于它需要大量的递归,我不会推荐它。
它需要两种递归类型,一种是获取从对象类型推断出的有效键集的类型,另一种是 getter 来访问给定这些经过验证的键的属性。对于 TypeScript < 4.5,深度限制将为长度为 10 的元组。
验证:
// walk through the keys and validate as we recurse. If we reach an invalid
// key, we return the currently validated set along with a type hint
type ValidatedKeys<K extends readonly PropertyKey[], O, ValidKeys extends readonly PropertyKey[] = []> =
K extends readonly [infer Key, ...infer Rest]
// Excluding undefined to allow `a?.b?.c`
? Key extends keyof Exclude<O, undefined>
? Rest extends []
? [...ValidKeys, Key] // case: nothing left in the array, and the last item correctly extended `keyof O`.
: Rest extends readonly PropertyKey[] // obligatory typeguard
? ValidatedKeys<Rest,Exclude<O, undefined>[Key], [...ValidKeys, Key]> // recurse
: never // impossible, we've sufficiently typechecked `Rest`
: [...ValidKeys, keyof Exclude<O, undefined>] // case: key doesn't exist on object at this level, adding `keyof O` will give a good type hint
: [...ValidKeys] // case: empty top level array. This gives a good typehint for a single incorrect string;
Run Code Online (Sandbox Code Playgroud)
吸气剂:
// access a property recursively. Utilizes the fact that `T | never` === `T`
type GetNestedProp<K extends readonly PropertyKey[], O, MaybeUndef extends undefined = never> =
K extends readonly [infer Key, ...infer Rest]
? Key extends keyof O
? Rest extends []
? O[Key] | MaybeUndef // succesful exit, no more keys remaining in array. Union with undefined if needed
/* obligatory typeguard to validate the inferred `Rest` for recursion */
: Rest extends readonly PropertyKey[]
// If it's potentially undefined, We're going to recurse excluding the undefined, and then unify it with an undefined
? O[Key] extends infer Prop
? Prop extends undefined
? GetNestedProp<Rest, Exclude<Prop, undefined>, undefined>
: GetNestedProp<Rest,Prop, MaybeUndef>
: never // impossible, `infer Prop` has no constraint so will always succeed
:never // impossible, we've typechecked `Rest` sufficiently
: undefined // case: key doesn't exist on object at this level
: undefined; // case: empty top level array
Run Code Online (Sandbox Code Playgroud)
为了使函数正确推断泛型,该泛型需要作为可能的参数出现。我们想要的是,但如果没有它本身作为潜在的论点,ValidKeys我们就无法做到这一点。Keys因此,我们使用...keys参数的条件来强制其解决。
关于返回类型,即使可能GetNestedProp是与 的联合undefined,编译器也无法推断出它肯定是在 else 分支被命中的情况下。因此,您可以将返回类型设置为这种尴尬的条件,或者//@ts-expect-error使用更简单的返回类型 的 else 分支返回语句GetNestedProp<Keys, Obj>。该替代方案包含在游乐场中:
function getNestedItem<Obj, Keys extends readonly [keyof Obj, ...PropertyKey[]], ValidKeys extends ValidatedKeys<Keys, Obj>>(
obj: Obj,
...keys: ValidKeys extends Keys ? Keys : ValidKeys
): GetNestedProp<Keys, Obj> extends undefined ? GetNestedProp<Keys, Obj> | undefined : GetNestedProp<Keys,Obj> {
let level: any = obj;
for (const key of keys) {
if (level !== undefined) {
level = level[key];
} else {
return;
}
}
return level;
}
Run Code Online (Sandbox Code Playgroud)
给定一个具有可选属性的类型,深入研究该属性会将嵌套属性类型转换为未定义的联合:
interface HasOpt {
a: { b: number };
aOpt?: {b: number };
}
declare const obj: HasOpt;
const ab = getNestedItem(obj, "a", "b") // number
const abOpt = getNestedItem(obj, "aOpt", "b") // number | undefined
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
636 次 |
| 最近记录: |