reselect.js

关于 reselect ,我们需要从两个方面来理解它。

reselect 的用法

首先是它最直接的使用方法以及它带来的性能优化,我们直接使用它的官方例子。例如你需要从store 中的根据命令过滤出指定类型的 todos,并返回给组件:

const getVisibleTodos = (todos, filter) => {
  switch (filter) {
    case 'SHOW_ALL':
      return todos
    case 'SHOW_COMPLETED':
      return todos.filter(t => t.completed)
    case 'SHOW_ACTIVE':
      return todos.filter(t => !t.completed)
  }
}
​
const mapStateToProps = (state) => {
  return {
    todos: getVisibleTodos(state.todos, state.visibilityFilter)
  }
}

上面的方法是行得通的,但同时也存在缺陷:如果你的状态树过于复杂的话,那么每次非 todos 相关的状态的改变都会引起 getVisibleTodos 的重新计算,并且如果 getVisibleTodos 的计算量过于繁重的话,这会造成一个性能的瓶颈。接下来我们看看如何使用 reselect 解决这个问题:

import { createSelector } from 'reselect'const getVisibilityFilter = (state) => state.visibilityFilter
const getTodos = (state) => state.todos
​
export const getVisibleTodos = createSelector(
  [ getVisibilityFilter, getTodos ],
  (visibilityFilter, todos) => {
    switch (visibilityFilter) {
      case 'SHOW_ALL':
        return todos
      case 'SHOW_COMPLETED':
        return todos.filter(t => t.completed)
      case 'SHOW_ACTIVE':
        return todos.filter(t => !t.completed)
    }
  }
)

首先 getVisibilityFiltergetTodos 是两个简单的非缓存的 selector (其实就是两个简易函数),它帮助我们取得用于需要使用到的变量todos 和visibilityFilter

接下来我们借助 reselect 的 createSelector 方法创建一个具有缓存机制的 selector getVisibleTodos ,它使用之前创建的两个简易 selector 作为参数(注意直接传递的是函数本身,而不是函数的返回值),根据简易缓存器返回的 visibilityFiltertodos 值计算出最终需要返回给组件的 todos

使用 createSelector 创建的 selector 的特殊之处在于它是具有缓存机制的。只有当作为参数的 selector 的返回结果发生了更改,当前的 selector 才会重新执行计算逻辑。否则它会直接使用上一次的计算结果,或者把这一次的计算结果缓存下来。

输入参数不一定是需要是原生函数,也可以是另一个 createSelector 创建的 selector,例如

const shopItemsSelector = state => state.shop.items
const taxPercentSelector = state => state.shop.taxPercent
​
const subtotalSelector = createSelector(
  shopItemsSelector,
  items => items.reduce((acc, item) => acc + item.value, 0)
)
​
const taxSelector = createSelector(
  subtotalSelector,
  taxPercentSelector,
  (subtotal, taxPercent) => subtotal * (taxPercent / 100)
)
​
export const totalSelector = createSelector(
  subtotalSelector,
  taxSelector,
  (subtotal, tax) => ({ total: subtotal + tax })
)

对谁缓存

在初次使用 reselect 时通常人们会常常问道两个问题:

  • Why isn’t my selector recomputing when the input state changes?
  • Why is my selector recomputing when the input state stays the same?

这两个问题的本质实际上是在问究竟在什么情况下 selector 会进行重新计算,什么情况下会使用缓存。正如上面提到的,selector 响应的是“参数”,既输入 selector 的返回值,既上面的 visibilityFilter 和 todos. 只有当这两者发生其一改变时才会进行重新计算。

更重要的是怎样才算是“发生改变”?非常简单, createSelector 使用 === 进行判断。所以回到上一篇里所说的,如果你的在 reducer 中习惯直接修改原状态 (object.property),而不是返回新的状态(Object.assign()),那么在 selector 中你极容易陷入到这个陷阱中,既状态发生了改变,但是每次仍然需要重新计算

selector 设计模式

抛开 reselect 这个类库来看,selector 其实是一种设计模式。在这个设计模式中,一切在 mapStateToProps 中返回给组件的属性都应该由 selector 给出,而不是直接通过直接访问 state 取得。例如:

function mapStateToProps(state) {
    const {todoCollection, showType, userId} = state
    return {
        todos: todosSelector(todoCollection, showType),
        myTodos: myTodosSelector(todoCollection, userId)
    }
}

注意 selector 事实上也是与 state 隔离的,我们并非直接传递 state 给 selector ,而是传递具体参数。这样进一步进行了解耦。无论是 selector 还是组件都不关心 state 的数据结构。即使将来 state 的数据结构发生改变,selector 也不用进行修改,甚至整个 mapStateToProps 函数改动也能达到最小

results matching ""

    No results matching ""