首页 文章

用计算字段反应状态

提问于
浏览
2

我有一个反应组件,它具有属性和状态 . 某些状态字段包含输入数据(从输入控件中提升),但状态中的字段必须根据当前状态和道具计算:

enter image description here

The question: 更新状态计算字段的最佳方法是什么(基于状态和道具的其他字段)?

丑陋的方式:

componentDidUpdate(){
    this.setState({calculatedField:calculate(this.props,this.state)})) 
}

在这种情况下,我得到无限循环的更新或在最好的情况下(如果我使用PureComponent)双重渲染调用 .

The best solution I found so far (but still ugly) :是否在状态中创建 calculated 对象,其中包含计算字段并在componentWillUpdate中更新,以避免setState:

componentWillUpdate(nextProps,nextState){
   nextState.calculated.field1=f(nextProps,nextState)
}
class ParentComponent extends React.Component {
  constructor(props, ctx) {
    super(props,ctx)
    this.state={A:"2"}
  }

  render() {
    console.log("rendering ParentComponent")
    return <div>
      <label>A=<input value={this.state.A} onChange={e=>{this.setState({A:e.target.value})}} /></label> (stored in state of Parent component)
      <ChildComponent A={this.state.A} />
    </div>
  }
}

class ChildComponent extends React.PureComponent {
  constructor(props,ctx) {
    super(props,ctx);
    this.state={
      B:"3",
      Calculated:{}
    }
  }

  render() {
    console.log("rendering ChildComponent")
    return <div>
      <label>B=<input value={this.state.B} onChange={e=>{this.setState({B:e.target.value})}} /></label> (stored in state of Child component state)
      <div>
        f(A,B)=<b>{this.state.Calculated.result||""}</b>(stored in state of Child component)
        <button onClick={e=>{ this.setState({Calculated:{result:new Date().toTimeString()}}) }}>Set manual value</button>
      </div>
    </div>
  }

  componentWillUpdate(nextProps, nextState) {
    this.state.Calculated.result = getCalculatedResult(nextProps.A, nextState.B)
  }

  componentWillReceiveProps(nextProps) {
    this.state.Calculated.result = getCalculatedResult(nextProps.A, this.state.B)
  }

  componentWillMount() {
    this.state.Calculated.result = getCalculatedResult(this.props.A, this.state.B)
  }
}

function getCalculatedResult(a,b) {
  const aNum = Number(a)||0
  const bNum = Number(b)||0;
  const result = (aNum*bNum).toString();
  return result;
}

ReactDOM.render(<ParentComponent/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.2.0/umd/react-dom.development.js"></script>
<div id="root"></div>

这也是丑陋的解决方案,并且React不建议改变状态以避免setState . 那么什么是正确的解决方案呢?

NOTE:

在我的实际应用程序中,我无法在渲染过程中每次重新计算f(a,b),因为它实际上是复杂的对象,所以我需要以某种方式缓存它,最好的方法是在状态中 .

5 回答

  • 1

    我不建议你将你的计算值存储在你的州内 . 我的方法更像是这样的:

    import PropTypes from 'prop-types';
    import React from 'react';
    
    class Component extends React.Component {
      static defaultProps = { value: 0 };
    
      static propTypes = { value: PropTypes.number };
    
      state = { a: 0, b: 0 };
    
      result = () => this.state.a + this.state.b + this.props.value;
    
      updateA = e => this.setState({ a: +e.target.value });
    
      updateB = e => this.setState({ b: +e.target.value });
    
      render() {
        return (
          <div>
            A: <input onChange={this.updateA} value={this.state.a} />
            B: <input onChange={this.updateB} value={this.state.b} />
            Result: {this.result()}
          </div>
        );
      }
    }
    

    将计算存储在您的状态中的问题是,计算可以由多个源进行变更 . 如果你使用我的解决方案,没有办法,任何东西都可以覆盖计算而不使用正确的函数来计算它们 .

  • 1

    image from question

    您可以将计算结果保存在 this.calculated 而不是 this.state 中 . 它是依赖数据 . 导致更新和渲染的所有数据都已处于状态和道具状态 .

    class Component extends React.Component {
      constructor(props) {
        super(props)
        state = {
          b: 0
        }
      }
    
      updateThis = (event) => {
        this.setState({ b: event.target.value });
      }
    
      componentWillUpdate(nextProps,nextState){
        this.calculated.field1=f(nextProps.a, nextState.b)
      }
    
      render() {
        return (
          <form>
            A = <input onChange={this.props.updateParent} value={this.props.a} /> <br>
            B = <input onChange={this.updateThis} value={this.state.b} /> <br>
            f(A,B) = {this.calculated.field1} <br>
          </form>
        );
      }
    }
    
    class ParentComponent extends React.Component {
      constructor(props) {
        super(props)
        state = {
          a: 0
        }
      }
    
      render() {
         return (
           <Component
             updateParent={event=>this.setState({a: event.target.value})}
             a={this.state.a}
           />
         }
      }
    }
    
  • 0

    不要在您的州中包含冗余信息 .

    一个简化的例子是你所在州的 firstNamelastName . 如果我们想在 render 方法中显示全名,您只需执行以下操作:

    render() {
        return <span>{`${this.state.firstName} ${this.state.lastName}`}</span>
    
    }
    

    我喜欢这个例子,因为很容易看到在我们的状态中添加 fullName ,只是持有 ${this.state.firstName} ${this.state.lastName} 是不必要的 . 每次我们的组件渲染时,我们都会进行字符串连接,并且我们是一个廉价的操作 .

    在您的示例中,您的计算很便宜,因此您也应该在 render 方法中执行此操作 .

  • 0

    你第一次尝试是解决这个问题的正确方法 . 但是,您需要添加一个检查以查看状态是否实际已更改:

    componentDidUpdate(prevProps, prevState){
        if(prevState.field !== this.state.field){
            this.setState({calculatedField:calculate(this.props,this.state)})) 
        }
    }
    
    shouldComponentUpdate(nextProps, nextState) {
        return this.state.calculatedField !== nextState.calculatedField
    }
    

    您需要检查在计算方法中使用的状态和道具,并确保它们在更新状态之前已更改 . 这将阻止无限循环 .

  • 0

    看起来“状态”是存储您需要在渲染函数上使用的所有内容(甚至是计算值)的地方,但通常我们会遇到您描述的问题 .

    React 16.3 以来,这种情况的新方法已经以 static getDerivedStateFromProps (nextProps, prevState) "lifecycle hook"的方式给出 .

    如果还没有,则应至少更新到此版本,并且follow the advice given by the React Team on their blog .

    Here is the official documentation for this functionality .

    这里的问题是这个函数在每次渲染之前被调用,并且是“静态的”你不能访问当前实例的先前道具,这通常需要决定是否必须再次生成计算值(我想这是你的情况) ,正如你所说,你的计算过程很繁重) . 在这种情况下,React团队建议在状态中存储相关道具的值,因此可以将它们与新的道具进行比较:

    if (nextProps.propToCompute !== prevState.propToComputePrevValue) {
      return {
        computedValue: Compute(nextProp.propToCompute),
        propToComputePrevValue: nextProps.propToCompute
      };
    }
    return null;
    

相关问题