学习使用选择器计算JavaScript中的派生状态

59 阅读3分钟

简介

状态管理是具有挑战性的。我们可以通过确保不在我们的状态中存储任何多余的信息来减少它的挑战性。我的意思是什么呢?假设在我们的程序中,我们需要弄清楚人们是否会被允许进入我们的酒吧。我们可以通过检查这个人的几个属性来确定:我们可以看他或她的年龄(任何21岁或以上的人都可以进入酒吧),或者我们可以看他或她是否是酒吧的雇员(所有酒吧雇员都可以进入,无论年龄大小)。现在,我们可以在我们的状态对象中存储所有这些信息。

const state = {
  name: 'Joe',
  age: 15,
  employee: false,
  allowedIn: false,
};

这里的问题是,allowedIn 可以很容易地从ageemployee 道具中派生出来,这意味着从技术上讲它与这些信息是多余的。这是最有问题的,因为它为我们的状态提供了一个自相矛盾的机会。

引入选择器

我们可以使用选择器来解决这个问题。选择器是将状态作为一个属性并返回衍生状态值的函数。让我们看看我们是否可以创建一个选择器来取代我们的allowedIn 属性。

const state = {
  name: 'Joe',
  age: 15,
  employee: false,
};

const allowedIn = (state) => state.age >= 21 || state.employee;

现在我们看到,如果我们需要确定这个人是否被允许进入我们的酒吧,我们可以简单地使用调用allowedIn(state) !的布尔值结果。

对可组合选择器的深入研究

现在,如果我们有一些更复杂的要求呢?也许我们需要根据他们是否被允许进入酒吧,以及他们是否友好来做出一个叫做highFiveThem 的决定。让我们首先假设我们有一个新的状态对象,其中包括他们是否友好。

const state = {
  name: 'Judy',
  age: 22,
  employee: false,
  isFriendly: true,
};

我们的决定不再只是基于我们的状态对象,而是也基于另一个选择器的结果。这就是我们开始使用高阶函数来组合来自其他选择器的选择器的地方。让我们看看这在实践中是如何工作的,然后我们就可以窥视一下引擎盖下的情况了。

const state = {
  name: "Judy",
  age: 22,
  employee: false,
  isFriendly: true
};
const allowedIn = state => state.age >= 21 || state.employee;
const isFriendly = state => state.isFriendly;
const highFiveThem = createSelector(
    allowedIn,
    isFriendly,
    (allowedIn, isFriendly) => allowedIn && isFriendly;
)
highFiveThem(state);
// true

这基本上会计算出allowedIn(state)isFriendly(state) 选择器的结果,并将这些输入到最后传递给createSelector 的函数。

在学术上,让我们看看这个高阶函数如何工作。

const createSelector = (...funcs) => {
  const last = funcs.pop();
  return (state) => {
    const inputs = funcs.map((func) => func(state));
    return last(...inputs);
  };
};

这是如何工作的。

  • createSelector 函数接受任何数量的funcs
  • 我们创建一个名为last 的变量来存储传递给createSelector 的最后一个函数。(最后一个函数将接收所有其他函数的结果作为参数)。
  • 我们返回一个函数(我们的新选择器!)。
  • 每当最后一个函数被执行时,我们映射所有的输入函数,根据传递的状态确定它们的结果。 考虑到之前所有函数的结果,我们返回我们最后一个函数的值。

很整洁吧?

思考效率问题

许多选择器库(例如Redux的Reselect)包括了额外的功能来记忆选择器的结果。这是因为如果一个选择器的输入没有变化的话,重新计算它的效率就会很低。在这里,映射我们的记忆功能有点超出范围,但请记住,由于这种优化,使用这些库中的一个可能是有益的(相对于推出你自己的选择器解决方案)。