欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

reselect是怎样提升react组件渲染性能的?

程序员文章站 2022-07-09 20:17:32
reselect是什么? "reselect" 是配合 使用的一款轻量型的状态选择库,目的在于当store中的state重新改变之后,使得局部未改变的状态不会因为整体的state变化而全部重新渲染,功能有点类似于组件中的生命周期函数 ,但是它们并不是一个东西。下面是官方的一些简介: Selector ......

reselect是什么?

是配合redux使用的一款轻量型的状态选择库,目的在于当store中的state重新改变之后,使得局部未改变的状态不会因为整体的state变化而全部重新渲染,功能有点类似于组件中的生命周期函数shouldcomponentdidupdate,但是它们并不是一个东西。下面是官方的一些简介:

  • selectors can compute derived data, allowing redux to store the minimal possible state.
  • selectors are efficient. a selector is not recomputed unless one of its arguments changes.
  • selectors are composable. they can be used as input to other selectors.

[注]:并不是reselect非要和redux绑定使用不可,可以说reselect只是一个enhancement,并不代表强耦合。

什么时候用reselect?

  • store状态树庞大且层次较深
  • 组件中的state需要经过复杂的计算才能呈现在界面上

个人认为符合这两点就可以使用reselect,为什么?简单的state或许根本完全没有必要引入redux,状态管理组件内部就可以消化,再者reselect只是在参数级别的缓存,如果组件状态逻辑并不是特别复杂,只是简单的getter,那也可不必引入reselect。

[建议]:建议引入了redux就可以引入reselect,去看官方的源码,总共加起来才短短的108行代码,对测试并没有什么成本,同时加入也不会对打包体积造成什么影响,但是有些时候对组件渲染的性能却有很大的改善。

基本用法

这里是直接copy的官方仓库中的代码

reselect是怎样提升react组件渲染性能的?

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 }

reselect是怎么优化代码性能的?

const selector = memoize(function () {
  const params = []
  const length = dependencies.length

  for (let i = 0; i < length; i++) {
    // apply arguments instead of spreading and mutate a local list of params for performance.
    params.push(dependencies[i].apply(null, arguments))
  }

  // apply arguments instead of spreading for performance.
  return memoizedresultfunc.apply(null, params)
})

selector.resultfunc = resultfunc
selector.dependencies = dependencies
selector.recomputations = () => recomputations
selector.resetrecomputations = () => recomputations = 0
return selector

函数整体返回的就是这个selector,因为我们调用createselector,其实返回的是一个函数,所以memoize返回的其实也是一个函数,那么selector中做了什么?

export function defaultmemoize(func, equalitycheck = defaultequalitycheck) {
  let lastargs = null
  let lastresult = null
  // we reference arguments instead of spreading them for performance reasons
  // 这里作为返回的函数,传入的参数即为state
  return function () {
    if (!areargumentsshallowlyequal(equalitycheck, lastargs, arguments)) {
      // apply arguments instead of spreading for performance.
      lastresult = func.apply(null, arguments)
    }

    lastargs = arguments
    return lastresult
  }
}

memoize是reselect中提供的默认缓存函数,可以的得知执行这个函数的时候,返回的函数即为上面代码中的selector,那么arguments即为传入的state,通过areargumentsshallowlyequal比较两次传入的参数是否相等,注意,这里是浅比较,即第一层引用的比较

function defaultequalitycheck(a, b) {
  return a === b
}

当两次传入的值存在变化的时候,那么就会执行

func.apply(null, arguments)

这里会计算得到所有的依赖,然后得到下一轮缓存函数的params

就redux的reducer来讲,这层缓存并没有什么作用,看看reducer代码:

function reducer(state, action) {
  switch (action.type): 
    case request_todo_pending:
        return { ...state, loading: true };
    case request_todo_list_success:
        return { ...state, list: ['todo'], loading: false };
    // ...
    // default
}

redux社区推崇所有的state都是不可变的,所以只要dispatch了一个action,每次返回的state必然会是一个新的对象,对于浅比较每次返回的结果必然是true;

所以,缓存的关键还在第二层momoize,因为这里的state并不是每一次都必须变化:

const resultfunc = funcs.pop()
const dependencies = getdependencies(funcs)

const memoizedresultfunc = memoize(
  function () {
    recomputations++
    // apply arguments instead of spreading for performance.
    return resultfunc.apply(null, arguments)
  },
  ...memoizeoptions
)

真正代码的执行在resultfunc.apply(null, arguments),这里缓存的逻辑跟上面没什么区别,这里就不在讲解了。resultfunccreateselector中的最后一个参数

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

大家可以自行对照一下上面的这个例子,那么arguments就是第二个函数的参数,也就是第一步缓存函数中的params

总结

好了,就啰嗦这么多了,最后,多读书,多看报,少吃零食,多睡觉