首页 文章

如何在React中不在render函数中使用setState

提问于
浏览
2

我有一个完整的运行代码,但它有一个缺陷 . 它从render()内部调用setState() . 因此,反应会引发反模式警告 .

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 父组件中,我有如下代码 . 构造函数()使用初始值调用图()以显示图形 . 用户还有一个表单来指定新值并提交表单 . 它使用新值再次运行图形()并重新渲染图形 .

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组件,该组件触发graph()和componentWillReceiveProps .

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

代码一切正常 . 有没有更好的方法呢?没有在render()中执行setState?

2 回答

  • 2

    永远不要在渲染中使用setState . 您不应该这样做的原因是因为对于每个setState,您的组件将重新渲染,因此在渲染中执行setState将导致无限循环,这是不推荐的 .

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

    还要在所有statefull组件中开始使用shouldComponentUpdate menthod,以避免不必要的重新渲染 . 这是每个statefull组件中最好的实践和推荐方法 .

    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版本 .

  • 0

    您不应该使用 componentWillReceiveProps ,因为在最新版本中,'s UNSAFE and it won'适用于React的异步呈现 .

    还有其他方法!

    static getDerivedStateFromProps(props, state)

    在初始装载和后续更新时,在调用render方法之前调用getDerivedStateFromProps . 它应返回一个对象来更新状态,或者返回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,但在您的情况下,由您决定 . The link has one example where you can achieve the same result with memoization and getDerivedStateFromProps

    例如,在改变道具后更新列表(搜索)你可以从这里:

    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>
        );
      }
    }
    

相关问题