React父组件状态变更时子组件未触发重渲染问题排查
问题根因
你当前代码无法触发子组件重渲染的核心错误有3个:
- 在Route的render回调中直接对
this.state.url赋值,没有调用this.setState()方法。React的状态更新必须通过setState触发,直接修改state对象属性不会被React感知,也就不会触发组件树的重渲染流程,控制台能打印到更新后的值只是因为你修改了对象属性,React本身没有收到状态变更通知。 - 给TopBar传入的
key={this.state}是无效值,React的key属性只接受字符串、数字等基础类型,传入整个state对象无法起到强制刷新组件的作用。 - TopBar组件中将props传入的url重复拷贝到本地state是典型的冗余状态写法,本身就容易出现props和本地状态不同步的问题,完全没有必要。
官方推荐最简实现(无需手动维护url状态)
React Router本身提供了withRouter高阶组件,可以直接让包裹的组件拿到当前路由信息,路由变化时会自动触发组件重渲染,根本不需要父组件手动传递url属性,代码量更少也不会出现状态同步bug。
首先修改TopBar组件:
import React from 'react'; import { AppBar, Toolbar, Typography } from '@material-ui/core'; import { withRouter } from 'react-router-dom'; import './TopBar.css'; class TopBar extends React.Component { // 不需要额外constructor、不需要本地state存url、不需要getDerivedStateFromProps render() { // 直接从注入的props中取路由信息,this.props.location包含当前完整路由数据 const currentPath = this.props.location.pathname; return ( <AppBar position="absolute"> <Toolbar> <Typography variant="h5" color="inherit"> {currentPath} {/* 如果需要提取userId等参数,直接解析pathname或调用react-router提供的matchPath方法即可 */} </Typography> </Toolbar> </AppBar> ); } } // 用withRouter包裹导出,组件会自动注入路由相关props,路由变化时自动重渲染 export default withRouter(TopBar);
然后简化父组件PhotoShare:
- 删掉父组件中手动维护的url state
- 删掉Route render函数里直接修改state的错误逻辑
- 不需要给TopBar传url属性,也不需要传特殊key
import React from 'react'; import ReactDOM from 'react-dom'; import { HashRouter, Route, Switch } from 'react-router-dom'; import { Grid, Paper } from '@material-ui/core'; import './styles/main.css'; import TopBar from './components/topBar/TopBar'; import UserDetail from './components/userDetail/userDetail'; import UserPhotos from './components/userPhotos/userPhotos'; class PhotoShare extends React.Component { // 无需手动维护路由相关状态 render() { return ( <HashRouter> <div> <Grid container spacing={8}> <Grid item xs={12}> <TopBar /> </Grid> <Grid item sm={9}> <Paper className="cs142-main-grid-item"> <Switch> {/* 无额外props传递时直接用component属性即可,性能更好 */} <Route path="/users/:userId" component={UserDetail} /> <Route path="/photos/:userId" component={UserPhotos} /> </Switch> </Paper> </Grid> </Grid> </div> </HashRouter> ); } } ReactDOM.render( <PhotoShare />, document.getElementById('photoshareapp'), );
注意:如果路由组件需要传递自定义props,可以保留render写法,但render函数必须是纯函数,不要在里面执行任何修改父组件状态、修改全局变量这类有副作用的逻辑,否则会触发死循环或渲染异常。
如果坚持使用父组件传props的方案,修正点如下
- 绝对不要直接修改
this.state的属性,所有状态更新必须通过this.setState()触发 - 不要在render函数里调用
setState,会触发无限重渲染。可以通过路由监听钩子、跳转逻辑触发点来同步更新url状态 - 子组件不需要拷贝props到本地state,直接用
this.props.url渲染即可,冗余状态是类组件bug的高发来源 - key属性直接传入url字符串类型值即可,不要传入整个state对象
内容的提问来源于stack exchange,提问作者dannnny
相关产品推荐
相关产品推荐

