我正在使用带有钩子的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>;
}
答案 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>
</>
);
};
完整示例:
答案 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
创建的选择器一起使用。