开发者问题收集

Typescript 错误地假设属性是只读的

2017-06-22
12145

我有一个使用 typescript 创建的简单 react 组件,遇到了以下奇怪的错误。 这是我的代码。

interface State {
  value: string
}

class App extends React.Component<{}, State> {
  constructor() {
    super();
    this.state = {
      value: ''
    }
  }

  changeHandler = (e: any) => {
    let state = Object.assign({}, this.state);
    state.value = e.target.value;
    this.setState(state);
  }

  render() {
    return (
      <div className="App">
        <input
          type="text"
          value={this.state.value}
          name="value"
          onChange={this.changeHandler} />
      </div>
    );
  }
}

export default App;

这是我得到的错误。

error TS2540: Cannot assign to 'value' because it is a constant or a read-only property.

这个错误让我想到,也许这是 typescript 强制执行不改变状态规则的方式。 为了测试这个理论,我做了以下事情。

this.state.value = e.target.value

在这种情况下,我显然直接改变了状态,果然我得到了同样的错误。

然后我想到将我的界面改为这样。

interface State {
  value: Ivalue
}

interface Ivalue {
  value: string;
}

然后我重构了我的组件以像这样使用这个新接口。

class App extends React.Component<{}, State> {
  constructor() {
    super();
    this.state = {
      value: {
        value: ''
      }
    }
  }

  changeHandler = (e: any) => {
    let value = Object.assign({}, this.state.value);
    value.value = e.target.value;
    this.setState({value});
  }

  render() {
    return (
      <div className="App">
        <input
          type="text"
          value={this.state.value.value}
          name="value"
          onChange={this.changeHandler} />
      </div>
    );
  }
}

export default App;

果然这样编译了! 我的问题实际上是 2 个问题。首先,为什么 TypeScript 对我的状态副本不满意,就像我在第一段代码中使用 Object.assign 一样? 其次,为什么将状态对象进一步嵌套一层可以解决这个问题?

3个回答

如果 Object.assign 的内部实现(或 TypeScript 对此的理解)是复制对象成员的描述符,而不仅仅是字段名称,那么它将复制 state.valuereadonly 属性,而不仅仅是 state.value 的键名。

我不能保证这确实是正在发生的事情,因为这对我来说是个新闻。但这实际上是个好消息,而不是坏消息。

您使用的是哪些版本?

此外,如果您要在下一个主要版本中继续使用 setState 的函数形式,您需要养成使用它的习惯。

LetterEh
2017-06-22

您可以重写为:

changeHandler = (e: any) => {
    this.setState({value: e.target.value});
}

或者使用 desctructuring assigment

changeHandler = ({target: {value}}: any) => {
    this.setState({value});
}

在 Typescript 中,您可以将属性标记为只读。这是更好的方法,因为直接的不可变状态不是好的模式。您必须运行 setState() 函数才能更改状态。

您的 props 和 state 可能如下所示:

interface State {
   readonly value: string;
}

我用“readonly”标记类型中的所有属性(对于 props、state、redux 等)。我希望所有属性都采用不可变模式 :)

Aleš Dostál
2017-06-22

如果你查看 React 组件的类型定义,你会看到 state 属性定义为 Readonly<S> ,其中 S 是状态类型参数。

https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react/index.d.ts#L209

Schmuli
2017-06-29