最新消息:20210917 已从crifan.com换到crifan.org

【已解决】ReactJS中setState去更改值但无效该值没有立刻改变

ReactJS crifan 5216浏览 0评论

折腾ReactJS项目期间,遇到个诡异的事情:

抽象出年月的选择为一个单独的组件类:

import React, { Component } from ‘react’;
import MonthPicker from ‘react-month-picker’;
import ‘react-month-picker/css/month-picker.less’;
import PropTypes from ‘prop-types’;
export default class YearMonthPicker extends Component {
    state = {
        curDict : {
            year: 0,
            month: 0,
        },
    };
    constructor(props) {
        super(props);
        console.log(‘YearMonthPicker constructor: props=’, this.props);
        this.state.curDict = this.props.value;
        console.log(‘this.state.curDict=’, this.state.curDict);
        this.onInputChange = this.onInputChange.bind(this);
        this.onInputClick = this.onInputClick.bind(this);
        this.onPickerChange = this.onPickerChange.bind(this);
        this.onPickerDissmis = this.onPickerDissmis.bind(this);
    }
    PickerMonthStrList = [ ‘1月’, ‘2月’, ‘3月’, ‘4月’, ‘5月’, ‘6月’,
        ‘7月’, ‘8月’, ‘9月’, ’10月’, ’11月’, ’12月’];
    pickerElement = null;
    static format(yearMonthDict){
        console.log(‘YearMonthPicker format: yearMonthDict=’, yearMonthDict);
        return `${yearMonthDict.year}年${yearMonthDict.month}月`;        
    }
    onInputChange(e){
        console.log(‘onInputChange: e=’, e);
    }
    onInputClick(e) {
        console.log(‘onInputClick: e=’, e,
         ‘ ,this.pickerElement=’, this.pickerElement);
        if (this.pickerElement) {
            this.pickerElement.show();
        }
    }
    onPickerChange(yearStr, monthStr) {
        console.log(‘onPickerChange: yearStr=’, yearStr, ‘ ,monthStr=’, monthStr);
        let yearInt = parseInt(yearStr);
        let monthInt = parseInt(monthStr);
        
        console.log(‘onPickerChange: before set state: curDict’, this.state.curDict,
        ‘ ,yearInt=’, yearInt, ‘ ,monthInt=’, monthInt);
        this.setState({
            curDict : {
                year: yearInt,
                month: monthInt
            }
        });
        console.log(‘onPickerChange: after set state: curDict’, this.state.curDict,
            ‘ ,this.props.onChange=’, this.props.onChange);
        if (this.props.onChange) {
            this.props.onChange(this.state.curDict);            
        }
        if (this.pickerElement) {
            this.pickerElement.dismiss();
        }
    }
    onPickerDissmis(value) {
        console.log(‘onPickerDissmis: value=’, value);
    }
    render() {
        console.log(‘YearMonthPicker render’);
        return (
        <div>
            <div className=”input-group date”>
                <div className=”input-group-addon”>
                    <i className=”fa fa-calendar”></i>
                </div>
                <input
                    type=”text”
                    readOnly
                    className=”form-control pull-right”
                    value={YearMonthPicker.format(this.state.curDict)}
                    onClick={this.onInputClick}
                    onChange={this.onInputChange}
                />
            </div>
            <MonthPicker
                ref={(mPicker) => {this.pickerElement = mPicker;}}
                years={{min: 2014}}
                value={this.state.curDict}
                lang={this.PickerMonthStrList}
                theme={‘light’}
                onChange={this.onPickerChange}
                onDismiss={this.onPickerDissmis}
            >
            </MonthPicker>
        </div>
        );
    }
}
YearMonthPicker.contextTypes = {
    router: PropTypes.object.isRequired,
    onChange: PropTypes.func,
    value: PropTypes.shape({
        year: PropTypes.number,
        month: PropTypes.number,
    })
};
YearMonthPicker.defaultProps = {
    value : {
        year: 0,
        month: 0,
    },
};

然后外部调用:

import YearMonthPicker from ‘components/year-month-picker/year-month-picker’;
export default class ProcessMonitor extends Component {
  state = {
    curYearMonth : {
      year: 0,
      month: 0
    },
}
  constructor(props) {
      super(props);
      let curDate = new Date();
      this.state.curYearMonth.year = curDate.year();
      this.state.curYearMonth.month = curDate.month();
      console.log(‘curDate=’, curDate, ‘ ,this.state.curYearMonth=’, this.state.curYearMonth);
      this.onYearMonthPickerChange = this.onYearMonthPickerChange.bind(this);
  }
  onYearMonthPickerChange(curDict) {
    console.log(‘onYearMonthPickerChange: curDict=’, curDict);
    this.setState({
      curYearMonth : curDict
    });
    this.refreshChartData();
  }
  render() {
    console.log(`ProcessMonitor render`);
    return (
      <div className=”content-wrapper”>
                <div className=”box-body”>
                  <YearMonthPicker
                    value={this.state.curYearMonth}
                    onChange={this.onYearMonthPickerChange}
                  />
}

但是在回调函数中,setState时:

        console.log(‘onPickerChange: before set state: curDict’, this.state.curDict,
        ‘ ,yearInt=’, yearInt, ‘ ,monthInt=’, monthInt);
        this.setState({
            curDict : {
                year: yearInt,
                month: monthInt
            }
        });
        console.log(‘onPickerChange: after set state: curDict’, this.state.curDict,
            ‘ ,this.props.onChange=’, this.props.onChange);

竟然state中的curDict的值,还是没有变化:

以为是:console.log中直接打印对象导致的问题,所以改为:

        // console.log(‘onPickerChange: before set state: curDict’, this.state.curDict,
        // ‘ ,yearInt=’, yearInt, ‘ ,monthInt=’, monthInt);
        console.log(`onPickerChange: before set state: curDict=${JSON.stringify(this.state.curDict)}`,
        ‘ ,yearInt=’, yearInt, ‘ ,monthInt=’, monthInt);
        this.setState({
            curDict : {
                year: yearInt,
                month: monthInt
            }
        });
        // console.log(‘onPickerChange: after set state: curDict’, this.state.curDict,
        // ‘ ,this.props.onChange=’, this.props.onChange);
        console.log(`onPickerChange: after set state: curDict=${JSON.stringify(this.state.curDict)}`,
            ‘ ,this.props.onChange=’, this.props.onChange);

问题依旧:

诡异的是,通过console.log打印出的对象显示,实际上curDict已经是6月了,但是打印输出的还是8月:

继续调试:

        // console.log(‘onPickerChange: before set state: curDict’, this.state.curDict,
        // ‘ ,yearInt=’, yearInt, ‘ ,monthInt=’, monthInt);
        console.log(`onPickerChange: before set state: curDict=${JSON.stringify(this.state.curDict)}`,
        ‘ ,yearInt=’, yearInt, ‘ ,monthInt=’, monthInt, ‘, this=’, this);
        this.setState({
            curDict : {
                year: yearInt,
                month: monthInt
            }
        });
        // console.log(‘onPickerChange: after set state: curDict’, this.state.curDict,
        // ‘ ,this.props.onChange=’, this.props.onChange);
        console.log(`onPickerChange: after set state: curDict=${JSON.stringify(this.state.curDict)}`,
            ‘ ,this.props.onChange=’, this.props.onChange);
        if (this.props.onChange) {
            console.log(`onPickerChange: before call onChange, is 8: ${this.state.curDict.month === 8}, is 6: ${this.state.curDict.month === 6}`);
            this.props.onChange(this.state.curDict);            
        }
        if (this.pickerElement) {
            this.pickerElement.dismiss();
            console.log(`onPickerChange: after dismiss: curDict=${JSON.stringify(this.state.curDict)}`);
        }

发现此时,还是8月,而不是6月:

reactjs setstate not working

javascript – Why calling react setState method doesn’t mutate the state immediately? – Stack Overflow

->

React.Component – React

“Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.

setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied. If you need to set the state based on the previous state, read about the updater argument below.

。。。

Generally we recommend using componentDidUpdate() for such logic instead.”

setState就类似于一个request,只是发出了请求,虽然往往是,但是未必一定是,立即就修改了对应的值。

-》setState之后立即就去获得被修改的state值,并不能保证一定是新的修改后的

-》但是之后肯定会更新的。会在某个合适的时机去修改。往往是多个变量一次性批量更新的时候再去更新。

-》如果想要得到确定的更新后的值,则可以:

(1)在componentDidUpdate中去处理

(2)用setState的callback

官网推荐用componentDidUpdate。

但是我此处去用callback:

去写代码的时候,也可以看到语法提示,setState的参数分别是:

(1)updater函数

(2)callback函数

即,对应的:

然后对应的代码:

效果:

可见,此处的picker先去dismiss消失了,之后setState才执行到callback,才得到更新后的值。

【总结】

此处,ReactJS中的setState中设置了新的值之后,(虽然大多数时候都是可以立刻更新值的,但是不保证)会立刻更新值,所以setStat之后获得的值,可能是旧的,而不是修改后的新值。

想要确保获得新值,有两种方式:

(1)在componentDidUpdate中去处理

(2)用setState的callback

虽然官网推荐用componentDidUpdate,但是对于此处的逻辑:

当选择日期之后,Picker控件就消失了,然后在消失之前,应该就去回调,让父页面得到修改后的值

如果放到componentDidUpdate处理,就很麻烦,还要设置标识去表示是onChange了,然后再去回调,很麻烦。

所以还是用简单的setState的callback即可:

相关代码:

    onPickerChange(yearStr, monthStr) {
        console.log(‘onPickerChange: yearStr=’, yearStr, ‘ ,monthStr=’, monthStr);
        this.setState(
            {
                curDict : {
                    year: parseInt(yearStr),
                    month: parseInt(monthStr)
                }
            },
            // Note: here use setState callback to makesure curDict indeed changed
            () => {
                if (this.props.onChange) {
                    this.props.onChange(this.state.curDict);            
                }
                if (this.pickerElement) {
                    this.pickerElement.dismiss();
                }
            }
        );
    }

转载请注明:在路上 » 【已解决】ReactJS中setState去更改值但无效该值没有立刻改变

发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
92 queries in 0.184 seconds, using 23.36MB memory