React Router 4代码分割下路由组件重复挂载问题咨询
分析你的路由组件重挂载现象
我来帮你拆解一下你遇到的这个现象背后的原因:
静态导入(无代码分割)时组件不重挂载:
当你在App中直接手动导入IndexPage或Registration组件,再传给Route的component/element属性时,这些组件的引用在App组件的生命周期里是固定不变的。即使App的状态(比如kappa)更新,React会识别到路由组件的引用没有变化,所以只会更新App本身的DOM(比如显示那个p元素),不会卸载再重新挂载路由组件——这和你观察到的一致,确实是预期行为。代码分割/动态导入时组件会重挂载:
不管是用React官方的React.lazy配合Suspense做代码分割,还是你自己在componentDidMount里用webpack动态导入import(/path/to/${this.props.component}/index.jsx),本质上每次状态更新触发渲染时,动态导入的组件都会生成一个新的组件引用(或者说,每次动态导入返回的Promise resolve后,得到的组件实例引用可能会被重新创建)。当
Route发现传入的组件引用变了,就会判定这是一个全新的组件,于是会卸载旧的路由组件实例,重新挂载新的实例——这就是你看到“路由组件重新挂载”的核心原因。举个直观的例子:如果你在AsyncView里每次状态更新都重新执行动态导入,相当于每次给Route传递的都是一个新的组件类型,React的diff算法会判定需要替换整个组件树,自然就触发了重挂载。
如何避免动态导入时的组件重挂载?
如果你想保留代码分割的优势,同时不让路由组件在App状态更新时重挂载,可以试试这几个方案:
缓存动态导入的组件:
把动态导入的结果缓存起来,比如在AsyncView组件的实例属性里存储已经加载好的组件,只有在组件路径变化时才重新导入,否则复用之前缓存的组件引用。示例代码如下:class AsyncView extends React.Component { state = { Component: null }; // 用实例属性缓存已加载的组件 componentCache = {}; async componentDidMount() { await this.loadComponent(); } async componentDidUpdate(prevProps) { // 只有当目标组件路径变化时才重新加载 if (prevProps.component !== this.props.component) { await this.loadComponent(); } } loadComponent = async () => { const componentName = this.props.component; if (!this.componentCache[componentName]) { const module = await import(`/path/to/${componentName}/index.jsx`); this.componentCache[componentName] = module.default; } this.setState({ Component: this.componentCache[componentName] }); }; render() { const { Component } = this.state; return Component ? <Component /> : null; } }这样只有当路由对应的组件路径变化时,才会重新加载并更新组件;App的其他状态(比如
kappa)更新时,因为组件引用已经被缓存,不会触发重挂载。缩小状态更新的影响范围:
如果你那个控制p元素显示的kappa状态不需要放在App组件里,可以把它移到App的子组件(比如独立的布局组件)中,这样App组件本身不会重新渲染,自然也就不会触发路由组件的重新评估。配合
React.memo做优化:
用React.memo包裹动态导入的组件,避免组件在props未变化时重复渲染。不过这个方案的前提是组件引用不会频繁变化,所以最好还是配合缓存方案一起使用。
内容的提问来源于stack exchange,提问作者Limbo

