如何在react-redux的useSelector钩子上正确使用咖喱选择器功能?

时间:2019-08-12 15:41:56

标签: javascript reactjs redux react-redux react-hooks

我正在使用带有钩子的react-redux,并且我需要一个选择器,该选择器采用的参数不是prop。 documentation状态

  

选择器函数未接收到ownProps参数。然而,   道具可以通过闭合(请参见以下示例)或通过使用   一个咖喱的选择器。

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

这是我所做的并且似乎可以正常运行,但这是对的吗?

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>;
}

4 个答案:

答案 0 :(得分:4)

当选择器的返回值是新函数时,组件将始终在每次商店更改时重新呈现。

  

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一起使用的选择器中返回新功能(或任何新的非primitives)的操作效率极低。被称为。

  道具可以通过闭包(请参见下面的示例)或使用咖喱选择器来使用。

该文档的意思是:

  • 关闭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更改为shallow equality check

import { shallowEqual } from 'react-redux'

export function useShallowEqualSelector(selector) {
  return useSelector(selector, shallowEqual)
}

或者只是

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

如果您在选择器中执行昂贵的计算,或者数据被深层嵌套并且性能成为问题,请查看Olivier's answer which uses memoization

答案 1 :(得分:3)

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

首先,我创建一个用于创建选择器的函数,因为选择器取决于组件属性id,所以我希望每个组件实例具有一个新选择器

在待办事项或id属性未更改时,选择器将阻止组件重新渲染。

最近我使用useMemo是因为我不想每个组件实例有个选择器

您可以看到last example of the documentation以获得更多信息

// 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>;
}

答案 2 :(得分:1)

是的,这是完成的过程,简化的示例:

// Curried functions
const getStateById = state => id => state.todo.byId[id];

const getIdByState = id => state => state.todo.byId[id];

const SOME_ID = 42;

const TodoComponent = () => {
  // id from API
  const id = SOME_ID;

  // Curried
  const todoCurried = useSelector(getStateById)(id);
  const todoCurried2 = useSelector(getIdByState(id));

  // Closure
  const todoClosure = useSelector(state => state.todo.byId[id]);

  // Curried + Closure
  const todoNormal = useSelector(state => getStateById(state)(id));

  return (
    <>
      <span>{todoCurried.display}</span>
      <span>{todoCurried2.display}</span>
      <span>{todoClosure.display}</span>
      <span>{todoNormal.display}</span>
    </>
  );
};

完整示例:

Edit styled-antd-react-starter

答案 3 :(得分:0)

这是 TypeScript 的帮助器挂钩useParamSelector,它实现了Redux Toolkit的官方方法。

挂钩实现:

// Define types and create new hook 
export type ParametrizedSelector<A, R> = (state: AppState, arg: A) => R;
export const proxyParam: <T>(_: AppState, param: T) => T = (_, param) => param;
export function useParamSelector<A, R>(
  selectorCreator: () => ParametrizedSelector<A, R>,
  argument: A,
  equalityFn: (left: R, right: R) => boolean = shallowEqual
): R {
  const memoizedSelector = useMemo(() => {
    const parametrizedSelector = selectorCreator();
    return (state: AppState) => parametrizedSelector(state, argument);
  }, [typeof argument === 'object' ? JSON.stringify(argument) : argument]);
  return useSelector(memoizedSelector, equalityFn);
}

创建参数化选择器:

export const selectUserById = (): ParametrizedSelector<string, User> =>
  createSelector(proxyParam, selectAllUsers, (id, users) => users.find((it) => it.id === id));

并使用它:

const user = useParamSelector(selectUserById, 1001); // in components
const user = selectUserById()(getState(), 1001); // in thunks

您还可以将其与通过重新选择的createSelector创建的选择器一起使用。