React 16.3子组件FeatureBuilds基于Props初始化状态异常
问题描述
我有一个名为FeatureBuilds的子组件,从父组件接收builds对象数组作为props。遇到的问题:
- 直接访问或刷新展示Feature builds的页面时,builds列表无法显示;
- 先访问其他页面再进入该页面则正常。
原因:
FeatureBuilds组件先完成挂载,而父组件仍在异步获取数据,导致组件初始化时props没有数据,后续父组件数据更新后,子组件未同步更新状态。同时观察到FeatureBuilds的componentWillMount日志在父组件获取数据前输出。
环境限制:项目使用React 16.3,无法使用React Hooks。
相关组件代码
FeatureBuilds Component
export default class FeatureBuilds extends Component { state = { featureBuilds: [] }; componentWillMount() { this.setState({featureBuilds: this.props.builds.slice(0,10)}) console.log('FeatureBuilds : componentWillMount :: ' + this.state.featureBuilds); } onClickInstall = val => { this.props.onClickInstall(val); }; onPageChange = (page) => { console.log('FeatureBuilds : onPageChange :: ' + page); const startIndex = (page - 1) * 10 const builds = this.props.builds.slice(startIndex, startIndex + 10); this.setState({featureBuilds: builds}) } getFeatureBuildsList() { return this.state.featureBuilds.map(val => ( <div key={val.Key}> <Card key={val.Key} extra={ <a onClick={() => this.onClickInstall(val)}> <span style={{ fontSize: '14px', color: '#00B14F' }}> Install </span> </a> } title={ <span style={{ whiteSpace: 'pre-line', fontSize: '14px', wordBreak: 'break-word', }} > {val.Key} </span> } type="inner" style={{ marginTop: 8, }} bodyStyle={{ padding: '10px 20px 10px' }} > { <span style={{ fontSize: '12px' }}> {moment(val.LastModified).format('DD/MM/YYYY | HH:mm')} </span> } </Card> </div> )); } render() { return ( <div> <Card title="Feature builds" bordered={false} bodyStyle={{ padding: '0px' }} > <p style={{ color: 'rgba(0, 0, 0, 0.85)', fontSize: 14, fontWeight: 500, }} /> {this.getFeatureBuildsList()} </Card> <br/> <Pagination onChange={this.onPageChange} total={this.props.builds.length} /> </div> ); } } FeatureBuilds.propTypes = { builds: PropTypes.arrayOf( PropTypes.shape({ Key: PropTypes.string.isRequired, LastModified: PropTypes.string.isRequired, }), ).isRequired, onClickInstall: PropTypes.func.isRequired, };
App Component
export default class Apps extends Component { changeApp = activeKey => { this.props.onAppChange(activeKey); }; onPageChange = (page) => { console.log('App Component : onPageChange :: ' + page); } render() { return ( <div key="apps"> <Tabs defaultActiveKey="1" onChange={this.changeApp}> <Tabs.TabPane key="a" tab="A"> <BetaBuilds onClickInstall={this.props.onClickInstall} builds={this.props.betaBuilds} /> <br /> <FeatureBuilds onClickInstall={this.props.onClickInstall} builds={this.props.featureBuilds} onPageChange={this.props.onPageChange} /> </Tabs.TabPane> <Tabs.TabPane key="b" tab="B"> <BetaBuilds onClickInstall={this.props.onClickInstall} builds={this.props.betaBuilds} /> <br /> <FeatureBuilds onClickInstall={this.props.onClickInstall} builds={this.props.featureBuilds} onPageChange={this.props.onPageChange} /> </Tabs.TabPane> </Tabs> </div> ); } }
Home Component
export default class Home extends Component { state = { contentA: [], contentB: [], }; async componentWillMount() { await this.getAllBuilds(this.state.selectedApp); // 在getAllBuilds中设置contentA和contentB的状态 } render() { return ( <div> <Apps betaBuilds={this.state.contentB} featureBuilds={this.state.contentA} onAppChange={this.changeApp} onClickInstall={this.clickInstallHandler} onPageChange={this.onPageChange} /> </div> ); } }
解决方案
核心问题是componentWillMount仅执行一次,无法响应props更新。针对React 16.3,我们通过修改生命周期方法解决:
1. 替换生命周期方法,监听props变化
- 用
componentDidMount替代componentWillMount做初始化; - 添加
componentWillReceiveProps监听buildsprops的变化,同步更新子组件状态。
2. 修改后的FeatureBuilds组件
export default class FeatureBuilds extends Component { state = { featureBuilds: [] }; componentDidMount() { // 组件挂载后初始化列表 this.updateFeatureBuilds(this.props.builds); console.log('FeatureBuilds : componentDidMount :: ', this.state.featureBuilds); } componentWillReceiveProps(nextProps) { // 当父组件传递的builds更新时,同步更新子组件状态 if (nextProps.builds !== this.props.builds) { this.updateFeatureBuilds(nextProps.builds); } } // 提取状态更新逻辑,提升复用性 updateFeatureBuilds = (builds) => { this.setState({ featureBuilds: builds.slice(0, 10) }); }; onClickInstall = val => { this.props.onClickInstall(val); }; onPageChange = (page) => { console.log('FeatureBuilds : onPageChange :: ' + page); const startIndex = (page - 1) * 10; const builds = this.props.builds.slice(startIndex, startIndex + 10); this.setState({ featureBuilds: builds }); }; getFeatureBuildsList() { return this.state.featureBuilds.map(val => ( <div key={val.Key}> <Card key={val.Key} extra={ <a onClick={() => this.onClickInstall(val)}> <span style={{ fontSize: '14px', color: '#00B14F' }}> Install </span> </a> } title={ <span style={{ whiteSpace: 'pre-line', fontSize: '14px', wordBreak: 'break-word', }} > {val.Key} </span> } type="inner" style={{ marginTop: 8, }} bodyStyle={{ padding: '10px 20px 10px' }} > <span style={{ fontSize: '12px' }}> {moment(val.LastModified).format('DD/MM/YYYY | HH:mm')} </span> </Card> </div> )); } render() { return ( <div> <Card title="Feature builds" bordered={false} bodyStyle={{ padding: '0px' }} > <p style={{ color: 'rgba(0, 0, 0, 0.85)', fontSize: 14, fontWeight: 500, }} /> {this.getFeatureBuildsList()} </Card> <br/> <Pagination onChange={this.onPageChange} total={this.props.builds.length} /> </div> ); } } FeatureBuilds.propTypes = { builds: PropTypes.arrayOf( PropTypes.shape({ Key: PropTypes.string.isRequired, LastModified: PropTypes.string.isRequired, }), ).isRequired, onClickInstall: PropTypes.func.isRequired, };
额外优化建议
- 可以去掉子组件的
featureBuilds状态,直接在渲染时基于this.props.builds和当前页码计算展示内容,避免状态与props不同步的问题; - React 16.3中
componentWillMount已被标记为不安全方法,建议后续尽量避免使用。
内容的提问来源于stack exchange,提问作者Mahtab Alam
相关产品推荐
相关产品推荐

