useSelector 解构与多次调用
Posted
技术标签:
【中文标题】useSelector 解构与多次调用【英文标题】:useSelector destructuring vs multiple calls 【发布时间】:2020-03-23 03:03:48 【问题描述】:最近我在阅读 react-redux 文档https://react-redux.js.org/next/api/hooks 还有一个与平等比较和更新相关的部分,其中说:
多次调用useSelector(),每次调用返回一个字段值。
第一种方法:
const open, importId, importProgress = useSelector((importApp) => importApp.productsImport);
第二种方法:
const open = useSelector((importApp) => importApp.productsImport.open);
const importId = useSelector((importApp) => importApp.productsImport.importId );
const importProgress = useSelector((importApp) => importApp.productsImport.importProgress);
那么有什么真正的区别吗?或者由于解构 useSelector 钩子会在检查引用时遇到麻烦?
【问题讨论】:
【参考方案1】:从性能的角度来看,第一种方法更好。 在引擎盖下,破坏代码被转译为
var temp = useSelector((importApp) => importApp.productsImport);
var open = temp.open;
var importId = temp.importId;
var importProgress = temp.importProgress;
在第二种方法中,商店被访问了 3 次,操作成本更高。
【讨论】:
【参考方案2】:当一个动作被分派到 Redux 存储时,useSelector() 只进行严格的“===”引用比较。这不会进行浅层检查。
因此,如果您想从存储中检索多个值,则必须多次调用 useSelector(),每次调用都会从 state 中返回单个字段值。或者在react-redux
中使用shallowEqual
:
import shallowEqual, useSelector from 'react-redux'
const data = useSelector(state => state.something, shallowEqual)
详细解释请参考https://react-redux.js.org/next/api/hooks#equality-comparisons-and-updates。
【讨论】:
这是正确的。这是一个沙箱来说明这一点:codesandbox.io/s/… @sallf 感谢您创建示例。非常有用。【参考方案3】:只是为了打好基础:当一个动作被调度时,你传递给useSelector()
的选择器将被调用。如果它返回的值与上次调度动作时返回的值不同,组件将重新渲染。
破坏确实是错误的方法,但这里的最佳答案完全无关紧要。文档指的是选择器每次都创建一个新对象的场景,就像您在 mapStateToProps()
函数中所做的那样。这将导致组件在每次调度动作时重新渲染,无论该动作做什么,因为选择器返回的值在技术上是内存中的不同对象,即使实际数据没有改变。 在那种情况下,您需要担心严格相等和浅层相等比较。但是,您的选择器不会每次都创建一个新对象。如果调度的操作没有修改importApp.productsImport
,它将是内存中与以前完全相同的对象,呈现所有这些没有意义。
相反,这里的问题是您选择了整个状态切片,而您实际上只关心该切片的一些特定属性。考虑importApp.productsImport
可能除了open
、importId
和importProgress
之外还有其他属性。如果这些其他属性发生变化,那么您的组件将不必要地重新渲染,即使它没有引用它们。原因很简单:选择器返回importApp.productsImport
,并且该对象发生了变化。 Redux 无法知道open
、importId
和importProgress
是您真正关心的唯一属性,因为您没有选择这些属性;您选择了整个对象。
解决方案
因此,要选择多个属性而不进行不必要的重新渲染,您有两种选择:
使用多个useSelector()
挂钩,每个挂钩选择您商店中的一个属性。
拥有一个 useSelector()
挂钩和一个选择器,可将商店中的多个属性组合到一个对象中。你可以这样做:
使用来自reselect 的记忆选择器。
只需编写一个函数,从state
的特定属性创建一个新对象并返回它。如果您这样做了,那么您将不得不担心严格相等和浅相等比较。
为此,我觉得多个useSelector()
钩子实际上是要走的路。文档特别提到了这一点
对 useSelector() 的每次调用都会创建一个对 Redux 存储的单独订阅。
但我认为,与纯粹出于这个原因的单个调用相比,多个调用是否真的会导致真正的性能损失,还有待观察,在我看来,担心这可能是过度优化,除非你有拥有数百或数千个订阅的大型应用程序。如果您使用单个 useSelector()
钩子,那么此时您基本上只是在编写一个 mapStateToProps
函数,我觉得它破坏了使用钩子开始的很多魅力,尤其是如果您重新编写 TypeScript。如果你然后想要对结果进行解构,那就更麻烦了。我还认为使用多个钩子肯定更符合 Hooks API 的一般精神。
【讨论】:
非常感谢您的详尽回答。你是一个救生员。【参考方案4】:如何选择多个状态 ::
const [village, timeZone, date] = useSelector((state) => [
state.active_village,
state.time_zone,
state.date,
]);
【讨论】:
它仍然会重新渲染组件【参考方案5】:这个答案没有很多技术细节。请浏览其他答案以更好地理解。在这里,我只提到用例场景。希望对您有所帮助:
假设:productsImport只有你提到的字段(open、importId、importProgress)
1. 假设您将使用 useSelector 获取单个组件中的所有字段,那么我会说您应该使用 First Approach。因为使用 Second Approach 将添加几行额外的代码而没有其他好处。无论如何,只要有任何字段的值更新,您的组件就会重新渲染。
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.'
如果您将在此处使用 First Approach,那么您的两个组件都会在您每次更新任何字段时重新渲染。
【讨论】:
以上是关于useSelector 解构与多次调用的主要内容,如果未能解决你的问题,请参考以下文章
在我的权限上下文提供程序的函数错误中调用了 React 钩子“useSelector”
我应该使用 useselector/useDispatch 而不是 mapStateToProps