折腾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
->
“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(); } } ); } |