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

时间:2018-08-03 17:07:50

标签: javascript reactjs

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

POST https://dialogflow.googleapis.com/v2/{parent=projects/*}/agent:restore

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

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

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

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

代码运行正常。有更好的方法吗?不用在render()中做setState吗?

2 个答案:

答案 0 :(得分:2)

永远不要在渲染中使用setState。之所以不这样做,是因为对于每个setState,您的组件都会重新呈现,因此在render中执行setState会导致无限循环,因此不建议这样做。

不需要

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

还要开始在所有有状态的组件中使用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版本。

答案 1 :(得分:2)

您不应使用 componentWillReceiveProps ,因为在最新版本中,它是UNSAFE,并且不能与React的异步渲染一起很好地工作。

还有其他方法!

static getDerivedStateFromProps(props, state)

  

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,但要根据您的情况决定。 链接中有一个示例,您可以通过备忘录和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>
    );
  }
}