如何通过 react-redux 的 useSelector 钩子正确使用柯里化的选择器函数?

新手上路,请多包涵

我正在使用带钩子的 react-redux,我需要一个选择器,它接受一个不是 prop 的参数。 文档 说明

选择器函数不接收 ownProps 参数。但是,可以通过闭包(参见下面的示例)或使用柯里化选择器来使用 props。

但是,他们没有提供示例。如文档中所述,咖喱的正确方法是什么?

这就是我所做的并且似乎有效,但这是对的吗?从 useSelector 函数返回一个函数是否有影响(它似乎永远不会重新渲染?)

 // selectors
export const getTodoById = state => id => {
  let t = state.todo.byId[id];
  // add display name to todo object
  return { ...t, display: getFancyDisplayName(t) };
};

const getFancyDisplayName = t => `${t.id}: ${t.title}`;

// example component
const TodoComponent = () => {
   // get id from react-router in URL
   const id = match.params.id && decodeURIComponent(match.params.id);

   const todo = useSelector(getTodoById)(id);

   return <span>todo.display</span>;
}

原文由 user210757 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 674
2 个回答

When the return value of a selector is a new function, the component will always re-render on each store change.

useSelector() 使用严格 === 默认引用相等性检查,而不是浅层相等性

您可以使用一个超级简单的选择器来验证这一点:

 const curriedSelector = state => () => 0;

let renders = 0;
const Component = () => {
  // Returns a new function each time
  // triggers a new render each time
  const value = useSelector(curriedSelector)();
  return `Value ${value} (render: ${++renders})`;
}

即使 value 始终是 0 ,该组件将在每次存储操作时重新呈现,因为 useSelector 调用我们不知道的函数 真正的价值。

但是,如果我们确保 useSelector 收到最终的 value 而不是函数,那么组件只会在真实的 value 上呈现

const curriedSelector = state => () => 0;

let renders = 0;
const Component = () => {
  // Returns a computed value
  // triggers a new render only if the value changed
  const value = useSelector(state => curriedSelector(state)());
  return `Value ${value} (render: ${++renders})`;
}


结论是它可以工作,但是每次调用它时从与 useSelector 一起使用的选择器 返回一个新函数(或任何新的非 原语)是非常低效的

props 可以通过闭包(参见下面的示例)或使用柯里化选择器来使用。

该文档意味着:

  • 关闭 useSelector(state => state.todos[props.id])
  • 咖喱 useSelector(state => curriedSelector(state)(props.id))

connect 始终可用,如果您稍微更改选择器,它可以同时用于两者。

 export const getTodoById = (state, { id }) => /* */

const Component =  props => {
  const todo = useSelector(state => getTodoById(state, props));
}
// or
connect(getTodoById)(Component)


请注意,由于您要从选择器返回一个对象,因此您可能希望将 useSelector 的默认相等性检查更改为 浅层相等性检查

>  import { shallowEqual } from 'react-redux'
>
> export function useShallowEqualSelector(selector) {
>   return useSelector(selector, shallowEqual)
> }
>
> ```

要不就

const todo = useSelector(state => getTodoById(state, id), shallowEqual);

”`

如果您在选择器中执行昂贵的计算或者数据嵌套很深并且性能成为问题,请查看 Olivier 使用 memoization 的答案

原文由 Emile Bergeron 发布,翻译遵循 CC BY-SA 4.0 许可协议

这是一个解决方案,它使用 memoïzation 不在每次商店更改时重新渲染组件:

首先我创建了一个函数来制作选择器,因为选择器取决于组件属性 id ,所以我想为 每个组件实例创建一个新的选择器

当 todo 或 id 属性没有改变时,选择器将阻止组件重新渲染。

最后我使用 useMemo 因为我不希望 每个组件实例有一个以上的选择器

您可以查看 文档的最后一个示例以 获取更多信息

// selectors
const makeGetTodoByIdSelector = () => createSelector(
   state => state.todo.byId,
   (_, id) => id,
   (todoById, id) => ({
       ...todoById[id],
       display: getFancyDisplayName(todoById[id])
   })
);

const getFancyDisplayName = t => `${t.id}: ${t.title}`;

// example component
const TodoComponent = () => {
  // get id from react-router in URL
  const id = match.params.id && decodeURIComponent(match.params.id);

  const getTodoByIdSelector = useMemo(makeGetTodoByIdSelector, []);

  const todo = useSelector(state => getTodoByIdSelector(state, id));

  return <span>todo.display</span>;
}

原文由 Olivier Boissé 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题