类型安全版本的lodash_.get-有条件地解构数组类型



长期以来,我们一直存在一个问题,即安全、轻松地访问嵌套属性的唯一方法是使用_.get。例如:

_.get(obj, "Some.Nested[2].Property", defaultValue);

这非常有效,但不能像经常发生的那样经得起属性重命名的考验。从理论上讲,应该可以将上述内容转换为以下内容,并允许TypeScript隐式键入检查:

safeGet(obj, "Some", "Nested", 2, "Property", defaultValue);

我已经成功地为除了数组类型之外的所有类型创建了这样一个类型:

function getSafe<TObject, P1 extends keyof TObject>(obj: TObject, p1: P1): TObject[P1];
function getSafe<TObject, P1 extends keyof TObject, P2 extends keyof TObject[P1]>(obj: TObject, p1: P1, p2: P2): TObject[P1][P2];

这可以正确地检查深度项目(我将自动生成10个级别左右的这些语句)。它在数组属性方面失败,因为传递到下一个参数的类型是T[]而不是T

任何解决方案的复杂性或冗长性都不需要考虑,因为代码将自动生成,问题是我似乎找不到任何类型声明的组合,这将允许我接受整数参数并解构数组类型。

可以使用T[number]解构数组(其中T是数组)。问题是,我无法约束T是嵌套属性上的数组的位置。

function getSafe<TObject, P1 extends keyof TObject, P2 extends keyof TObject[P1][number]>(obj: TObject, p1: P1, index: number, p2: P2): TObject[P1][number][P2];
           ^^^^^^                                                             ^^^^^^
const test2 = getSafe(obj, "Employment", 0, "Id"); // example usage

这实际上在调用站点上有效(没有错误,正确地给了我们参数和返回类型),但在声明本身中给了我们一个错误,因为您不能用[number]索引TObject[P1],因为我们不能保证TObject[P1]是一个数组。

(注意:TType[number]是从数组类型中获取元素类型的可行方法,但我们需要说服编译器我们正在对数组执行此操作)

实际上,问题是,是否可以向TObject[P1]添加数组约束,是否还有我缺少的其他方法。

我已经弄清楚了这一点,并在这里发布了一个npm包:ts-get-safe

关键是要弄清楚如何有条件地将数组重组为其元素类型。要做到这一点,首先必须断言所有属性都是数组或never。求解方程的类型是:

type GSArrEl<TKeys extends keyof TObj, TObj> = { [P in TKeys]: undefined[] & TObj[P] }[TKeys][number];

神奇的是在{ [P in TKeys]: undefined[] & TObj[P] }中,我们本质上联合了TObjundefined[]的每个属性。因为我们确信每个属性都是数组或never(对于不是数组的每个属性,它将是never),所以我们可以执行析构函数表达式[number]来获得元素类型。

以下是同时发生两个数组破坏的示例:

function getSafe<TObject, P0 extends keyof TObject, A1 extends GSArrEl<P0, TObject>, P2 extends keyof A1, P3 extends keyof A1[P2], A4 extends GSArrEl<P3, A1[P2]>>(obj: TObject, p0: P0, a1: number, p2: P2, p3: P3, a4: number): A4;

在我的ts-get-safe库中已经生成了数百个数组和对象属性的组合,并且已经可以使用了,但是我仍然对以通用方式改进这一点持开放态度,这样我们就可以在同一声明中使用动态数量的参数。甚至还有一种将数组和属性导航组合到同一类型约束中的方法,这样我们就不必生成数组和属性访问的每一种变体。

最新更新