useSelector 破坏与多次调用

Che*_*iex 27 reactjs react-redux react-hooks

最近我正在阅读 react-redux 文档https://react-redux.js.org/next/api/hooks 并且有一个与平等比较和更新相关的部分,它说:

多次调用 useSelector(),每次调用返回一个字段值。

第一种方法:

const { open, importId, importProgress } = useSelector((importApp) => importApp.productsImport);
Run Code Online (Sandbox Code Playgroud)

第二种方法:

const open = useSelector((importApp) => importApp.productsImport.open);
const importId = useSelector((importApp) => importApp.productsImport.importId );
const importProgress = useSelector((importApp) => importApp.productsImport.importProgress);
Run Code Online (Sandbox Code Playgroud)

那么有什么真正的区别吗?或者由于破坏 useSelector 钩子会在检查引用时遇到麻烦?

小智 26

只是为了奠定基础:当一个动作被分派时,你传递给的选择器useSelector()将被调用。如果它返回的值与上次调度 action 时返回的值不同,则组件将重新渲染。

破坏确实是错误的方法,但这里的最佳答案完全无关紧要。文档指的是选择器每次都创建一个新对象的场景,就像您在mapStateToProps()函数中所做的那样。这将导致组件在每次分派动作时重新渲染,无论该动作做什么,因为即使实际数据没有更改,选择器返回的值在技术上也是内存中的不同对象。在这种情况下,您需要担心严格相等和浅相等比较。但是,您的选择器并不是每次都创建一个新对象。如果分派的操作没有修改importApp.productsImport,它将是内存中与以前完全相同的对象,从而呈现所有这些没有实际意义的内容。

相反,这里的问题是您正在选择整个状态切片,而您实际上只关心该切片的一些特定属性。考虑到importApp.productsImport可能有其它特性除了刚刚openimportIdimportProgress。如果这些其他属性发生变化,那么您的组件将不必要地重新渲染,即使它没有引用它们。原因很简单:选择器返回importApp.productsImport,并且那个对象改变了。终极版无法知道的方式openimportIdimportProgress是你真正关心,因为唯一的属性,你没有选择那些性能; 您选择了整个对象

解决方案

因此,要选择多个属性而不进行不必要的重新渲染,您有两个选择:

  • 使用多个useSelector()挂钩,每个挂钩选择您商店中的一个属性。
  • 有一个useSelector()钩子和一个选择器,将商店中的多个属性组合成一个对象。你可以这样做:
    • 使用reselect 中的记忆选择器。
    • 只需编写一个函数,根据 的特定属性创建一个新对象state并返回它。如果你这样做,你会有大约严格的平等和浅相等比较担心。

为此,我觉得多个useSelector()钩子实际上是要走的路。文档特别提到

每次调用 useSelector() 都会创建一个对 Redux 存储的单独订阅。

但是,纯粹出于这个原因,与单个调用相比,多个调用是否真的会导致真正的性能损失,我认为还有待观察,在我看来,担心这可能是过度优化,除非你有一个巨大的应用程序数百或数千个订阅。如果您使用单个useSelector()钩子,那么此时您基本上只是在编写一个mapStateToProps函数,我觉得这会挫败一开始使用钩子的很多吸引力,尤其是在您编写 TypeScript 时。如果您随后想要对结果进行解构,那会使它变得更加麻烦。我也认为使用多个钩子绝对更符合钩子 API 的一般精神。


Shr*_*a S 13

当一个动作被分派到 Redux 存储时,useSelector() 只做一个严格的“===”引用比较。这不会进行浅层检查。

因此,如果您想从存储中检索多个值,则必须多次调用 useSelector(),每次调用都会从 state 中返回一个字段值。或者使用shallowEqualreact-redux

import { shallowEqual, useSelector } from 'react-redux'

const data = useSelector(state => state.something, shallowEqual)
Run Code Online (Sandbox Code Playgroud)

详细解释请参考https://react-redux.js.org/next/api/hooks#equality-comparisons-and-updates

  • 这是对的。这是一个沙箱来说明这一点:https://codesandbox.io/s/redux-useselector-object-destructuring-example-6up7h (5认同)

Ahm*_*san 7

如何选择多个状态::

const [village, timeZone, date] = useSelector((state) => [
    state.active_village,
    state.time_zone,
    state.date,
  ], shallowEqual);
Run Code Online (Sandbox Code Playgroud)

  • 它仍然会重新渲染组件 (2认同)

Vik*_*nia 5

这个答案没有太多技术细节。请查看其他答案以获得更好的理解。在这里,我仅提及用例场景。希望您会发现它有帮助:

假设:productsImport仅包含您提到的字段(open、importId、importProgress

1.假设您要使用 useSelector 获取单个组件中的所有字段,那么我会说您应该使用第一种方法。因为使用第二种方法将添加几行额外的代码,但没有其他好处。无论如何,每当任何字段的值更新时,您的组件都会重新渲染。

2.现在,假设您正在一个组件(Component1)中使用和更新importId,并在另一个组件(Component2)中使用和更新其余字段。而且,这些组件也应该呈现在同一窗口/屏幕上。那么你应该使用第二种方法

Component1:
    const importId = useSelector((importApp) => importApp.productsImport.importId );

Component2:
 const open = useSelector((importApp) => importApp.productsImport.open);
 const importProgress = useSelector((importApp) => importApp.productsImport.importProgress);
 
if you want to use only one selector:
const [open, importProgress] = useSelector((importApp) =>[importApp.productsImport.open, importApp.productsImport.importProgress], 
shallowEqual);
'Note: Here do not forget to use shallowEqual. Otherwise, your component will rerender on every useSelector() call.'
Run Code Online (Sandbox Code Playgroud)

如果您将在此处使用第一种方法,那么每次更新任何字段时,您的两个组件都会重新渲染。