无需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
相关产品推荐
相关产品推荐

