Reactjs 使用react-redux、reselect和react.memo()记忆功能组件

Reactjs 使用react-redux、reselect和react.memo()记忆功能组件,reactjs,react-redux,memoization,reselect,Reactjs,React Redux,Memoization,Reselect,我已经在React JS 16.8.5和React Redux 3.7.2上构建了一个应用程序。当应用加载应用挂载时,将设置初始存储,并针对Firebase实时数据库设置数据库订阅。 该应用程序包含标题、侧栏和内容部分。 我已经实现了,以避免在道具更改时重新渲染,但是侧栏组件仍在重新渲染。 在React.memo中使用和一个areEqual比较函数,我可以看到,侧栏被渲染了好几次,尽管道具是相等的 app.js //Imports etc... const jsx = ( <React

我已经在React JS 16.8.5和React Redux 3.7.2上构建了一个应用程序。当应用加载应用挂载时,将设置初始存储,并针对Firebase实时数据库设置数据库订阅。 该应用程序包含标题、
侧栏和内容部分。
我已经实现了,以避免在道具更改时重新渲染,但是
侧栏
组件仍在重新渲染。 在React.memo中使用和一个
areEqual
比较函数,我可以看到,
侧栏
被渲染了好几次,尽管道具是相等的

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()
  }
})
//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)
//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.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()
  }
})
//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)
//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)
后续渲染不会影响映射到道具(位置)的存储的任何部分,但组件仍在重新渲染

控制台输出-后续渲染

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
Profile Sidebar areEqual true
onRender Sidebar update 76
onRender Sidebar update 4
我希望
侧栏
会被记忆,并且在初始加载期间,在装载/更新存储期间只渲染/重新渲染几次

为什么
侧栏
组件被渲染了这么多次


类/K

不需要React.memo,因为React redux connect将返回纯组件,仅当您更改传递的道具或在调度的操作导致状态发生任何更改后,该组件才会重新渲染

您的
MapStateTopsMoized
应该可以工作(请参阅更新),但最好是这样编写:

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);
更新

你的状态应该有效

我无法使用您的代码再现组件重新渲染。从mapState返回的对象每次都是一个新对象,但它的直接属性永远不会更改,因为选择器总是返回已记忆的结果。见下面的例子

const{useRef,useffect}=React;
常数{
供应商,
使用调度,
连接
使用选择器,
}=反应次数;
const{createStore}=Redux;
const{createSelector}=重新选择;
const state={someValue:2,unrelatedCounter:0};
//每个操作返回一个新的状态值
//永远不会改变,只有反击
常量减速机=(状态)=>({
……国家,
unrelatedCounter:state.unrelatedCounter+1,
});
const store=createStore(
减速器,
{…州},
窗口。\uuuu REDUX\u DEVTOOLS\u EXTENSION\uuuu&&
窗口。\uuuu REDUX\u开发工具\u扩展
);
//选择器
const selectSomeValue=(state)=>state.someValue;
//选择器仅在某些值更改时返回新对象
const selectA=createSelector(
[选择某些值],
()=>({value:'A'})//如果某些值发生更改,则返回新对象
);
const selectB=createSelector(
[选择某些值],
()=>({vale:'B'})//如果某些值发生更改,则返回新对象
);
const selectC=createSelector(
[选择某些值],
()=>({vale:'C'})//如果某些值发生更改,则返回新对象
);
常量计数器=()=>{
常量计数器=使用选择器(
(状态)=>state.unrelatedCounter
);
返回计数器:{Counter};
};
常量AppComponent=(道具)=>{
const dispatch=usedpatch();
常数r=useRef(0);
//因为state.someValue从不更改此组件
//永远不会被重新渲染
r、 电流++;
使用效果(
//每秒调度一个操作,这将创建一个新的
//状态但是状态。someValue永远不会改变
() => {
setInterval(()=>dispatch({type:88}),1000;
},
[dispatch]//dispatch从不更改,但linting工具不知道这一点
);
返回(
呈现{r.current}次
{JSON.stringify(props,未定义,2)}
);
};
常量mapStateToProps=(状态)=>{
返回{
A:选择A(州),
B:选择B(状态),
C:选择C(状态),
};
};
const App=connect(mapStateToProps)(应用组件);
ReactDOM.render(
,
document.getElementById('root'))
);


非常感谢HMR!我有点困惑,我们各自的“MapStateTopromemoided”之间的功能差异是什么。你能详细说明一下吗?非常感谢/K@Kermit更新的ansewr。当你说道具永远不会更改,那么组件不应该重新呈现时,请参阅我的更新答案,其中的示例代码显示,当来自重新选择的选择器的状态值没有更改时,选择器将返回已记忆的结果。非常感谢@HMR的澄清!(^u^)//K