使用 React.memo 与连接的 useSelector

Posted

技术标签:

【中文标题】使用 React.memo 与连接的 useSelector【英文标题】:useSelector with React.memo vs connect 【发布时间】:2019-10-26 07:58:36 【问题描述】:

参考链接。 https://react-redux.js.org/next/api/hooks#performance

我理解useSelector 钩子的好处是避免包装地狱。由于使用了connect HOCWrapper hell 正在发生。如果由于性能原因我们必须将React.memo HOCuseSelector 一起使用,那么简单地使用connect HOC 会更好吗?因为在任何情况下,我们都必须处于包装器的地狱。如果地狱不是connect,那么就是React.memo

请任何人解释React.memo 相对于connect 的好处。

【问题讨论】:

【参考方案1】:

我刚刚自定义了 useSelector 钩子来避免这种情况,效果很好

import  useSelector, useDispatch  from 'react-redux'
import  _lodash  from '../../../lodash'

export const useCloneSelector = (selector = (obj) => obj) => 
  const selectWithClonedState = (state = , ...others) => selector(_lodash.cloneDeep(state), ...others)
  return useSelector(selectWithClonedState, _lodash.isEqual)


export  useDispatch, useSelector 

【讨论】:

【参考方案2】:

首先,虽然 React.memo 是一个 HOC,但它并没有像 connect 那样创建相同的嵌套,这很有趣。我已经创建了一个测试代码:

    import React from "react";
    import ReactDOM from "react-dom";
    import connect, Provider from 'react-redux'
    import  createStore  from 'redux'
    import "./styles.css";
    
    const MemoComponent = React.memo(function MyMemo() 
      return <div>Memo</div>;
    );
    
    const ConnectedComponent = connect(null,null)(function MyConnected() 
      return <div>ReduxConnectComponent</div>;
    )
    
    const store = createStore(()=>,)
    
    
    function App() 
      return (
        <Provider store=store>
          <MemoComponent />
          <ConnectedComponent/>
        </Provider>
      );
    
    
    const rootElement = document.getElementById("root");
    ReactDOM.render(<App />, rootElement);

这是渲染的结构:

我们可以看到连接的内容被渲染得更深了。

其次,文档说:

默认情况下,在调度操作后运行选择器函数时,将执行所选值的参考相等比较,并且如果所选值更改,则仅导致组件重新呈现。但是,与 connect() 不同的是,useSelector() 不会由于其父级重新渲染而阻止组件重新渲染,即使组件的 props 没有改变。

这意味着当 store 的无关部分发生变化时,useSelector 的组件将不会被重新渲染。这是优化中最重要的部分。现在是否使用 React.memo 进行优化完全取决于您的决定,并且在大多数情况下,根本不需要它。我们仅在组件渲染成本非常高的情况下使用 React.memo。

总而言之,连接到商店需要连接包装器。有了 useSelector,我们就不必再包装了。在极少数情况下,当我们需要优化一些繁重的组件时,我们仍然需要使用 React.memo 进行包装。 React.memo 的工作也是由 connect 完成的,但在大多数情况下,这是过早的优化。

【讨论】:

你的意思是 useSelector 的问题,如果父组件重新渲染,不会阻止重新渲染,不会被 memo hoc 解决。请确认我是否理解正确。 没有。我认为 React.memo 将通过这种重新渲染来解决问题。我只是说这个问题非常罕见,是一个真正的问题。仅在某些情况下,组件渲染缓慢或渲染非常频繁。 我注意到 React 社区不再鼓励使用 redux。可能是我错了。 在引入 hooks 和 suspense 之后,现在 react 不会阻止在 react 组件中使用副作用。 @SheikhAbdulWahid 我自己也注意到了这一点。但这可能是因为它被过度使用了,现在在 React 中有更简单的方法来做事。但是,对于具有复杂服务器交互的非常复杂的应用程序,我仍然会选择 Redux,因为它独特地有助于观察和控制应用程序中发生的事情 + saga 在这些设置中是一个非常有用的工具。【参考方案3】:

我一直试图得到一个答案很长一段时间,但我得到的答案并不清楚。虽然 Redux 文档中的理论并不复杂:useSelector 使用严格相等 === 而 connect 使用浅相等来确定。因此,在这两种情况下,如果您从 Redux 状态(数字、字符串、布尔值)“拉取”一个原始值,您将得到相同的结果。如果值未更改,则任何组件都不会重新呈现。如果您正在“拉”非基元(数组或对象)并且两种情况下的值都没有改变(useSelector、connect),那么使用useSelector 的组件仍然会重新渲染,当然[] === [] 将始终是false,因为它们引用了不同的数组,而 connected 组件将不会重新呈现。现在,为了使 useSelector 行为相似而不重新渲染,您可以这样做: const object = useSelector(state =&gt; state.object, shallowEqual) 您可以从react-redux 导入shallowEqual。或者通过使用reselect 库来使用该状态的记忆版本:

const makeGetObject = () => createSelector(state => state.object, object => object)

并将其添加到您的选择器中,例如:const object = useSelector(state =&gt; state.object, makeGetObject); 当我试图了解它的底部时,我已经创建了这个代码框(检查 WithUseSelector 组件中的 cmets):useSelector vs connect()

【讨论】:

不,这个答案是完全错误的。在“拉”未更改的非基元的情况下,useSelector 也不会导致组件重新渲染,因为 store 将返回相同的引用值。 不,你的例子展示了完全不同的东西。在评论您的回复之前,我已经自己证明了这一点。所以请不要打扰。 WithUseSelector 组件之所以被重新渲染,是因为父组件(App)正在重新渲染。另一方面connect HOC有其性能优化。如果我们想获得与 HOC 相同的好处,我们必须在使用 useSelector 时添加 React.useMemo。您可以查看文档(react-redux.js.org/api/hooks#performance) useSelector 的选择器 Fn 没有 useCallback 将在组件重新渲染时被调用,因为功能组件将在每次渲染时创建新函数。并且 useSelector 也会在任何 redux store 的值发生变化时被调用,因为选择器 Fn 已经订阅了 redux store。但它不会触发组件重新渲染,除非它的返回值没有改变。 "useSelector 使用严格相等 ===" 是对的,但是 useSelector 本身不会在“拉”未更改的引用类型值的情况下触发重新渲染,因为同样,store 将返回相同参考价值。除非您没有使用触发对象副本的方法,例如 .map、.filter、.slice 或其他任何东西,否则引用将是相同的。您可以在此处参考 CAUTION 部分。 redux.js.org/tutorials/fundamentals/…

以上是关于使用 React.memo 与连接的 useSelector的主要内容,如果未能解决你的问题,请参考以下文章

React性能优化之memo,useMemo,useCallback的使用与区别

React.memo prevProps 总是与 nextProps 不同,即使 props 永远不会改变

重构 pure 还是 React.memo?

React.memo 不适用于功能组件和谷歌地图

无法在 React Native 应用程序中使用 React.memo

为啥我的标题每次渲染都已经使用 React.memo?