用计算字段反应状态

问题描述 投票:0回答:6

我有一个 React 组件,它具有属性和状态。 State 的某些字段包含输入数据(从输入控件提升),但 State 中也有一些字段必须根据当前 State 和 Props 进行计算:

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

丑陋的做法:

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

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

迄今为止我找到的最好的解决方案(但仍然很难看): 就是在state中创建一个

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。那么正确的解决方案是什么?

注意:

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

javascript reactjs state
6个回答
15
投票

如果您使用React 16.8.0及以上版本,您可以使用React hooks API。我认为这是您可能需要的

useMemo()
钩子。例如:

import React, { useMemo } from 'react'

const MyComponent = ({ ...props }) => {
  const calculatedValue = useMemo(
    () => {
      // Do expensive calculation and return.
    },
    [a, b]
  )

  return (
    <div>
      { calculatedValue }
    </div>
  )
}

2
投票

我不建议您将计算出的值存储在您的状态中。我的方法更像是这样:

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
投票

您可以将计算结果保存在

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
投票

您的第一次尝试是解决此问题的正确方法。但是,您需要添加检查以查看状态是否确实发生了变化:

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)
“生命周期钩子”。

如果尚未更新,您应该至少更新到此版本,并且遵循 React 团队在其博客上给出的建议

这里是此功能的官方文档

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

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

-1
投票

请勿包含您所在州的冗余信息。

一个简化的示例是您所在州有

firstName
lastName
。如果我们想在您的
render
方法中显示全名,您只需执行以下操作:

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

}

我喜欢这个例子,因为很容易看出,在我们的状态中添加一个

fullName
,只保留
${this.state.firstName} ${this.state.lastName}
是不必要的。每次组件渲染时我们都会进行字符串连接,我们对此很满意,因为这是一个廉价的操作。

在您的示例中,您的计算很便宜,因此您也应该在

render
方法中进行计算。

© www.soinside.com 2019 - 2024. All rights reserved.