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

无需Redux:React子组件TermsOfServices向父组件OptinPage传状态

不用Redux在父组件中获取子组件状态的解决方案

嘿,你这个需求完全不用Redux就能搞定!React本身的单向数据流+回调函数或者Ref引用就可以轻松实现父组件获取子组件的状态来显示页码。下面给你两种具体的实现方案,结合你的代码来改:


方案一:通过回调函数传递状态(推荐,符合React设计理念)

这个方案的核心是让子组件在状态变化时,主动把当前页码信息通过回调函数通知给父组件,父组件维护自己的状态来展示页码。

步骤1:修改父组件OptinPage

在OptinPage中添加状态存储页码信息,同时定义回调函数传给子组件:

import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { Link, NavRight, Navbar, Page, Popup, } from 'framework7-react';
import { FormattedMessage, intlShape } from 'react-intl';
import './OptInPage.scss';
import TermsOfServices from '../components/TermsOfServices';

class OptinPage extends PureComponent {
  static propTypes = {
    logout: PropTypes.func.isRequired,
    opened: PropTypes.bool.isRequired,
    onSucceeded: PropTypes.func,
  };
  static contextTypes = {
    intl: intlShape,
    logout: PropTypes.func,
  };
  static defaultProps = {
    onSucceeded: () => {},
  };
  
  // 新增状态存储页码信息
  state = {
    currentPage: 0,
    totalPages: 0,
  };

  // 定义回调函数,接收子组件传来的页码更新
  handlePageChange = (current, total) => {
    this.setState({
      currentPage: current,
      totalPages: total,
    });
  };

  render() {
    const { opened, logout, onSucceeded } = this.props;
    const { intl } = this.context;
    const { formatMessage } = intl;
    const { currentPage, totalPages } = this.state;

    return (
      <Popup opened={opened} className="demo-popup-swipe" tabletFullscreen>
        <Page id="optin_page">
          <Navbar title={formatMessage({ id: 'press_yui_tos_title' })}>
            {/* 在这里显示页码 */}
            <NavRight>
              {totalPages > 0 && (
                <span className="page-indicator">
                  {currentPage + 1}/{totalPages}
                </span>
              )}
              <Link onClick={() => logout()}>
                <FormattedMessage id="press_yui_comments_popup_edit_close" />
              </Link>
            </NavRight>
          </Navbar>
        </Page>
        {/* 给子组件传递回调函数 */}
        <TermsOfServices 
          onSucceeded={onSucceeded} 
          onPageChange={this.handlePageChange}
        />
      </Popup>
    );
  }
}

export default OptinPage;

步骤2:修改子组件TermsOfServices

添加onPageChange的prop定义,在所有更新currentTos的地方调用这个回调,传递页码信息:

import API from 'api';
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { Block, BlockTitle, Col, Fab, Icon, Preloader, } from 'framework7-react';
import { FormattedMessage } from 'react-intl';
import { connect } from 'react-refetch';
import ReactHtmlParser from 'react-html-parser';

class TermsOfServices extends PureComponent {
  static propTypes = {
    agreeTosFunc: PropTypes.func.isRequired,
    agreeTos: PropTypes.object,
    onSucceeded: PropTypes.func,
    tos: PropTypes.object.isRequired,
    onPageChange: PropTypes.func, // 新增prop定义
  };
  static contextTypes = {
    apiURL: PropTypes.string,
    loginToken: PropTypes.string,
    userId: PropTypes.string,
  };
  static defaultProps = {
    agreeTos: {},
    onSucceeded: () => {},
    onPageChange: () => {}, // 添加默认值避免报错
  };
  state = {
    currentTos: -1,
  };

  componentDidUpdate(prevProps) {
    const { agreeTos, onSucceeded, tos, onPageChange } = this.props;
    const { currentTos } = this.state;
    /* Prepare for first tos after receiving all of them */
    if ( prevProps.tos.pending && tos.fulfilled && tos.value.length && currentTos < 0 ) {
      const newCurrent = 0;
      this.setState({ currentTos: newCurrent });
      // 通知父组件页码更新
      onPageChange(newCurrent, tos.value.length);
    }
    /* When sending ToS agreement is done */
    if ( prevProps.agreeTos.pending && agreeTos.fulfilled ) {
      onSucceeded();
    }
  }

  handleNext = () => {
    const { agreeTosFunc, tos, onPageChange } = this.props;
    const { currentTos: currentTosId } = this.state;
    const termsOfServices = tos.value;
    const done = currentTosId + 1 === termsOfServices.length;
    const newCurrent = currentTosId + 1;
    this.setState({ currentTos: newCurrent });
    // 通知父组件页码更新
    onPageChange(newCurrent, termsOfServices.length);
    if (done) {
      agreeTosFunc(termsOfServices.map((v) => v._id));
    }
  };

  handlePrev = () => {
    const { tos, onPageChange } = this.props;
    const { currentTos: currentTosId } = this.state;
    const newCurrent = currentTosId - 1;
    this.setState({ currentTos: newCurrent });
    // 通知父组件页码更新
    onPageChange(newCurrent, tos.value.length);
  };

  render() {
    const { tos } = this.props;
    const { currentTos: currentTosId } = this.state;
    const termsOfServices = tos.value;
    const currentTermsOfServices = termsOfServices && termsOfServices[currentTosId];
    const loaded = termsOfServices && !tos.pending && tos.fulfilled;
    const htmlTransformCallback = (node) => {
      if (node.type === 'tag' && node.name === 'a') {
        // eslint-disable-next-line no-param-reassign
        node.attribs.class = 'external';
      }
      return undefined;
    };
    return (
      <div>
        { (!loaded || !currentTermsOfServices) && (
          <div id=" optin_page_content" className="text-align-center">
            <Block className="row align-items-stretch text-align-center">
              <Col><Preloader size={50} /></Col>
            </Block>
          </div>
        )}
        { loaded && currentTermsOfServices && (
          <div id="optin_page_content" className="text-align-center">
            <h1>
              <FormattedMessage id="press_yui_tos_subtitle" values={{ from: currentTosId + 1, to: termsOfServices.length }} />
            </h1>
            <BlockTitle>
              {ReactHtmlParser(
                currentTermsOfServices.title,
                { transform: htmlTransformCallback },
              )}
            </BlockTitle>
            <Block strong inset>
              <div className="tos_content">
                {ReactHtmlParser(
                  currentTermsOfServices.html,
                  { transform: htmlTransformCallback },
                )}
              </div>
            </Block>
            <Fab position="right-bottom" slot="fixed" color="pink" onClick={() => this.handleNext()}>
              {currentTosId + 1 === termsOfServices.length && <Icon ios="f7:check" aurora="f7:check" md="material:check" />}
              {currentTosId !== termsOfServices.length && <Icon ios="f7:chevron_right" aurora="f7:chevron_right" md="material:chevron_right" />}
            </Fab>
            {currentTosId > 0 && (
              <Fab position="left-bottom" slot="fixed" color="pink" onClick={this.handlePrev}>
                <Icon ios="f7:chevron_left" aurora="f7:chevron_left" md="material:chevron_left" />
              </Fab>
            )}
          </div>
        )}
      </div>
    );
  }
}

export default connect.defaults(new API())((props, context) => {
  const { apiURL, userId } = context;
  return {
    tos: {
      url: new URL(`${apiURL}/tos?outdated=false&required=true`),
    },
    agreeTosFunc: (tos) => ({
      agreeTos: {
        body: JSON.stringify({ optIn: tos }),
        context,
        force: true,
        method: 'PUT',
        url: new URL(`${apiURL}/users/${userId}/optin`),
      },
    }),
  };
})(TermsOfServices);

方案二:使用React Refs直接读取子组件状态(简单但不推荐,违反封装)

如果你觉得回调函数太繁琐,也可以用Ref直接获取子组件状态,不过这种方式会打破组件封装性,后期维护成本较高,适合快速应急:

修改OptinPage组件:

class OptinPage extends PureComponent {
  // 创建Ref引用子组件
  tosRef = React.createRef();

  // 获取页码信息的辅助函数
  getPageInfo = () => {
    if (this.tosRef.current) {
      const { currentTos } = this.tosRef.current.state;
      const total = this.tosRef.current.props.tos.value?.length || 0;
      return { current: currentTos + 1, total };
    }
    return { current: 0, total: 0 };
  };

  render() {
    const { opened, logout, onSucceeded } = this.props;
    const { intl } = this.context;
    const { formatMessage } = intl;
    const { current, total } = this.getPageInfo();

    return (
      <Popup opened={opened} className="demo-popup-swipe" tabletFullscreen>
        <Page id="optin_page">
          <Navbar title={formatMessage({ id: 'press_yui_tos_title' })}>
            <NavRight>
              {total > 0 && (
                <span className="page-indicator">
                  {current}/{total}
                </span>
              )}
              <Link onClick={() => logout()}>
                <FormattedMessage id="press_yui_comments_popup_edit_close" />
              </Link>
            </NavRight>
          </Navbar>
        </Page>
        {/* 传递Ref给子组件 */}
        <TermsOfServices 
          ref={this.tosRef}
          onSucceeded={onSucceeded} 
        />
      </Popup>
    );
  }
}

这种方式不需要修改子组件,直接通过Ref读取状态即可,但注意只有类组件能这么用(你的子组件正好是类组件),函数组件需要配合forwardRef使用。


总结

优先选择方案一,它符合React单向数据流的设计原则,组件通信逻辑清晰,更容易维护;方案二适合快速实现,但长期来看可能会带来组件耦合的问题。

内容的提问来源于stack exchange,提问作者Amélie

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 21:37:39