开发者问题收集

根据 id 重新选择 createSelector

2021-05-26
3823

我在我的 React 项目中使用 reselect 库。

我创建了 Posts 选择器,它工作正常。这是代码

// selectors.ts

const getPosts = (state: RootState) => state.posts.posts;

export const getPostsSelector = createSelector(getPosts, (posts) => posts);

我在我的页面上这样调用它

// SomePage.tsx    
const posts = useSelector(getPostsSelector);

现在我需要通过 id 获取帖子。我想这样做:

// selectors.ts

const getPostDetail = (state: RootState) =>
  state.posts.posts.entities[ID];

export const getPostsById = createSelector(
  getPostDetail,
  (detail) => detail
);

并在页面上调用它:

const PostDetail = useSelector(getPostsById);

我有两个问题:

  • 获取单个帖子的方法正确吗?
  • 如果是,如何传递帖子的 ID,如果不是,如何以正确的方式处理?
2个回答

这里的问题是, react-redux 作为 HOC 具有 connect(...) 函数,该函数允许选择器为 (state, props) => ... ,而 useSelector 仅传递 (state) => ... 。 ... 。

为了仍然允许传递 props,您可以实现 useSelectorWithProps

function useSelectorWithProps (selector, props, deps, equalityFn) {
  const selectorFn = useCallback(state => selector(state, props), deps)
  return useReduxSelector(
    selectorFn,
    equalityFn
  )
}

这样您就可以执行以下操作:

// selectors.js

const selectPosts = (state) => state.posts.posts;

const selectPostById = createSelector(
  selectPosts,
  (_, props) => props.id,
  (posts, id) => posts[id]
)

// Or make a utility function for selecting the prop
const selectProp = (key, orDefault) => (_, props) => (props[key] || orDefault)

const selectPostById = createSelector(
  selectPosts,
  selectProp('id'),
  (posts, id) => posts[id]
)

// but don't do this, as it will de-optimize the memoization of reselect.
// `{id: 1} !== {id: 1}` and thus causes it to recalculate the selector
// (Keyword: referential equality)
const selectPostById = createSelector(
  selectPosts,
  (_, props) => props,
  (posts, { id }) => posts[id]
)
// component.js
function Component(props) {
  const value = useSelectorWithProps(
    selectPostById,
    { id: props.id }, // pass an object
    [ props.id ] // pass dependencies of the props-object
  )
}

这是我在我工作的项目中实现它的方式。

这允许您的完整“选择器逻辑”仍然存在于您的选择器中,从而更容易测试,并且不必在钩子中编写更少的粘合代码。

它还允许您对 connect(...)useSelectorWithProps(..) 使用相同的选择器。

DoXicK
2021-05-26

按特定 id 进行选择实际上 是正确的方法。 useSelector 钩子不允许将除 state 之外的内容传递给选择器。

一种迂回的解决方案是将 特定 帖子 id 也存储到状态中并选择它。缺点是帖子在初始渲染时将不可用,并且您需要分派(从 useEffect 钩子或回调)一个操作来存储您想要的帖子 id

const getPostById = createSelector(
  [getPostsSelector, getPostId],
  (posts, id) => posts.find(post => post.id === id);
);

用法:

const postById = useSelector(getPostById);

...

dispatch(setPostId(postId)); // store the id

由于您无法创建选择器来直接按 id 返回特定帖子,因此我建议创建一个选择器,该选择器返回帖子的派生状态对象,以便更快地进行查找,即地图或对象。

示例:

const postDetailMap = createSelector(
  [getPosts],
  posts => posts.reduce((posts, post) => ({
    ...posts,
    [post.id]: post,
  }), {}),
);

用法:

const postsMap = useSelector(postDetailMap);

const specificPost = postMap[postId];

您也许能够将其抽象为自定义钩子不过。

const useGetPostById = id => {
  const postsMap = useSelector(postDetailMap);
  return postsMap[id];
}

用法:

const post = useGetPostById(postId);
Drew Reese
2021-05-26