我有一个反应组件,它具有属性和状态。某些状态字段包含输入数据(从输入控件中提升),但状态中的字段必须根据当前状态和道具计算:
问题:更新状态计算字段的最佳方法是什么(基于状态和道具的其他字段)?
丑陋的方式:
componentDidUpdate(){
this.setState({calculatedField:calculate(this.props,this.state)}))
}
在这种情况下,我得到无限循环的更新,或者在最好的情况下(如果我使用PureComponent)双重渲染调用。
到目前为止我找到的最佳解决方案(但仍然很难看):
是在状态中创建一个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),因为它实际上是复杂的对象,所以我需要以某种方式缓存它,最好的方法就是处于状态。
答案 0 :(得分:2)
如果您使用的是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>
)
}
有关更多详细信息,请参阅React documentation
答案 1 :(得分: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>
);
}
}
将计算存储在您的状态中的问题是,计算可以由多个源进行变更。如果您使用我的解决方案,那么任何事情都无法覆盖计算而不使用正确的函数来计算它们。
答案 2 :(得分: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}
/>
}
}
}
答案 3 :(得分: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
}
您需要检查计算方法中使用的状态和道具,并确保在更新状态之前已更改它们。这将阻止无限循环。
答案 4 :(得分:0)
不要在您的州内包含冗余信息。
一个简化的示例是您所在的州有firstName
和lastName
。如果我们想在您的render
方法中显示全名,您只需执行以下操作:
render() {
return <span>{`${this.state.firstName} ${this.state.lastName}`}</span>
}
我喜欢这个示例,因为很容易看到在我们的状态中添加fullName
,只需保留${this.state.firstName} ${this.state.lastName}
是不必要的。每次我们的组件渲染时,我们都会进行字符串连接,我们对此感到满意,因为这是一个便宜的操作。
在您的示例中,您的计算很便宜,因此您也应该使用render
方法进行计算。
答案 5 :(得分:0)
看起来“状态”是存储您需要在渲染函数上使用的所有内容(甚至是计算值)的地方,但通常我们会遇到您描述的问题。
自 React 16.3 以来,static getDerivedStateFromProps (nextProps, prevState)
“生命周期挂钩”的方式已经出现了针对这种情况的新方法。
如果还没有,您应该至少更新到此版本,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;