lodash 的类型安全版本 _.get - 有条件地解构数组类型
Type safe version of lodash _.get - deconstruct array type conditionally
很长一段时间以来,我们一直遇到一个问题,即安全、轻松地访问嵌套 属性 的唯一方法是使用 _.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
这实际上在调用站点有效(那里没有错误,正确地给了我们参数和 return 类型),但在声明本身中给了我们一个错误,因为你不能索引 TObject[P1]
和 [number]
因为我们不能保证 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] }
,我们基本上将 TObj
的每个 属性 联合到 undefined[]
。因为我们确定每个 属性 要么是一个数组,要么是 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
库中生成了数百种数组和对象属性的组合并可以使用,但是我仍然愿意以通用的方式改进它,以便我们可以使用同一声明中参数的动态数量。甚至是一种将数组和 属性 导航组合到同一类型约束中的方法,这样我们就不必生成数组和 属性 访问的每个变体。
很长一段时间以来,我们一直遇到一个问题,即安全、轻松地访问嵌套 属性 的唯一方法是使用 _.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
这实际上在调用站点有效(那里没有错误,正确地给了我们参数和 return 类型),但在声明本身中给了我们一个错误,因为你不能索引 TObject[P1]
和 [number]
因为我们不能保证 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] }
,我们基本上将 TObj
的每个 属性 联合到 undefined[]
。因为我们确定每个 属性 要么是一个数组,要么是 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
库中生成了数百种数组和对象属性的组合并可以使用,但是我仍然愿意以通用的方式改进它,以便我们可以使用同一声明中参数的动态数量。甚至是一种将数组和 属性 导航组合到同一类型约束中的方法,这样我们就不必生成数组和 属性 访问的每个变体。