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

2024-01-24

我有一个选择器:

const someSelector = createSelector(
   getUserIdsSelector,
   (ids) => ids.map((id) => yetAnotherSelector(store, id),
);                                      //     ^^^^^ (yetAnotherSelector expects 2 args)

That yetAnotherSelector是另一个选择器,它采用用户 ID -id并返回一些数据。

However,因为它是createSelector,我无权存储在其中(我不希望它作为一个函数,因为那样记忆就不起作用)。

有没有办法以某种方式访问​​商店内部createSelector?或者还有其他方法可以处理吗?

EDIT:

我有一个功能:

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

And it does窍门,但这不只是一种解决方法吗?


对于您的 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
      };
    }
);

Then someFunc会成为:

const someFunc = createSelector(
  userSelector,
  extendUserDataSelectorSelector,
  // I prefix injected functions with a $.
  // It's not really necessary.
  (data, $extendUserDataSelector) =>
    data.map($extendUserDataSelector)
);

我将其称为具体化模式,因为它创建了一个预先绑定到当前状态的函数,并且该函数接受单个输入并将其具体化。我通常用它来通过 id 获取东西,因此使用“reify”。我也喜欢说“具体化”,这实际上是我这样称呼它的主要原因。

对于您的情况

在这种情况下:

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

基本上就是这样重新选择 https://www.npmjs.com/package/re-reselect做。如果您计划在全球范围内实施每个 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计算,但这意味着将想要使用该计算的所有其他函数暴露给其接口。通过返回一个只接受单个的函数user基本数据,您可以保持其他选择器的界面干净。

编辑:关于收藏

上述实现目前容易受到的一件事是如果extendUserDataSelectorSelector的输出发生变化,因为它自己的依赖选择器发生了变化,但是通过获取的用户数据userSelector没有改变,并且由创建的实际计算实体也没有改变extendUserDataSelectorSelector。在这些情况下,您需要做两件事:

  1. 多重记忆功能extendUserDataSelectorSelector返回。我建议将其提取到一个单独的全局记忆函数中。
  2. Wrap someFunc这样,当它返回一个数组时,它会将该数组与之前的结果逐元素进行比较,如果它们具有相同的元素,则返回之前的结果。

编辑:避免太多缓存

全局级别的缓存当然是可行的,如上所示,但如果您考虑其他几个策略来解决该问题,则可以避免这种情况:

  1. 不要急于扩展数据,而是将其推迟到实际渲染数据本身的每个 React(或其他视图)组件。
  2. 不要急于将 ids/base-objects 列表转换为扩展版本,而是让父母将这些 ids/base-objects 传递给孩子们。

我一开始并没有在我的一个主要工作项目中遵循这些原则,但愿我做到了。事实上,我后来不得不采用全局记忆路线,因为这比重构所有视图更容易修复,这是应该做的事情,但我们目前缺乏时间/预算。

编辑 2(或者我猜是 4?):重新考虑 Collections pt。 1:多重记忆扩展器

注意:在完成这一部分之前,假设传递给扩展器的基本实体将具有某种类型id可以用来唯一地识别它的属性,或者可以从它廉价地衍生出某种类似的属性。

为此,您可以以类似于任何其他选择器的方式记忆扩展器本身。但是,由于您希望 Extender 记住其参数,因此您不希望将 State 直接传递给它。

基本上,您需要一个 Multi-Memoizer,其作用方式与重新选择 https://www.npmjs.com/package/re-reselect对于选择器来说。 其实打拳也是小事一桩createCachedSelector为我们做这件事:

function cachedMultiMemoizeN(n, cacheKeyFn, fn) {
  return createCachedSelector(
    // NOTE: same as [...new Array(n)].map((e, i) => Lodash.nthArg(i))
    [...new Array(n)].map((e, i) => (...args) => args[i]),
    fn
  )(cacheKeyFn);
}

function cachedMultiMemoize(cacheKeyFn, fn) {
  return cachedMultiMemoizeN(fn.length, cacheKeyFn, fn);
}

然后代替旧的extendUserDataSelectorSelector:

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

我们有这两个函数:

// This is the main caching workhorse,
// creating a memoizer per `user.id`
const extendUserData = cachedMultiMemoize(
  // Or however else you get globally unique user id.
  (user) => user.id,
  function $extendUserData(user, stuff, somethingElse) {
    // your magic goes here.
    return {
      // ...user with stuff and somethingElse
    };
  }
);

// This is still wrapped in createSelector mostly as a convenience.
// It doesn't actually help much with caching.
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) => extendUserData(
      user,
      stuff,
      somethingElse
    )
);

That extendUserData是真正的缓存发生的地方,但公平警告:如果你有很多baseUser实体,它可能会变得相当大。

编辑 2(或者我猜是 4?):重新考虑 Collections pt。 2:数组

数组是缓存存在的祸根:

  1. arrayOfSomeIds本身可能不会改变,但 id 所指向的实体可以具有。
  2. arrayOfSomeIds可能是内存中的一个新对象,但实际上具有相同的 id。
  3. arrayOfSomeIds没有改变,但是保存所引用实体的集合确实改变了,但是这些特定 ID 所引用的特定实体没有改变。

这就是为什么我主张将数组(和其他集合!)的扩展/扩展/具体化/任何其他化委托给尽可能晚的数据获取-派生-视图-渲染过程:这是杏仁核的痛苦考虑所有这一切。

也就是说,这并非不可能,只是需要一些额外的检查。

从上面的缓存版本开始someFunc:

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

然后我们可以将其包装在另一个仅缓存输出的函数中:

function keepLastIfEqualBy(isEqual) {
  return function $keepLastIfEqualBy(fn) {
    let lastValue;

    return function $$keepLastIfEqualBy(...args) {
      const nextValue = fn(...args);
      if (! isEqual(lastValue, nextValue)) {
        lastValue = nextValue;
      }
      return lastValue;
    };
  };
}

function isShallowArrayEqual(a, b) {
  if (a === b) return true;
  if (Array.isArray(a) && Array.isArray(b)) {
    if (a.length !== b.length) return false;
    // NOTE: calling .every on an empty array always returns true.
    return a.every((e, i) => e === b[i]);
  }
  return false;
}

现在,我们不能仅仅将其应用于结果createCachedSelector,这只适用于一组输出。相反,我们需要将它用于每个底层选择器createCachedSelector创造。幸运的是,重新重新选择允许您配置它使用的选择器创建器:

const someFunc = createCachedSelector(
  userSelector,
  extendUserDataSelectorSelector,
  (data, $extendUserDataSelector) =>
    data.map($extendUserDataSelector)
)((state, id) => id,
  // NOTE: Second arg to re-reselect: options object.
  {
    // Wrap each selector that createCachedSelector itself creates.
    selectorCreator: (...args) =>
      keepLastIfEqualBy(isShallowArrayEqual)(createSelector(...args)),
  }
)

奖励部分:数组输入

您可能已经注意到,我们只检查数组输出,涵盖情况 1 和 3,这可能已经足够好了。然而,有时您可能还需要 catch case 2,检查输入数组。 这可以通过使用重新选择来实现createSelectorCreator to 自己做createSelector使用自定义相等函数 https://www.npmjs.com/package/reselect#customize-equalitycheck-for-defaultmemoize

import { createSelectorCreator, defaultMemoize } from 'reselect';

const createShallowArrayKeepingSelector = createSelectorCreator(
  defaultMemoize,
  isShallowArrayEqual
);

// Also wrapping with keepLastIfEqualBy() for good measure.
const createShallowArrayAwareSelector = (...args) =>
  keepLastIfEqualBy(
    isShallowArrayEqual
  )(
    createShallowArrayKeepingSelector(...args)
  );

// Or, if you have lodash available,
import compose from 'lodash/fp/compose';
const createShallowArrayAwareSelector = compose(
  keepLastIfEqualBy(isShallowArrayEqual),
  createSelectorCreator(defaultMemoize, isShallowArrayEqual)
);

这进一步改变了someFunc定义,尽管只是通过改变selectorCreator:

const someFunc = createCachedSelector(
  userSelector,
  extendUserDataSelectorSelector,
  (data, $extendUserDataSelector) =>
    data.map($extendUserDataSelector)
)((state, id) => id, {
  selectorCreator: createShallowArrayAwareSelector,
});

其他想法

总而言之,当您搜索时,您应该尝试看看 npm 中显示的内容reselect and re-reselect。那里的一些新工具可能对某些情况有用,也可能没有用。不过,您只需重新选择和重新重新选择,再加上一些额外的功能来满足您的需求,就可以做很多事情。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

重新选择 - 调用另一个选择器的选择器? 的相关文章

  • 如何使用键盘和鼠标控制相机 - Three.js

    我在 WEB GL 中有一个带有 Three js 的 3D 环境 并且我曾经使用 Orbitcontrols js http codepen io nireno pen cAoGI http codepen io nireno pen c
  • Ext JS - 如何滚动到文本区域的底部

    这是我下面的代码 如何滚动到文本区域的底部 它一定是类似的东西 Ext getCmp output setScrollPosition Ext getCmp output getScrollHeight 这是我的文本区域代码 var myW
  • 如何向 jQuery Tokeninput 添加占位符?

    如何将占位符添加到 jQuery Tokeninput 字段 一个正常的placeholder属性在这里不起作用 对于这样的输入
  • 如何通过 HTML 按钮播放声音

    我目前通过网站播放音乐的方法是通过 HTML 音频标签 不过我希望能够通过 HTML 按钮来播放它 该按钮应该能够在播放和停止之间切换音乐 我在 JSFiddle 创建了一个示例 但不知道如何实现它 有人可以告诉我如何使用我的 JSFidd
  • 在浏览器中语音聊天? [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我们正在寻求建立一个小组 voice 使用服务器上的node js 在浏览器中聊天 这可能吗 如果您希望您的解决方案是基于服务器端和客
  • 禁用 JavaScript 中的右键单击

    当我尝试禁用右键单击时 它不起作用 我尝试使用下面的代码 document onclick function e console log e button if e button 2 e preventDefault return fals
  • 何时不使用承诺[关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 在阅读了数十篇关于 es6 Promise 有多伟大以及为什么我们应该实现它们的文章之后 我有这样的感觉 ALL我的 不平凡的 JavaScri
  • 使用模态表单 ajax 超出 HTMLFormElement.toString 的最大调用堆栈大小

    我想使用模态窗口中的 ajax 请求提交表单 单击此链接可打开该模式 a class btn btn primary i class fa fa edit i Write a review a 模态窗口 div class modal fa
  • ElectronJS ReferenceError:导航器未定义

    我正在尝试在电子上制作自定义标题栏 但是当我启动我的应用程序时 我遇到了 ReferenceError 导航器未定义 问题 请帮忙 这是我的 main js 中的代码片段 My Codes https i stack imgur com c
  • 在 MVC Razor 中的 C# 和 Javascript 之间共享常量

    我想在服务器上的 C 和客户端上的 Javascript 中都使用字符串常量 我将常量封装在 C 类中 namespace MyModel public static class Constants public const string
  • 如何记录返回的事件发射器

    如何记录所发出的事件stream返回于MyFunc 与 JSDoc MyFunc description param Object opts description return Stream description function My
  • HTML colorpicker 发生变化时如何获取新值?

    我正在开发一个需要更改 HTML 颜色的网络应用程序canvas基于的价值观colorpicker 我有一个colorpicker在我需要获取的 HTML 中value从每次更新开始
  • Flux + React.js - 操作中的回调是好还是坏?

    让我解释一下我最近遇到的问题 我有 React js Flux 驱动的应用程序 有一个列表显示文章数量 注意 应用程序中有多个不同的列表 和文章详情查看在里面 但每个列表只有一个 API 端点 它返回文章数组 为了显示我需要的详细信息fin
  • 使水平滚动条始终可见,即使底部不在视图中

    我将用一个片段来开始这个问题 该片段几乎显示了我想要完成的任务 wrapper overflow hidden display flex sidebar min width 200px background 333 color FFF co
  • 如何在网页上实现文件上传进度条?

    当用户将文件上传到我的网络应用程序时 我想显示比动画 gif 更有意义的内容 我还有哪些可能性 编辑 我正在使用 Net 但我不介意是否有人向我展示与平台无关的版本 如果您对这一切在客户端通常如何工作感兴趣 就是这样 所有解决方案都通过 J
  • 加载另一个 JS 脚本后加载

    这是我的代码 very big js file lots of html stuff 问题是 这些是异步加载的 有没有办法等待第二个脚本直到第一个脚本加载 如果您使用 jQuery 有一个非常简单的方法可以通过获取脚本 https api
  • 如何获取使用 .map 渲染的第一个元素的 ref?

    我需要在几行中显示视频 卡片 的缩略图 并重点关注第一个缩略图 我使用嵌套地图进行了显示 该代码基本上迭代视频数组并返回多行视频 我们如何关注第一个渲染的元素 我认为我们需要获得第一个要聚焦的元素的引用 但是我们如何在这里设置 ref 并在
  • 在javascript中动态生成行?

    我是 javascript 新手 我想在按下 Tab 时动态生成行 并希望获取在动态生成的行中输入的值 以便我可以在 servlet 代码中使用这些值 这是我的html
  • 如何修复超出最大调用堆栈大小

    有一个 MERN Firebase 应用程序并收到此错误和一堆 atdeepExtend deepCopy ts 71 RangeError Maximum call stack size exceeded getApps as apps
  • 如何调试 Gulp 任务?

    如何调试我的中定义的 gulp 任务gulpfile js使用诸如 Google Chrome 调试器之类的调试器逐行单步执行任务的代码 对于 Node js 6 3 版本 您可以使用 inspect flag https nodejs o

随机推荐

  • Pandas:检查列值是否小于任何先前的列值

    我想检查 c 列的任何值是否小于所有先前的列值 在我当前的方法中 我使用 pandas diff 但它只让我与以前的值进行比较 import pandas as pd df pd DataFrame c 1 4 9 7 8 36 df di
  • REST 中的资源和资源表示有什么区别?

    我是 REST 新手 刚刚开始阅读一些教程 真正让我困惑的一件事是 txt xml json 形式的内容是什么 资源还是资源表示形式 一定是后者吧 由于资源可以是视频 音频或其他 MIME 类型 以下面的例子为例 假设我得到的描述类似于 R
  • 长时间等待向 Service Worker 发出请求

    我注意到 等待服务工作人员响应缓存中的项目的时间并不像您预期 的那么快 我发现两者的等待时间相同sw precache和一个定制的书面服务人员 造成此等待时间的可能原因是什么 如何减少它 My fetch自定义 Service Worker
  • ggplot错误的颜色分配

    颜色到十六进制值的分配是错误的 我不明白为什么 在将十六进制颜色添加到数据框之前我对它们进行了排序data然后在 ggplot2 函数中使用 我以为这样就能解决问题 但事实并非如此 我还创建了向量col其中包含十六进制以及 rgb 颜色值
  • Mod_Rewrite 不适用于 codeigniter 站点

    我为我的 codeigniter 站点尝试了 modrewrite 但是当我进入 htaccess 文件时 已经有一个了 RewriteEngine on RewriteCond 1 index php combine php images
  • Swift Scenekit - 居中 SCNText - getBoundingBoxMin:Max 问题

    享受 SCNText 上的alignmentMode 选项带来的乐趣 谷歌搜索了一下 看起来alignmentMode和containerFrame有问题 我发现的替代方案建议使用获取边界框功能来查找文本大小 然后手动进行相应调整 很好 除
  • W3C 验证器显示“提要未验证”“url 必须是完整的 URL”...有什么问题吗?

    正在验证我的提要 它的附件的 URL 为 https archive org download NigelFarageAPersonalMessageToNorthernIrelandVoters Nigel 20Farage 20a 20
  • 当复选框被选中时如何调用javascript函数

    当复选框位于 gridview 内时 如何调用 Javascript 函数 protected void AlteraStatusExpiraSeteDias Click object sender EventArgs e for int
  • 从 Rails 2.3.6 开始不推荐使用 overwrite_params —— 接受的解决方法吗?

    所以 看起来像overwrite params从 Rails 2 3 6 开始已弃用 是否存在被普遍接受为最佳实践的合适解决方法 例如 我曾经能够执行以下操作 我认为这非常有用 url for overwrite params gt pag
  • 散列范围

    我有一个范围元素数组 每个元素都有一个开始和一个结束 在数组内 范围不重叠 并且已排序 即 代码只是为了说明 不要指望它能够编译 var arr 0 3 5 10 15 59 给定一个值 比如 9 是否有范围的哈希函数可以让我快速获取包含该
  • Java构建时间常数配置

    我有一个项目想使用多种配置来构建 我有一个常量需要在构建之间有所不同 但我不知道如何根据我的配置更改它 例如 我希望能够根据配置文件中的值执行以下操作 WebService targetNamespace http example com
  • 在 PHP 中获取浮点数的小数部分的最佳方法是什么?

    在 PHP 中如何找到浮点数的小数部分 例如 如果我有值1 25 我想回来0 25 x x floor x
  • C:表示不带浮点数的分数

    我正在为没有硬件浮点支持的嵌入式系统 MSP430 编写一些代码 不幸的是 当我进行测距时 我需要在代码中使用分数 而精度为 1m 的短程传感器并不是一个很好的传感器 我可以用整数进行尽可能多的数学运算 但到最后 我肯定需要对两个值进行分数
  • 使用 iOS 模块构建 Native View 并在 Titanium 中使用它们

    我想构建一个 iOS 模块 其中有一个 viewController 类及其 xib 文件 现在的问题是如何从我的钛代码中调用该视图 我知道有可用的视图代理 但由于文档不太好 不知道如何使用它们 到目前为止 我已经创建了一个可以传递非图形数
  • 如何从 PHP 中的日期时间戳获取时间和日期?

    我有一根像8 29 2011 11 16 12 AM 我想保存在变量中 例如 dat 8 29 2011 and tme 11 16 12 AM 如何实现这一目标 你能举个例子吗 E g
  • 无法向 Google 电子表格添加行

    我可以打开工作表并从标题中读取单元格 Google 电子表格中的第一行是标题 我已在 Google 电子表格中手动添加了 Name my val1 my val2 my val3 Other 这是相关的谷歌文档 https develope
  • 从 git svn clone 中排除文件

    我正在将 SVN 存储库迁移到 Git 并且我有 7000 多个二进制文件 我希望从一开始就将它们排除在导入之外并成为 Git 历史记录的一部分 而不是在之后清理它们 参考此question https stackoverflow com
  • 如何将元组的元组转换为其元素的计数?

    我有这个tuple元组 TupleOfTuples Venue1 Name1 Venue1 Name2 Venue2 Name3 Venue3 Name4 Venue3 Name5 Venue3 Name6 我想将其转换以获得如下结果 Ou
  • 如何在 WPF 中使用线程的结果?

    不幸的是 线程和任务对我来说是一个很大的谜 必须在与 DependencyObject 相同的线程上创建 DependencySource 我正在努力避免用户界面冻结 我的方法是在线程中完成工作 然后使用线程的输出设置属性 我有一个基类Jo
  • 重新选择 - 调用另一个选择器的选择器?

    我有一个选择器 const someSelector createSelector getUserIdsSelector ids gt ids map id gt yetAnotherSelector store id yetAnother