在 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 中处理嵌套的可能为空的值的主要内容,如果未能解决你的问题,请参考以下文章
未处理的拒绝(错误):期望不可为空的变量的值:类型的时间:时间!在变量值中