You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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监听builds props的变化,同步更新子组件状态。

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 22:19:51