如何不在 React 的渲染函数中使用 setState

IT技术 javascript reactjs
2021-05-09 07:31:12

我有一个完整的运行代码,但它有一个缺陷。它从 render() 内部调用 setState()。所以,react 会抛出反模式警告。

Cannot update during an existing state transition (such as within render or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to componentWillMount

我的逻辑是这样的。index.js父组件中,我有如下代码。构造函数()调用具有初始值的图(),以显示图形。用户还有一个表单来指定新值并提交表单。它使用新值再次运行 graphs() 并重新渲染图形。

import React, { Component } from 'react';
import FormComponent from './FormComponent';
import PieGraph from './PieGraph';

const initialval = '8998998998';

class Dist extends Component {
  constructor() {
    this.state = {
      checkData: true,
      theData: ''
    };
    this.graphs(initialval);
  }

  componentWillReceiveProps(nextProps) {
    if (this.props.cost !== nextProps.cost) {
      this.setState({
        checkData: true
      });
    }
  }

  graphs(val) {
    //Calls a redux action creator and goes through the redux process
    this.props.init(val);
  }

  render() {
    if (this.props.cost.length && this.state.checkData) {
      const tmp = this.props.cost;
      //some calculations
      ....
      ....
      this.setState({
        theData: tmp,
        checkData: false
      });
    }

    return (
      <div>
        <FormComponent onGpChange={recData => this.graphs(recData)} />
        <PieGraph theData={this.state.theData} />
      </div>
    );
  }
}

FormComponent 是一个普通的表单,带有输入字段和如下所示的提交按钮。它将回调函数发送到 Parent 组件,从而触发 graphs() 和 componentWillReceiveProps。

handleFormSubmit = (e) => {
    this.props.onGpChange(this.state.value);
    e.preventdefaults();
}

代码一切正常。有没有更好的方法来做到这一点?在 render() 中不做 setState ?

2个回答

永远不要在渲染中设置状态。您不应该这样做的原因是,对于每个 setState,您的组件都会重新渲染,因此在渲染中执行 setState 会导致无限循环,这是不推荐的。

不需要 checkData 布尔变量。您可以在 componentWillReceiveProps 中直接比较以前的成本和当前的成本,如果它们不相等,则使用 setState 将成本分配给数据。请参阅以下更新的解决方案。

同时开始在所有有状态组件中使用 shouldComponentUpdate 方法以避免不必要的重新渲染。这是每个有状态组件中的最佳实践和推荐方法。

import React, { Component } from 'react';
import FormComponent from './FormComponent';
import PieGraph from './PieGraph';

const initialval = '8998998998';

class Dist extends Component {
  constructor() {
    this.state = {
      theData: ''
    };
    this.graphs(initialval);
  }

  componentWillReceiveProps(nextProps) {
    if (this.props.cost != nextProps.cost) {
      this.setState({
        theData: this.props.cost
      });
    }
  }

  shouldComponentUpdate(nextProps, nextState){
     if(nextProps.cost !== this.props.cost){
         return true;
     }
     return false;
  }
  graphs(val) {
    //Calls a redux action creator and goes through the redux process
    this.props.init(val);
  }

  render() {
    return (
      <div>
        <FormComponent onGpChange={recData => this.graphs(recData)} />
        {this.state.theData !== "" && <PieGraph theData={this.state.theData} />}
      </div>
    );
  }
}

PS:- 以上解决方案适用于版本 React v15。

您不应该使用componentWillReceiveProps,因为在大多数最新版本中它是不安全的,并且它不适用于 React 的异步渲染。

还有其他方法!

静态 getDerivedStateFromProps(props, state)

getDerivedStateFromProps 在调用 render 方法之前调用,在初始安装和后续更新中都是如此。它应该返回一个对象来更新状态,或者返回 null 来不更新任何内容。

所以在你的情况下

...component code
static getDerivedStateFromProps(props,state) {
  if (this.props.cost == nextProps.cost) {
    // null means no update to state
    return null;
  }

  // return object to update the state
  return { theData: this.props.cost };
}
... rest of code

您也可以使用记忆功能,但在您的情况下由您决定。 该链接有一个示例,您可以通过 memoization 和 getDerivedStateFromProps 获得相同的结果

例如,在props更改后更新列表(搜索)您可以从这里开始:

static getDerivedStateFromProps(props, state) {
    // Re-run the filter whenever the list array or filter text change.
    // Note we need to store prevPropsList and prevFilterText to detect changes.
    if (
      props.list !== state.prevPropsList ||
      state.prevFilterText !== state.filterText
    ) {
      return {
        prevPropsList: props.list,
        prevFilterText: state.filterText,
        filteredList: props.list.filter(item => item.text.includes(state.filterText))
      };
    }
    return null;
  }

对此:

import memoize from "memoize-one";

class Example extends Component {
  // State only needs to hold the current filter text value:
  state = { filterText: "" };

  // Re-run the filter whenever the list array or filter text changes:
  filter = memoize(
    (list, filterText) => list.filter(item => item.text.includes(filterText))
  );

  handleChange = event => {
    this.setState({ filterText: event.target.value });
  };

  render() {
    // Calculate the latest filtered list. If these arguments haven't changed
    // since the last render, `memoize-one` will reuse the last return value.
    const filteredList = this.filter(this.props.list, this.state.filterText);

    return (
      <Fragment>
        <input onChange={this.handleChange} value={this.state.filterText} />
        <ul>{filteredList.map(item => <li key={item.id}>{item.text}</li>)}</ul>
      </Fragment>
    );
  }
}