我已经在 ReactJS 16.8.5 和 React-Redux 3.7.2 上构建了一个应用程序。当应用加载应用挂载时,会设置初始存储并针对 Firebase 实时数据库设置数据库订阅。该应用程序包含标题Sidebar
和内容部分。
我已经与React.memo一起实现了reselect以避免在 props 更改时重新渲染,但组件仍在重新渲染。使用React profiler API和React.memo 中的比较函数,我可以看到尽管 props 相等,但它被渲染了几次。Sidebar
areEqual
Sidebar
app.js
//Imports etc...
const jsx = (
<React.StrictMode>
<Provider store={store}>
<AppRouter />
</Provider>
</React.StrictMode>
)
let hasRendered = false
const renderApp = () => {
if (!hasRendered) { //make sure app only renders one time
ReactDOM.render(jsx, document.getElementById('app'))
hasRendered = true
}
}
firebase.auth().onAuthStateChanged((user) => {
if (user) {
// Set initial store and db subscriptions
renderApp()
}
})
AppRouter.js
//Imports etc...
const AppRouter = ({}) => {
//...
return (
<React.Fragment>
//uses Router instead of BrowserRouter to use our own history and not the built in one
<Router history={history}>
<div className="myApp">
<Route path="">
<Sidebar ...props />
</Route>
//More routes here...
</div>
</Router>
</React.Fragment>
)
}
//...
export default connect(mapStateToProps, mapDispatchToProps)(AppRouter)
Sidebar.js
//Imports etc...
export const Sidebar = (props) => {
const onRender = (id, phase, actualDuration, baseDuration, startTime, commitTime) => {
if (id !== 'Sidebar') { return }
console.log('onRender', phase, actualDuration)
}
return (
<Profiler id="Sidebar" onRender={onRender}>
<React.Fragment>
{/* Contents of Sidebar */}
</React.Fragment>
</Profiler>
}
const getLang = state => (state.usersettings) ? state.usersettings.language : 'en'
const getMediaSize = state => (state.route) ? state.route.mediaSize : 'large'
const getNavigation = state => state.navigation
const getMyLang = createSelector(
[getLang], (lang) => console.log('Sidebar lang val changed') || lang
)
const getMyMediaSize = createSelector(
[getMediaSize], (mediaSize) => console.log('Sidebar mediaSize val changed') || mediaSize
)
const getMyNavigation = createSelector(
[getNavigation], (navigation) => console.log('Sidebar navigation val changed') || navigation
)
const mapStateToPropsMemoized = (state) => {
return {
lang: getMyLang(state),
mediaSize: getMyMediaSize(state),
navigation: getMyNavigation(state)
}
}
const areEqual = (prevProps, nextProps) => {
const areStatesEqual = _.isEqual(prevProps, nextProps)
console.log('Sidebar areStatesEqual', areStatesEqual)
return areStatesEqual
}
export default React.memo(connect(mapStateToPropsMemoized, mapDispatchToProps)(Sidebar),areEqual)
初始渲染看起来没问题,直到Sidebar navigation val changed
- 之后组件重新渲染了一大堆 - 为什么!?
Console output - initial render
onRender Sidebar mount 572
Sidebar mediaSize val changed
Profile Sidebar areEqual true
Sidebar navigation val changed
onRender Sidebar update 153
Sidebar navigation val changed
onRender Sidebar update 142
onRender Sidebar update 103
onRender Sidebar update 49
onRender Sidebar update 5
onRender Sidebar update 2
onRender Sidebar update 12
onRender Sidebar update 3
onRender Sidebar update 2
onRender Sidebar update 58
onRender Sidebar update 2
onRender Sidebar update 4
onRender Sidebar update 5
onRender Sidebar update 4
后续渲染不会影响 store 中映射到 props(位置)的任何部分,但组件仍在重新渲染。
Console output - subsequent render
Profile Sidebar areEqual true
onRender Sidebar update 76
onRender Sidebar update 4
我希望Sidebar
在初始加载期间在商店的装载/更新期间被记住并且只渲染/重新渲染几次。
为什么Sidebar
组件被渲染了这么多次?
亲切的问候/K