React子组件BasePagination状态未更新问题排查求助
问题描述
父组件切换页面并更新状态后,子组件BasePagination虽然触发了重渲染,但内部的初始状态始终没更新——即便父组件已经通过JSX属性传递了最新值。
父组件代码
function Users(props) { const [currentPage, setCurrentPage] = useState(1); const [perPage, setPerPage] = useState(1); const [sort, setSort] = useState('id'); const [dir, setDir] = useState('desc'); const [data, setData] = useState({results: []}); const [totalPages, setTotalPages] = useState(1); useEffect(() => { const fetchData = async (page, sort, dir, perPage) => { const users = await get(url.GET_USERS + '?page=' + page + '&sort=' + sort + '&dir=' + dir + '&per=' + perPage); setData(users); setTotalPages(Math.ceil(users.total / perPage)); } fetchData(currentPage, sort, dir, perPage); return () => { // 组件卸载时执行 }; }, [currentPage]); const handleClick = (e, index) => { e.preventDefault(); // 原代码错误:函数组件不能用this.setState // this.setState({ // currentPage: index // }); setCurrentPage(index); // 修正为直接调用状态更新函数 }; return ( <React.Fragment> <div className="page-content"> <div className="container-fluid"> <Row> <Col> <Card> <CardBody> <CardTitle className="h4">Users</CardTitle> <p className="card-title-desc"> in the api database </p> <div className="table-rep-plugin"> <div className="table-responsive mb-0" data-pattern="priority-columns" > <Table id="tech-companies-1" className="table table-striped table-bordered" > <Thead> <Tr> <Th data-priority="1">ID</Th> <Th data-priority="2">Name</Th> <Th data-priority="3">Email</Th> </Tr> </Thead> <Tbody> {data.results.map((item) => { return ( <tr key={item.id}> <td>{item.id}</td> <td>{item.name}</td> <td>{item.email}</td> </tr> ); })} </Tbody> </Table> <BasePagination pages={totalPages} currentPage={currentPage}/> <Button color="primary" onClick={() => setCurrentPage(1)}>回到第一页</Button> <Button onClick={() => setCurrentPage(2)}>跳转到第二页</Button> </div> </div> </CardBody> </Card> </Col> </Row> </div> </div> </React.Fragment> ); }
子组件BasePagination.js代码
import React from 'react'; import { Pagination, PaginationItem, PaginationLink } from 'reactstrap'; const maxPages = 6; let pages = []; export default class BasePagination extends React.Component { constructor(props){ super(props); this.state = {count: props.pages, currentPage: props.currentPage}; } componentDidMount() { console.log('mount'); } componentWillUnmount() { console.log('unmount'); } arrayRange(start, stop, step, maxPages) { return Array.from( { length: (Math.max(stop, maxPages) - start) / step + 1 }, (value, index) => start + index * step ); } render() { console.log(this.state); pages = this.arrayRange(this.state.currentPage, this.state.currentPage + maxPages-1, 1, this.state.count); if (pages > 1) { // 原代码错误:应该判断数组长度,不是数组本身 return ( <Pagination aria-label="Page navigation example"> <PaginationItem> <PaginationLink previous href="#" /> </PaginationItem> {pages.map((x, i) => <PaginationItem key={x}> <PaginationLink href="#">{x}</PaginationLink> </PaginationItem> )} <PaginationItem> <PaginationLink next href="#" /> </PaginationItem> </Pagination> ); } else { return; } } }
问题原因
子组件BasePagination是类组件,它的构造函数只在组件挂载时执行一次——也就是说,只会用最初的props初始化state。后续父组件更新props时,子组件的state不会自动同步,所以即便触发重渲染,state还是原来的初始值。
另外还有两个小错误:
- 父组件的
handleClick误用了this.setState,但父组件是函数组件,应该直接调用setCurrentPage - 子组件里判断
pages > 1逻辑错误,pages是数组,应该判断pages.length > 1
修复方案
优先方案:直接使用props,不维护子组件内部state
既然分页状态已经由父组件管理,子组件完全没必要自己存一份state,直接用props就行,这是最简洁合理的写法:
修改后的BasePagination.js:
import React from 'react'; import { Pagination, PaginationItem, PaginationLink } from 'reactstrap'; const maxPages = 6; export default class BasePagination extends React.Component { componentDidMount() { console.log('mount'); } componentWillUnmount() { console.log('unmount'); } arrayRange(start, stop, step, maxCount) { // 修正计算逻辑:避免生成超出总页数的页码 const end = Math.min(start + maxPages - 1, maxCount); return Array.from( { length: (end - start) / step + 1 }, (value, index) => start + index * step ); } render() { const { currentPage, pages: maxCount } = this.props; const pageList = this.arrayRange(currentPage, currentPage + maxPages - 1, 1, maxCount); if (pageList.length > 1) { return ( <Pagination aria-label="Page navigation example"> <PaginationItem> <PaginationLink previous href="#" /> </PaginationItem> {pageList.map((x) => <PaginationItem key={x} active={x === currentPage}> <PaginationLink href="#">{x}</PaginationLink> </PaginationItem> )} <PaginationItem> <PaginationLink next href="#" /> </PaginationItem> </Pagination> ); } else { return null; } } }
备选方案:同步props到state(仅当子组件需要独立维护状态时使用)
如果子组件必须自己管理state,需要在props更新时手动同步到state,添加componentDidUpdate生命周期:
export default class BasePagination extends React.Component { constructor(props){ super(props); this.state = {count: props.pages, currentPage: props.currentPage}; } // 当props变化时同步到state componentDidUpdate(prevProps) { if (prevProps.currentPage !== this.props.currentPage || prevProps.pages !== this.props.pages) { this.setState({ currentPage: this.props.currentPage, count: this.props.pages }); } } // 其余代码不变... }
内容的提问来源于stack exchange,提问作者Emery King
相关产品推荐
相关产品推荐

