3

为什么要使用记忆性技术?

使用React开发的时候,我们请求服务器拿回来一个复杂的数据,我们在render里去处理这个数据,但是state和props频繁修改会触发render,每次触发render,数据都要去处理一次,每次处理都是对性能的损耗

举个例子:把大于18岁的人列出来

class Example extends Component {
    ...
    render() {
        const { dataList } = this.props;
        const newDataList = dataList.filter((item) => item.age > 18);
        return (
            <div>
                {newDataList.map((item, i) =>
                    <p key={i}>{item.name}:{item.age}岁</p>
                )}
            </div>
        )
    }
    ...
}

从例子中我们看到render中我们处理数据,但是每次state和props的修改都会触发render,都会去处理数据dataList,生成新的数据newDataList,每次处理都是对性能的损耗!

什么叫记忆性技术?

每次调用函数把你的传参和结果记录下来,遇到相同的传参,就直接返回记录缓存的结果,不用再去调用函数处理数据!

memoize-one官方案例

import memoizeOne from 'memoize-one';

const add = (a, b) => a + b;
const memoizedAdd = memoizeOne(add);

memoizedAdd(1, 2); // 3

memoizedAdd(1, 2); // 3
// Add 函数并没有执行: 前一次执行的结果被返回

memoizedAdd(2, 3); // 5
// Add 函数再次被调用,返回一个新的结果

memoizedAdd(2, 3); // 5
// Add 函数并没有执行: 前一次执行的结果被返回

memoizedAdd(1, 2); // 3
// Add 函数再次被调用,返回一个新的结果

我们可以发现连续两次相同传参,第二次会直接返回上次的结果,每次传参不一样,就直接调用函数返回新的结果,会丢失之前的记录,并不是完全记忆,这也是个不足点!

在React中使用memoize-one

根据上的例子,我们对那个例子进行修改,使用memoize-one提升React的性能

import memoize from "memoize-one";

class Example extends Component {
    ...
    filter = memoize((dataList, age) => dataList.filter((item) => item.age > age))
    render() {
        const { dataList } = this.props;
        const newDataList = this.filter(dataList, 18)
        return (
            <div>
                ...
                {newDataList.map((item, i) =>
                    <p key={i}>{item.name}:{item.age}岁</p>
                )}
                ...
            </div>
        )
    }
    ...
}

memoize-one源码解析

memoize-one是采用闭包来缓存数据的

type EqualityFn = (a: mixed, b: mixed) => boolean;

const simpleIsEqual: EqualityFn = (a: mixed, b: mixed): boolean => a === b;

export default function <ResultFn: (...Array<any>) => mixed>(resultFn: ResultFn, isEqual?: EqualityFn = simpleIsEqual): ResultFn {
  let lastThis: mixed; // 用来缓存上一次result函数对象
  let lastArgs: Array<mixed> = []; // 用来缓存上一次的传参
  let lastResult: mixed; // 用来缓存上一次的结果
  let calledOnce: boolean = false; // 是否之前调用过
  // 判断两次调用的时候的参数是否相等
  // 这里的 `isEqual` 是一个抽象函数,用来判断两个值是否相等
  const isNewArgEqualToLast = (newArg: mixed, index: number): boolean => isEqual(newArg, lastArgs[index]);

  const result = function (...newArgs: Array<mixed>) {
    if (calledOnce &&
      lastThis === this &&
      newArgs.length === lastArgs.length &&
      newArgs.every(isNewArgEqualToLast)) {
      // 返回之前的结果
      return lastResult;
    }

    calledOnce = true; // 标记已经调用过
    lastThis = this; // 重新缓存result对象
    lastArgs = newArgs; // 重新缓存参数
    lastResult = resultFn.apply(this, newArgs); // 重新缓存结果
    return lastResult; // 返回新的结果
  };

  // 返回闭包函数
  return (result: any);
}

关于isEqual函数(memoize-one推荐使用loadsh.isEqual)

一般两个对象比较是否相等,我们不能用===或者==来处理,memoize-one允许用户自定义传入判断是否相等的函数,比如我们可以使用lodash的isEqual来判断两次参数是否相等

import memoizeOne from 'memoize-one';
import deepEqual from 'lodash.isEqual';

const identity = x => x;

const defaultMemoization = memoizeOne(identity);
const customMemoization = memoizeOne(identity, deepEqual);

const result1 = defaultMemoization({foo: 'bar'});
const result2 = defaultMemoization({foo: 'bar'});

result1 === result2 // false - 索引不同

const result3 = customMemoization({foo: 'bar'});
const result4 = customMemoization({foo: 'bar'});

result3 === result4 // true - 参数通过lodash.isEqual判断是相等的

参考

https://github.com/alexreardo...

云天 · 6月15日

传进来的 data不变的情况下,直接用 shouldComponentWillupdate不是更好吗?变了的话就不说了,一样的。这是我的疑问

+1 回复

0

重点在于这个data是处理dataList后生成的,当我们直接把newDataList直接渲染出来,而不是传给子组件渲染尼?我这个例子不是很好,我改下!

Nine 作者 · 6月15日
fantasy525 · 6月16日

在vue里面就是一个计算属性解决的事,react里面还要引入库去优化性能😂

回复

0

这个是优化调用函数,不用每次都去处理数据,在利用React的diff算法而优化的,其他框架也是能用的,不是针对React的

Nine 作者 · 6月16日
natureless · 6月18日

安利下immuatble.is的diff,immutable的diff也有hash weakMap的缓存机制,使其diff性能要比lodash好些

回复

载入中...