使用react-redux,reselect和React.memo()来记住功能组件 [英] Memoize functional component using react-redux, reselect and React.memo()

查看:118
本文介绍了使用react-redux,reselect和React.memo()来记住功能组件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经在ReactJS 16.8.5和React-Redux 3.7.2上构建了一个应用程序.当应用程序加载应用程序挂载时,将设置初始存储,并针对Firebase实时数据库设置数据库订阅. 该应用程序包含标题,Sidebar和内容部分.
我已经实现了重新选择反应探查器API areEqual比较函数,我可以看到尽管道具相等,但Sidebar被渲染了几次.

I have built an app on ReactJS 16.8.5 and React-Redux 3.7.2. When the app loads the app mounts, initial store is set and database subscriptions are set up against a Firebase Realtime Database. The app contains a header, Sidebar and content section.
I have implemented reselect along with React.memo to avoid rerendring when props change, but the Sidebar component is still re-rendering. Using React profiler API and a areEqual comparison function in React.memo I can see that the Sidebar is being rendered several times although props are equal.

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之前,初始渲染看起来还可以-在此之后,该组件重新渲染了很多次-为什么!

Initial render looks ok up until Sidebar navigation val changed - after that the component re-renders a whole bunch of times - why!?

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

后续渲染不会影响商店中映射到道具(位置)的任何部分,但是组件仍在重新渲染.

The subsequent render does not affect any part of the store that is mapped to props (location), but component is still re-rendering.

Console output - subsequent render

Profile Sidebar areEqual true
onRender Sidebar update 76
onRender Sidebar update 4

我希望Sidebar会被记住,并且在初始加载期间仅在商店的安装/更新期间会渲染/重新渲染几次.

I expect Sidebar to be memoized and only render/re-render a few times during mount/update of store during initial load.

为什么Sidebar组件渲染了这么多次?

Why is the Sidebar component being rendered so many times?

亲切的问候/K

推荐答案

不需要React.memo,因为react-redux connect将返回一个纯组件,仅当您更改传递的道具或分派动作后才会重新渲染导致状态发生任何变化.

The React.memo is not needed because react-redux connect will return a pure component that will only re render if you change the props passed or after a dispatched action caused any changes in the state.

您的mapStateToPropsMemoized应该可以工作(请参阅更新),但是最好用这种方式编写:

Your mapStateToPropsMemoized should work (see update) but probalby better to write it this way:

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应该可以工作.

Your getState should work.

我无法使用您的代码重现组件重新呈现.从mapState返回的对象每次都是一个新对象,但是它的直接属性永远不会改变,因为选择器始终会返回记录的结果.参见下面的示例

I cannot reproduce component re rendering with your code. The object returned from mapState is a new object every time but it's direct properties never change because the selectors always return memoized result. See example below

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>

这篇关于使用react-redux,reselect和React.memo()来记住功能组件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆