前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >reselect源码阅读

reselect源码阅读

作者头像
ACK
发布2020-01-14 17:20:17
5560
发布2020-01-14 17:20:17
举报
文章被收录于专栏:flytam之深入前端技术栈

reselect源码阅读

之前就听闻了reselect是一个用于react性能优化的库,并且源码只有100多行。可谓短小精悍,今天来阅读一波膜拜大佬们的思想

代码语言:javascript
复制
import { createSelector } from 'reselect'

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

let exampleState = {
  shop: {
    taxPercent: 8,
    items: [
      { name: 'apple', value: 1.20 },
      { name: 'orange', value: 0.95 },
    ]
  }
}

console.log(subtotalSelector(exampleState)) // 2.15
console.log(taxSelector(exampleState))      // 0.172
console.log(totalSelector(exampleState))    // { total: 2.322 }

官网demo如上,通过介绍可以知道,subtotalSelector taxSelector totalSelector在传进去的state不变的情况下,第二次调用不会重新计算,而是会取前一次的计算结果。在涉及到大量运算的时候,例如redux中,可以避免全局state某一小部分改变而引起这边根据小部分state进行计算的重新执行。起到性能优化的作用。下面开始阅读探读部分

先说几个简单的工具函数吧

首先是默认的比较函数,代表比较方式,可以根据业务需求换的。默认是进行全等比较

代码语言:javascript
复制
/**
 * 默认的比较函数,只进行一层全等比较。根据业务需要可以自己定制。
 * @param {*} a 
 * @param {*} b 
 */
function defaultEqualityCheck(a, b) {
    return a === b
  }

比较两个参数是否完全相等的库…见注释。记住不要为了装逼而弃用for循环

代码语言:javascript
复制
  /**
   * 比较前后两次的参数是否完全相等
   * @param {*} equalityCheck 比较规则,默认是使用defaultEqualityCheck全等比较
   * @param {*} prev 前一次的参数
   * @param {*} next 这次的参数
   */
  function areArgumentsShallowlyEqual(equalityCheck, prev, next) {
    if (prev === null || next === null || prev.length !== next.length) {
      return false
    }
    // 学到了。使用for循环是因为return后不会继续for后面的,forEach和every是会继续的,所以以后不要为了装逼而抛弃for循环了
    // Do this in a for loop (and not a `forEach` or an `every`) so we can determine equality as fast as possible.
    const length = prev.length
    for (let i = 0; i < length; i++) {
        // 只要遇到一个参数不想等就退出,判断返回false
      if (!equalityCheck(prev[i], next[i])) {
        return false
      }
    }

    return true
  }

获取依赖,就是保证inputSelectors的每一项都是函数。否则就报错

代码语言:javascript
复制
  /**
   * 用来保证inputSelectors的每一项都是函数,非函数就报错。然后返回每一个inputSelector组成的数组(依赖)
   * @param {*} funcs  
   */
  function getDependencies(funcs) {
      // 因为可以两种形式传入,所以处理下
      // createSelector(...inputSelectors | [inputSelectors], resultFunc)
    const dependencies = Array.isArray(funcs[0]) ? funcs[0] : funcs 

    if (!dependencies.every(dep => typeof dep === 'function')) {
      const dependencyTypes = dependencies.map(
        dep => typeof dep
      ).join(', ')
      throw new Error(
        'Selector creators expect all input-selectors to be functions, ' +
        `instead received the following types: [${dependencyTypes}]`
      )
    }

    return dependencies
  }

下面是默认记忆化函数defaultMemoize

就是说有个func函数,每次去调用它的时候,先去比较它的参数是否和上一次参数相同,相等就返回上一次结果,不等就重新计算一遍, 并把结果和参数存到lastArgs、lastResult中,这里是用到了闭包来保存的。

代码语言:javascript
复制
  /**
   * 默认的记忆化函数
   * @param {*} func 
   * @param {*} equalityCheck 比较的函数,默认情况下是判断是否全等
   */
  export function defaultMemoize(func, equalityCheck = defaultEqualityCheck) {
    let lastArgs = null
    let lastResult = null
    // 这里为了性能原因。使用arguments而不是rest运算符
    // we reference arguments instead of spreading them for performance reasons
    return function () {
      if (!areArgumentsShallowlyEqual(equalityCheck, lastArgs, arguments)) {
        // 参数改变了重新计算一遍
        lastResult = func.apply(null, arguments)
      }

      lastArgs = arguments // 记录下这次的参数值
      return lastResult
    }
  }

demo中引入的createSelector其实是export const createSelector = createSelectorCreator(defaultMemoize)这样创建的

下面看看createSelectorCreator的实现

1、memoize是自定义的记忆化函数,默认是上面说到的defaultMemoize。我们可以根据自己的业务需求进行定制

2、它的内部也使用了defaultMemoize进行优化。就是对于相同的state,不需要每一次都一次调用inputSelect去一个个获取对应的值

代码语言:javascript
复制
  /**
   * 
   * @param {*} memoize 
   * @param {*} memoizeOptions 
   */
  export function createSelectorCreator(memoize, ...memoizeOptions) {
    return (...funcs) => { // createSelector的本体
      let recomputations = 0
      const resultFunc = funcs.pop() // 最后一个。。就是那个计算函数
      const dependencies = getDependencies(funcs)// funs是inputSelectors

      // resultFunc经过包装。具备了记忆功能
      const memoizedResultFunc = memoize(
        function () {
            // 这里的arguments是每一项是inputSelector后的结果
            // 把inputSelector后的结果传进resultFunc
            // recomputations 用了记录调用了多少次
          recomputations++
          return resultFunc.apply(null, arguments)
        },
        ...memoizeOptions
      )

      // If a selector is called with the exact same arguments we don't need to traverse our dependencies again.
      // reselect内部的一个优化
      // 作用就是对于相同的state,不需要每一次都一次调用inputSelect去一个个获取对应的值
      const selector = defaultMemoize(function () {
          // arguments应该是state
        const params = []
        const length = dependencies.length

        for (let i = 0; i < length; i++) {
          // 对每一个inputSelect结合state取出相应的值
          params.push(dependencies[i].apply(null, arguments))
        }

        //最后供memoizedResultFunc计算使用Ss s
        return memoizedResultFunc.apply(null, params)
      })

      // 对外提供的几个方法

      // 返回那个函数
      selector.resultFunc = resultFunc

      //返回计算次数
      selector.recomputations = () => recomputations

      // 用来重置
      selector.resetRecomputations = () => recomputations = 0
      return selector
    }
  }

reselect这个库还是很吊的…主要是利用了必=闭包来保存变量,比较函数前后两次的参数值,参数值不同就重新计算。参数相同就返回之前的结果

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • reselect源码阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档