有没有办法在对象 属性 解构中利用无效合并运算符 (`??`)?

Is there a way to utilize the nullish coalescing operator (`??`) in object property destructuring?

在 ReactJS 中,我通常使用这种 destructurnig props 模式(我想这很地道):

export default function Example({ ExampleProps }) {
  const {
    content,
    title,
    date,
    featuredImage,
    author,
    tags,
  } = ExampleProps || {};

我可以在解构时添加默认值,这增加了一些安全性:

export default function Example({ ExampleProps }) {
  const {
    content = "",
    title = "Missing Title",
    date = "",
    featuredImage = {},
    author = {},
    tags = [],
  } = ExampleProps || {};

但现在我切换到 TypeScript strict 模式,我很难过。我的道具由 GraphQl codegen 键入,几乎所有属性都包装在 Maybe<T> 类型中,所以当展开时,有像 actualValue | null | undefined.

如果值为 undefined,默认值 ({ maybeUndefined = ""} = props) 可以拯救我,但 null 值会落空 ,所以TS 编译器很烦人,我的代码导致了很多:

tags?.nodes?.length // etc…

这让我有点紧张,因为 The Costs of Optional Chaining 文章(尽管我不知道它在 2021 年的相关性如何)。我还听说 ?. 运算符过度使用被称为“代码味道”的一个例子。

是否有一种模式(可能利用 ?? 运算符)可以使 TS 编译器满意并且至少可以清除其中的一些 very?.long?.optional?.chains

我看到两个可能的选择:

  1. 执行无效合并 属性-by-属性,或

  2. 使用效用函数

属性 来自 属性

相当乏味(我是一个乏味的开发者):

// Default `ExampleProps` here −−−−−−−−−−−−−−−vvvvv
export default function Example({ ExampleProps = {} }) {
    // Then do the nullish coalescing per-item
    const content = ExampleProps.content ?? "";
    const title = ExampleProps.title ?? "Missing Title";
    const date = ExampleProps.date ?? "";
    const featuredImage = ExampleProps.featuredImage ?? {},
    const author = ExampleProps.author ?? {},
    const tags = ExampleProps.tags ?? [];
    // ...

效用函数

或者,沿着这些行使用实用函数将 null 值(编译时和运行时)转换为 undefined,因此您可以在解构结果时使用解构默认值。类型部分相当简单:

type NullToUndefined<Type> = {
    [key in keyof Type]: Exclude<Type[key], null>;
}

那么效用函数可能是这样的:

function nullToUndefined<
    SourceType extends object,
    ResultType = NullToUndefined<SourceType>
>(object: SourceType) {
    return Object.fromEntries(
        Object.entries(object).map(([key, value]) => [key, value ?? undefined])
    ) as ResultType;
}

或像这样(在运行时方面可能更有效):

function nullToUndefined<
    SourceType extends object,
    ResultType = NullToUndefined<SourceType>
>(object: SourceType) {
    const source = object as {[key: string]: any};
    const result: {[key: string]: any} = {};
    for (const key in object) {
        if (Object.hasOwn(object, key)) {
            result[key] = source[key] ?? undefined;
        }
    }
    return result as ResultType;
}

请注意 Object.hasOwn 非常 新,但很容易填充。或者您可以改用 Object.prototype.hasOwn.call(object, key)

(在这两种情况下 within nullToUndefined 我在类型断言方面玩得有点快和松散。对于像这样的小实用函数,我认为这是一个合理的妥协,前提是输入和输出定义明确。)

然后:

export default function Example({ ExampleProps }) {
    const {
        content = "",
        title = "Missing Title",
        date = "",
        featuredImage = {},
        author = {},
        tags = [],
    } = nullToUndefined(ExampleProps || {});
    //  ^^^^^^^^^^^^^^^^−−−−−−−−−−−−−−−−−−^
    // ...

Playground link