我已经在ReactJS 16.8.5和ReactiveRedux3.7.2上构建了一个应用程序。当应用程序加载应用程序挂载时,设置初始存储,并针对Firebase实时数据库设置数据库订阅。该应用程序包含一个标题、Sidebar和内容部分。
我已经实现了重选和React.memo,以避免当道具发生变化时重定向,但是Sidebar组件仍然在重新呈现。使用反应性分析器API和React.memo中的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后续呈现不影响存储中映射到支持(位置)的任何部分,但组件仍在重新呈现。
Console output - subsequent render
Profile Sidebar areEqual true
onRender Sidebar update 76
onRender Sidebar update 4我希望Sidebar会被回传,并且在初始加载期间只在存储的挂载/更新期间渲染/重新呈现几次。
为什么Sidebar组件被呈现了这么多次?
亲切问候/K
发布于 2020-06-01 11:59:54
不需要React.memo,因为将返回一个纯组件,只有在更改传递的道具或在分派操作导致状态更改之后才会重新呈现。
您的mapStateToPropsMemoized应该工作(请参阅更新),但最好用这种方式编写它:
const mapStateToPropsMemoized = createSelector(
getMyLang,
getMyMediaSize,
getMyNavigation,
(lang, mediaSize, navigation) => ({
lang,
mediaSize,
navigation,
})
);
//using react.redux connect will return a pure component and passing that
// to React.memo should cause an error because connect does not return a
// functional component.
export default connect(
mapStateToPropsMemoized,
mapDispatchToProps
)(Sidebar);更新
你的getState应该能工作。
我不能用您的代码重新呈现组件。每次从mapState返回的对象都是一个新对象,但是它的直接属性不会改变,因为选择器总是返回回传的结果。见下面的例子
const { useRef, useEffect } = React;
const {
Provider,
useDispatch,
connect,
useSelector,
} = ReactRedux;
const { createStore } = Redux;
const { createSelector } = Reselect;
const state = { someValue: 2, unrelatedCounter: 0 };
//returning a new state every action someValue
// never changes, only counter
const reducer = (state) => ({
...state,
unrelatedCounter: state.unrelatedCounter + 1,
});
const store = createStore(
reducer,
{ ...state },
window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__()
);
//selectors
const selectSomeValue = (state) => state.someValue;
//selectors only return a new object if someValue changes
const selectA = createSelector(
[selectSomeValue],
() => ({ value: 'A' }) //returns new object if some value changes
);
const selectB = createSelector(
[selectSomeValue],
() => ({ vale: 'B' }) //returns new object if some value changes
);
const selectC = createSelector(
[selectSomeValue],
() => ({ vale: 'C' }) //returns new object if some value changes
);
const Counter = () => {
const counter = useSelector(
(state) => state.unrelatedCounter
);
return <h4>Counter: {counter}</h4>;
};
const AppComponent = (props) => {
const dispatch = useDispatch();
const r = useRef(0);
//because state.someValue never changes this component
// never gets re rendered
r.current++;
useEffect(
//dispatch an action every second, this will create a new
// state but state.someValue never changes
() => {
setInterval(() => dispatch({ type: 88 }), 1000);
},
[dispatch] //dispatch never changes but linting tools don't know that
);
return (
<div>
<h1>Rendered {r.current} times</h1>
<Counter />
<pre>{JSON.stringify(props, undefined, 2)}</pre>
</div>
);
};
const mapStateToProps = (state) => {
return {
A: selectA(state),
B: selectB(state),
C: selectC(state),
};
};
const App = connect(mapStateToProps)(AppComponent);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script>
<div id="root"></div>
https://stackoverflow.com/questions/61405482
复制相似问题