Javascript 重新选择-调用另一个选择器的选择器?

Javascript 重新选择-调用另一个选择器的选择器?,javascript,reactjs,reselect,Javascript,Reactjs,Reselect,我有一个选择器: const someSelector = createSelector( getUserIdsSelector, (ids) => ids.map((id) => yetAnotherSelector(store, id), ); // ^^^^^ (yetAnotherSelector expects 2 args) yetAnotherSelector是另一个选择器,

我有一个选择器:

const someSelector = createSelector(
   getUserIdsSelector,
   (ids) => ids.map((id) => yetAnotherSelector(store, id),
);                                      //     ^^^^^ (yetAnotherSelector expects 2 args)
yetAnotherSelector
是另一个选择器,它获取用户id-
id
并返回一些数据

但是,因为它是
createSelector
,所以我没有权限存储在其中(我不希望它作为一个函数,因为那时备忘录就不起作用了)

是否有办法访问
createSelector
中的存储?或者有没有其他的方法来处理

编辑: 我有一个功能:

const someFunc = (store, id) => {
    const data = userSelector(store, id);
              // ^^^^^^^^^^^^ global selector
    return data.map((user) => extendUserDataSelector(store, user));
                       //     ^^^^^^^^^^^^^^^^^^^^ selector
}
这种功能正在扼杀我的应用程序,导致一切重新渲染,让我发疯。谢谢你的帮助

!! 然而: 我做了一些基本的、定制的备忘录:

import { isEqual } from 'lodash';

const memoizer = {};
const someFunc = (store, id) => {
    const data = userSelector(store, id);
    if (id in memoizer && isEqual(data, memoizer(id)) {
       return memoizer[id];
    }

    memoizer[id] = data;
    return memoizer[id].map((user) => extendUserDataSelector(store, user));
}
而且它确实做到了这一点,但这不只是一个解决办法吗?

序言 我面临着与您相同的情况,不幸的是,没有找到一种有效的方法从另一个选择器的主体调用选择器

我说的是有效的方法,因为您总是可以有一个输入选择器,它传递整个状态(存储),但这将根据每个状态的更改重新计算选择器:

const someSelector = createSelector(
   getUserIdsSelector,
   state => state,
   (ids, state) => ids.map((id) => yetAnotherSelector(state, id)
)

方法 然而,对于下面描述的用例,我发现了两种可能的方法。我想你的情况也差不多,所以你可以了解一些情况

因此,情况如下:您有一个选择器,它通过id从存储中获取特定用户,选择器返回特定结构中的用户。比如说
getUserById
selector。现在一切都很好,尽可能简单。但是,当您希望通过ID获取多个用户并重用以前的选择器时,就会出现问题。让我们把它命名为
getUsersByIds
selector

1.始终使用数组作为输入ID值 第一种可能的解决方案是使用一个选择器,该选择器总是需要一个ID数组(
getUsersByIds
),第二个选择器重用前一个ID数组,但它只会得到一个用户(
getUserById
)。因此,当您只想从存储中获取1个用户时,必须使用
getUserById
,但必须传递一个只有一个用户id的数组

以下是实现方法:

import { createSelectorCreator, defaultMemoize } from 'reselect'
import { isEqual } from 'lodash'

/**
 * Create a "selector creator" that uses `lodash.isEqual` instead of `===`
 *
 * Example use case: when we pass an array to the selectors,
 * they are always recalculated, because the default `reselect` memoize function
 * treats the arrays always as new instances.
 *
 * @credits https://github.com/reactjs/reselect#customize-equalitycheck-for-defaultmemoize
 */
const createDeepEqualSelector = createSelectorCreator(
  defaultMemoize,
  isEqual
)

export const getUsersIds = createDeepEqualSelector(
  (state, { ids }) => ids), ids => ids)

export const getUsersByIds = createSelector(state => state.users, getUsersIds,
  (users, userIds) => {
    return userIds.map(id => ({ ...users[id] })
  }
)

export const getUserById = createSelector(getUsersByIds, users => users[0])
// Get 1 User by id
const user = getUserById(state, { ids: [1] })

// Get as many Users as you want by ids
const users = getUsersByIds(state, { ids: [1, 2, 3] }) 
export const getUsersByIds = createSelector(state => state.users, getUsersIds,
  (users, userIds) => {
    return userIds.map(id => _getUserById(users, id))
  }
)

export const getUserById = createSelector(state => state.users, (state, props) => props.id, _getUserById)

const _getUserById = (users, id) => ({ ...users[id]})
// Get 1 User by id
const user = getUserById(state, { id: 1 })

// Get as many Users as you want by ids
const users = getUsersByIds(state, { ids: [1, 2, 3] }) 
用法:

import { createSelectorCreator, defaultMemoize } from 'reselect'
import { isEqual } from 'lodash'

/**
 * Create a "selector creator" that uses `lodash.isEqual` instead of `===`
 *
 * Example use case: when we pass an array to the selectors,
 * they are always recalculated, because the default `reselect` memoize function
 * treats the arrays always as new instances.
 *
 * @credits https://github.com/reactjs/reselect#customize-equalitycheck-for-defaultmemoize
 */
const createDeepEqualSelector = createSelectorCreator(
  defaultMemoize,
  isEqual
)

export const getUsersIds = createDeepEqualSelector(
  (state, { ids }) => ids), ids => ids)

export const getUsersByIds = createSelector(state => state.users, getUsersIds,
  (users, userIds) => {
    return userIds.map(id => ({ ...users[id] })
  }
)

export const getUserById = createSelector(getUsersByIds, users => users[0])
// Get 1 User by id
const user = getUserById(state, { ids: [1] })

// Get as many Users as you want by ids
const users = getUsersByIds(state, { ids: [1, 2, 3] }) 
export const getUsersByIds = createSelector(state => state.users, getUsersIds,
  (users, userIds) => {
    return userIds.map(id => _getUserById(users, id))
  }
)

export const getUserById = createSelector(state => state.users, (state, props) => props.id, _getUserById)

const _getUserById = (users, id) => ({ ...users[id]})
// Get 1 User by id
const user = getUserById(state, { id: 1 })

// Get as many Users as you want by ids
const users = getUsersByIds(state, { ids: [1, 2, 3] }) 
2.将选择器的主体作为独立函数重用 这里的想法是在一个独立的函数中分离选择器主体的公共和可重用部分,这样该函数就可以从所有其他选择器中调用

以下是实现方法:

import { createSelectorCreator, defaultMemoize } from 'reselect'
import { isEqual } from 'lodash'

/**
 * Create a "selector creator" that uses `lodash.isEqual` instead of `===`
 *
 * Example use case: when we pass an array to the selectors,
 * they are always recalculated, because the default `reselect` memoize function
 * treats the arrays always as new instances.
 *
 * @credits https://github.com/reactjs/reselect#customize-equalitycheck-for-defaultmemoize
 */
const createDeepEqualSelector = createSelectorCreator(
  defaultMemoize,
  isEqual
)

export const getUsersIds = createDeepEqualSelector(
  (state, { ids }) => ids), ids => ids)

export const getUsersByIds = createSelector(state => state.users, getUsersIds,
  (users, userIds) => {
    return userIds.map(id => ({ ...users[id] })
  }
)

export const getUserById = createSelector(getUsersByIds, users => users[0])
// Get 1 User by id
const user = getUserById(state, { ids: [1] })

// Get as many Users as you want by ids
const users = getUsersByIds(state, { ids: [1, 2, 3] }) 
export const getUsersByIds = createSelector(state => state.users, getUsersIds,
  (users, userIds) => {
    return userIds.map(id => _getUserById(users, id))
  }
)

export const getUserById = createSelector(state => state.users, (state, props) => props.id, _getUserById)

const _getUserById = (users, id) => ({ ...users[id]})
// Get 1 User by id
const user = getUserById(state, { id: 1 })

// Get as many Users as you want by ids
const users = getUsersByIds(state, { ids: [1, 2, 3] }) 
用法:

import { createSelectorCreator, defaultMemoize } from 'reselect'
import { isEqual } from 'lodash'

/**
 * Create a "selector creator" that uses `lodash.isEqual` instead of `===`
 *
 * Example use case: when we pass an array to the selectors,
 * they are always recalculated, because the default `reselect` memoize function
 * treats the arrays always as new instances.
 *
 * @credits https://github.com/reactjs/reselect#customize-equalitycheck-for-defaultmemoize
 */
const createDeepEqualSelector = createSelectorCreator(
  defaultMemoize,
  isEqual
)

export const getUsersIds = createDeepEqualSelector(
  (state, { ids }) => ids), ids => ids)

export const getUsersByIds = createSelector(state => state.users, getUsersIds,
  (users, userIds) => {
    return userIds.map(id => ({ ...users[id] })
  }
)

export const getUserById = createSelector(getUsersByIds, users => users[0])
// Get 1 User by id
const user = getUserById(state, { ids: [1] })

// Get as many Users as you want by ids
const users = getUsersByIds(state, { ids: [1, 2, 3] }) 
export const getUsersByIds = createSelector(state => state.users, getUsersIds,
  (users, userIds) => {
    return userIds.map(id => _getUserById(users, id))
  }
)

export const getUserById = createSelector(state => state.users, (state, props) => props.id, _getUserById)

const _getUserById = (users, id) => ({ ...users[id]})
// Get 1 User by id
const user = getUserById(state, { id: 1 })

// Get as many Users as you want by ids
const users = getUsersByIds(state, { ids: [1, 2, 3] }) 
结论 方法#1.的样板文件较少(我们没有独立的函数),实现简洁

方法#2.更易于重用。想象一下这样的情况,当我们调用选择器时没有用户id,但我们从选择器的主体作为关系获取它。在这种情况下,我们可以很容易地重用独立函数。下面是一个伪例子:

export const getBook = createSelector(state => state.books, state => state.users, (state, props) => props.id,
(books, users, id) => {
  const book = books[id]
  // Here we have the author id (User's id)
  // and out goal is to reuse `getUserById()` selector body,
  // so our solution is to reuse the stand-alone `_getUserById` function.
  const authorId = book.authorId
  const author = _getUserById(users, authorId)

  return {
    ...book,
    author
  }
}
为了你的someFunc案子 对于您的特定情况,我将创建一个选择器,它本身返回一个扩展器

就是这个,

const someFunc = (store, id) => {
    const data = userSelector(store, id);
              // ^^^^^^^^^^^^ global selector
    return data.map((user) => extendUserDataSelector(store, user));
                       //     ^^^^^^^^^^^^^^^^^^^^ selector
}
我会写:

const extendUserDataSelectorSelector = createSelector(
  selectStuffThatExtendUserDataSelectorNeeds,
  (state) => state.something.else.it.needs,
  (stuff, somethingElse) =>
    // This function will be cached as long as
    // the results of the above two selectors
    // does not change, same as with any other cached value.
    (user) => {
      // your magic goes here.
      return {
        // ... user with stuff and somethingElse
      };
    }
);
然后
someFunc
将变成:

const someFunc = createSelector(
  userSelector,
  extendUserDataSelectorSelector,
  // I prefix injected functions with a $.
  // It's not really necessary.
  (data, $extendUserDataSelector) =>
    data.map($extendUserDataSelector)
);
我称之为reifier模式,因为它创建了一个预先绑定到当前状态的函数,该函数接受单个输入并将其具体化。我通常用它来通过id获取东西,因此使用了“具体化”。我也喜欢说“具体化”,老实说,这是我称之为“具体化”的主要原因

但是你的情况 在这种情况下:

import { isEqual } from 'lodash';

const memoizer = {};
const someFunc = (store, id) => {
    const data = userSelector(store, id);
    if (id in memoizer && isEqual(data, memoizer(id)) {
       return memoizer[id];
    }

    memoizer[id] = data;
    return memoizer[id].map((user) => extendUserDataSelector(store, user));
}
基本上就是这样。如果您计划在全球层面实施每一个ID记忆,您可能会考虑。
import createCachedSelector from 're-reselect';

const someFunc = createCachedSelector(
  userSelector,
  extendUserDataSelectorSelector,
  (data, $extendUserDataSelector) =>
    data.map($extendUserDataSelector)
// NOTE THIS PART DOWN HERE!
// This is how re-reselect gets the cache key.
)((state, id) => id);
或者你可以用蝴蝶结把你记忆中的多重选择器生成器包起来,称它为
createCachedSelector
,因为它基本上是一样的

编辑:为什么返回函数 另一种方法是只选择运行
extendUserDataSelector
计算所需的所有适当数据,但这意味着将希望使用该计算的所有其他函数公开到其接口。通过返回只接受单个
用户
基本数据的函数,可以保持其他选择器的界面干净

编辑:关于集合 上述实现目前易受攻击的一个问题是,如果
extendUserDataSelectorSelector
的输出因其自身的依赖项选择器的更改而更改,但是
userSelector
获取的用户数据没有更改,并且由
extendUserSelectorSelector
创建的实际计算实体也没有更改。在这种情况下,您需要做两件事:

  • 多重记忆
    extendUserDataSelectorSelector
    返回的函数。我建议将其提取到单独的全局记忆函数中
  • 包装
    someFunc
    ,以便在返回数组时,将该数组元素与上一个结果进行比较,如果它们具有相同的元素,则返回上一个结果
  • 编辑:避免如此多的缓存 如上所示,全局级别的缓存当然是可行的,但如果您考虑到两种其他策略来解决问题,则可以避免这种情况:

  • 不要急于扩展数据,将其延迟到实际呈现数据本身的每个React(或其他视图)组件
  • 不要急于将ids/base对象列表转换为扩展版本,而是让父对象将这些ids/base对象传递给子对象
  • 一开始在我的一个主要工作项目中,我没有遵循这些原则,我希望我能做到。事实上,我后来不得不走全局记忆路线,因为这比重构所有视图更容易修复,这是应该做的,但我们目前缺乏时间/预算

    编辑2(或者我猜是4?):Re关于收集。1:多重记忆扩展器 注意:在阅读本部分之前,假定传递给扩展器的基本实体将具有某种类型的
    id
    属性,可用于识别