首页 > 解决方案 > NextJS Typescript - 保护后对象可能未定义?

问题描述

好吧,我被难住了。我有一个简单的 React 组件(刚开始)。我需要获取数据,所以我正在等待一些异步数据。这是我现在的解决方案:

import { Component } from "react";
import { Bible } from "./models";

interface State {
  bible?: Bible;
}

export class RevApp extends Component<{}, State> {
  state = {
    bible: undefined,
  };

  componentDidMount() {
    Bible.onReady().then((bible) => {
      this.setState({
        bible,
      });
    });
  }

  render() {
    const { bible } = this.state;
    //return <div>{bible ? bible.ls() : "Loading Bible..."}</div>;
    if (bible) {
      return <div>{bible.ls()}</div>;
    } else {
      return <div>Loading Bible...</div>;
    }
  }
}

出于某种原因,我得到了bible.ls()(特别是圣经对象)编译器错误“对象可能未定义”。这没有任何意义!我试过放一个!(即bible!.ls(),但后来我收到一个投诉,“'never' 类型不存在属性 'ls'”。

真正令人头疼的是,这个错误只出现在我的 linter 中,而不是我的编译器中。这一切都很好,除非当我推送到生产环境时,我的代码需要在编译之前通过 linter。

谁能告诉我为什么我会出现这种行为?

这是我的 tsconfig,以防它与问题有关(由 nextjs 生成):

{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "baseUrl": "."
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
  "exclude": ["node_modules"]
}

打字稿版本“4.3.5”

节点版本“14.17.3”

标签: reactjstypescriptnext.jsreact-tsx

解决方案


无论好坏,当你有class一个extends超类时,子类的重写属性不会从超类继承它们的类型。相反,它们完全从初始化程序中获取类型,就好像超类不存在一样。因此,子类属性很容易成为比超类更窄的类型。请参阅microsoft/TypeScript#10570了解有关此问题的讨论,它如何使人们感到困惑,以及他们如何在这里没有找到更好的办法。

这意味着 的state属性RevApp 不继承的类型Component<{}, State>['state']。相反,它是从{bible: undefined}初始化程序中推断出来的,这会导致 type {bible: undefined}。所以bible属性的state属性RevApp永远是永远undefined

export class RevApp extends Component<{}, State> {
  state = {
    bible: undefined,
  };
  /* RevApp.state: { bible: undefined;} <-- oops */
}

所以你不可能undefined从类型中消除state类型保护(嗯,你可以,也许编译器会缩小到never,但这也不是你想要的);它保持undefined

const { bible } = this.state;
// const bible: undefined
if (bible) {
  bible // <-- still undefined
}

想必你实际上想要statebible财产也许不是undefined。如果是这样,获得此行为的唯一方法是将其显式注释为您想要的类型:

export class RevApp extends Component<{}, State> { state: State = { // 带注释的圣经:undefined, }; }

现在类型保护按需要工作了;的类型bibleBible | undefined检查之前,只要Bible它是真实的:

render() {
  const { bible } = this.state;
  return <div>{bible ? bible.ls() : "Loading Bible..."}</div>; // okay
}

Playground 代码链接


推荐阅读