使用 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
HOC,Wrapper hell 正在发生。如果由于性能原因我们必须将React.memo
HOC 与useSelector
一起使用,那么简单地使用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,因为它们引用了不同的数组,而 connect
ed 组件将不会重新呈现。现在,为了使 useSelector
行为相似而不重新渲染,您可以这样做:
const object = useSelector(state => state.object, shallowEqual)
您可以从react-redux
导入shallowEqual
。或者通过使用reselect
库来使用该状态的记忆版本:
const makeGetObject = () => createSelector(state => state.object, object => object)
并将其添加到您的选择器中,例如:const object = useSelector(state => 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 永远不会改变