在 Typescript 中处理嵌套的可能为空的值

Posted

技术标签:

【中文标题】在 Typescript 中处理嵌套的可能为空的值【英文标题】:Dealing with nested possibly null values in Typescript 【发布时间】:2019-02-17 21:52:03 【问题描述】:

我正在使用 GraphQL 和自动生成的类型来使用 Typescript 进行查询。它倾向于创建很多嵌套的type | null 类型。所以我需要对非空值进行相当长的检查。例如

            data &&
            data.getCoach &&
            data.getCoach.workouts &&
            data.getCoach.workouts.items &&
            data.getCoach.workouts.items.map((workout) => /* do something*/);

我尝试使用 lodash has 助手来检查路径是否存在,例如

has('getCoach.workouts.items', data) &&
data.getCoach.workouts.items.map((workout) => /* do something*/);

但 ts 编译器似乎不理解这一点。

除了让我的 GraphQL 端点始终返回值之外,还有更好的检查方法吗?

【问题讨论】:

你试过“npm install @types/lodash”吗? ts 编译器到底在抱怨什么? 是的,包括在内。错误是value is possibly null 快速查看lodash/has 的类型定义只是接受路径和对象并返回布尔值。这可能不足以让编译器理解它在做什么。 如果您使用(data as any).getCoach.workouts.items.map(…),它会起作用,对吧? 如果我禁用严格的空检查,它也会起作用。但我想在这里保持类型安全。如果值实际上是null,则执行(data as any).getCoach.workouts.items.map(…) 会在运行时爆炸 【参考方案1】:

TypeScript 的功能还不够强大,无法代表您正在尝试做的事情。理想情况下,javascript 和 TypeScript 将具有 null-coalescing operator 并且您会使用它。但它没有。

如果你尝试强输入lodashhas方法,你会发现编译器不能concatenate string literal types或者执行regular expression operations on them,所以编译器没有办法获取字符串'getCoach.workouts.items'并理解getCoach 将是data 的键,workouts 将是data.getCoach 的键,等等。

即使不使用has,要表示此处涉及的那种嵌套类型操作而不遇到recursion 导致编译器生气或崩溃的问题也是很棘手的。

我能做的最好的事情是:将对象包装在 Proxy(需要 ES2015 或更高版本)中,它始终在您要使用的任何键处具有值,并且您使用特殊的键名(例如 "value" ) 以提取该属性的实际值,如果没有,则为undefined。这是实现:

type WrapProperties<T, K extends keyof any> = Record<K, T> &  [P in keyof T]-?: WrapProperties<T[P], K> 

function wrapProperties<T>(val: T): WrapProperties<T, "value">;
function wrapProperties<T, K extends keyof any>(val: T, valueProp: K): WrapProperties<T, K>;
function wrapProperties(val: any, valueProp: keyof any = "value"): WrapProperties<any, any> 
    return new Proxy(, 
        get: (_, p) => p === valueProp ? val : wrapProperties(
            (typeof val === 'undefined') || (val === null) ? val : val[p], valueProp
        )
    );

所以现在,而不是这个:

if (data && data.getCoach && data.getCoach.workouts && data.getCoach.workouts.items) 
  data.getCoach.workouts.items.map((workout) => /* do something*/) 

你应该可以这样做:

const wrappedData = wrapProperties(data, "val");    
if (wrappedData.getCoach.workouts.items.value) 
  wrappedData.getCoach.workouts.items.value.map((workout) => /* do something*/) 

不知道实现是否完美;当我尝试时,它似乎有效。与您从 Stack Overflow 获得的任何东西一样,您的里程可能会有所不同,并且需要购买者注意。也许它会帮助你。祝你好运!

【讨论】:

我会接受这个作为答案。我猜只需要等待功能被引入到 TS 中。会尝试你的解决方案,但这有点超出我的想象......【参考方案2】:

编辑:

可选链接已released in typescript 3.7!

您现在可以这样做:

const items = data?.getCoach?.workouts?.items

items && items.map((v) => 
    console.log(v)
)

上一个答案:

只是想我会提到optional chaining 已进入 TC39 的第 2 阶段!

我认为目前没有任何巧妙的解决方案,所以像 jcalz 的回答这样的事情现在可能不得不做。

希望 TS 早点引入它,就像他们对 async/await 所做的一样 ?我可以更新这个答案。

【讨论】:

空值合并是?? ?. 实际上称为可选链。 好地方,刚刚更新

以上是关于在 Typescript 中处理嵌套的可能为空的值的主要内容,如果未能解决你的问题,请参考以下文章

未处理的拒绝(错误):期望不可为空的变量的值:类型的时间:时间!在变量值中

Math.Max 处理三个可为空的值

有没有一种速记方法来返回可能为空的值?

Typescript 映射类型,其中仅保留可为空的属性并转换为字符串类型

在 Typescript 中将可为空的对象值转换为字符串

Rails:查找嵌套关联为空的记录