我正在使用reselect
中的createSelector
构建一个自定义选择器。输入选择器将返回默认===比较失败的数据。为了确保我理解行为,我正在构建一些从输入选择器返回数组的基本测试。
我希望每次调用选择器时都会重新计算结果,但这不是我所看到的。即使我有memoryOptions来确保输入选择器比较返回false,该函数也不会重新计算。
我不确定我做错了什么,也不确定我在这次测试中犯了什么根本性的错误。如果能帮我解决这个问题,我将不胜感激。
我使用的是Jest v27.4,节点v15.12.0,在Linux上重新选择4.1.6。
import {test, expect} from '@jest/globals';
import { createSelector } from "reselect";
test("createSelector:array", async () => {
const a: number[] = [1, 2, 3, 4, 5];
const b: string[] = ["a", "b", "c", "d", "e"];
const state0: { a: number[], b: string[] } = { a, b };
// https://github.com/reduxjs/reselect#reselect
// > createSelector determines if the value returned by an input-selector has changed between calls
// > using reference equality (===). Inputs to selectors created with createSelector should be immutable.
const select = createSelector(
(state, index: number): number[] => state.a.slice(0, index),
(state, index: number): string[] => state.b.slice(0, index),
(a_in: number[], b_in: string[]): (number | string)[] => [...a_in, ...b_in],
{ memoizeOptions: { equalityCheck: (a, b) => { console.debug("equalityCheck", a, b); return false }, resultEqualityCheck: (a, b) => { console.debug("resultEqualityCheck", a, b); return false } } } // THESE DO NOTHING, WHY?
);
let out = select(state0, 2);
expect(out).toEqual([1, 2, "a", "b"]);
expect(select.recomputations()).toEqual(1);
out = select(state0, 2);
expect(out).toEqual([1, 2, "a", "b"]);
expect(select.recomputations()).toEqual(2); // THIS IS 1, WHY?
});
根据官方重新选择文档:
如果使用相同的参数再次调用选择器返回以前缓存的结果,而不是重新计算新结果后果
下面是重新选择在内部的工作方式,当您第二次调用选择器时,重新选择将首先检查您传递给选择器自上次以来发生了更改,在您的情况下是state0
和2
。如果他们有,它将遍历你的依赖项(输入选择器(,运行每个依赖项并检查它们的结果自上次以来是否发生了变化,如果没有,它将跳过运行你的输出选择器(或resultFunc(,如果它们发生了变化,它将运行输出选择器,重新计算计数器将递增。所以它有两层检查:
- 检查传递到选择器本身的参数
- 检查输入选择器的返回值
在您的情况下,选择器没有重新计算的原因是选择器通过了第一次检查,您使用完全相同的参数调用选择器本身两次。所以它不会重新计算。你可以这样做:
let out = select({ ...state0 }, 2);
equalityCheck
用于将输入选择器的返回值与前面的值进行检查,因此它用于进行第二层检查。
resultEqualityCheck
用于将输出选择器的当前结果与上一个结果进行比较。测试它的最好方法是这样做:
let out = select({ ...state0 }, 2);
const firstResult = select.lastResult();
expect(out).toEqual([1, 2, "a", "b"]);
expect(select.recomputations()).toBe(1);
out = select({ ...state0 }, 2);
const secondResult = select.lastResult();
expect(firstResult).toBe(secondResult); // If resultEqualityCheck returns true, this should pass.
所以执行顺序是:
- 第一个输入选择器
- 第二输入选择器
equalityCheck
根据每个输入选择器的结果运行- 如果失败,如果不跳过运行输出选择器,它将调用输出选择器
resultEqualityCheck
根据输出选择器的结果运行
我理解你的困惑,因为在重新选择中,你不能自定义第一层检查的相等性检查函数,重新选择使用引用相等性检查来查看传递到选择器本身的参数是否已经更改,并且到目前为止不允许你自定义它。所以你可以重写你的测试,使其看起来更像这样:
let out = select({ ...state0 }, 2);
const firstResult = select.lastResult();
expect(out).toEqual([1, 2, "a", "b"]);
expect(select.recomputations()).toBe(1);
out = select({ ...state0 }, 2);
const secondResult = select.lastResult();
expect(firstResult).toEqual(secondResult);
expect(out).toEqual([1, 2, "a", "b"]);
expect(select.recomputations()).toBe(2);