为什么 null React 组件状态初始化会得到 `never` 类型?

IT技术 reactjs typescript
2021-05-13 16:36:35

null在组件的构造函数之外初始化组件的状态时,状态never在渲染函数中具有类型

但是,在构造函数中初始化状态时,状态具有正确的类型。

根据 StackOverflow 上关于初始化状态的两种方式(在 babeled JS 中)的大多数问题,这两种方法应该是等效的。但是,在 Typescript 中,它们不是。这是错误还是预期的行为?

import * as React from "react";
import * as ReactDOM from "react-dom";

interface Person {
  name: string;
  address: string;
}
interface Props {
  items: Person[];
}
interface State {
  selected: Person | null;
}

class PersonSelector extends React.Component<Props, State> {
  // DOES NOT WORK:
  state = {
    selected: null
  };

  constructor(props: Props) {
    super(props);
    // WORKS:
    // this.state = {
    //   selected: null
    // };
  }

  handleClick = (item: Person) => {
    this.setState({
      selected: item
    });
  };

  render() {
    const { selected } = this.state;
    let selectedLabel = <div>None selected</div>;
    if (selected) {
      selectedLabel = <div>You selected {selected.name}</div>;
    }
    return (
      <div>
        {selectedLabel}
        <hr />
        {this.props.items.map(item => (
          <div onClick={() => this.handleClick(item)}>{item.name}</div>
        ))}
      </div>
    );
  }
}

const people: Person[] = [
  { name: "asdf", address: "asdf asdf" },
  { name: "asdf2", address: "asdf asdf2" }
];

document.write('<div id="root"></div>');
ReactDOM.render(
  <PersonSelector items={people} />,
  document.getElementById("root")
);

以下是 CodeSandbox 上的示例代码:https ://codesandbox.io/s/10l73o4o9q

2个回答

根据 StackOverflow 上关于初始化状态的两种方式(在 babeled JS 中)的大多数问题,这两种方法应该是等效的。但是,在 Typescript 中,它们不是。

它们在 TypeScript 中是不同的,因为state在类体中赋值(而不是在构造函数中)声明了statein PersonSelector,覆盖了基类中的声明React.Component在 TypeScript 中,覆盖声明允许具有不同的、更严格的类型,与基类中相同属性的类型单向兼容。

在没有类型注解的情况下初始化时,这个类型是由值的类型决定的:

class PersonSelector extends React.Component<Props, State> {
  // DOES NOT WORK:
  state = {
    selected: null
  };

正如预期的那样,您可以看到stateis 的类型{selected: null}它变成never了这段代码

const { selected } = this.state;
let selectedLabel = <div>None selected</div>;
if (selected) {

因为在里面的if语句中,类型selected被缩小了,使用的信息selectedtrueNull 永远不可能为真,因此类型变为never.

正如其他答案中所建议的,您可以State在类主体中初始化时显式注释

class PersonSelector extends React.Component<Props, State> {
  state: State = {
    selected: null
  };

更新以阐明类主体中的初始化与构造函数中的分配有何不同

当您state在构造函数中设置

  constructor(props: Props) {
    super(props);
    this.state = {
       selected: null
    };
  }

您正在将值分配给state在基类中声明时已经存在的属性。基类是React.Component<Props, State>,并且state那里的属性被声明为具有State类型,取自 中的第二个泛型参数<Props, State>

赋值不会改变属性的类型——它仍然是State,不管赋值是什么。

当您state在类主体中进行设置时,它不仅仅是赋值——它是类属性的声明,并且每个声明都为声明的实体提供了一个类型——或者通过类型注释显式地,或者从初始值隐式地推断出来。即使该属性已经存在于基类中,也会发生这种类型化。我在文档中找不到任何可以证实这一点的内容,但是有一个github 问题准确描述了这种行为,并确认有时它违背了开发人员的意图(到目前为止没有在该语言中实现的解决方案)。

您绝对可以在构造函数之外初始化状态,但是您需要确保键入它,以便 TypeScript 可以协调初始值的类型和泛型的类型。尝试:

class PersonSelector extends React.Component<Props, State> {
    state: State = {
        selected: null
    };

    // ...
}