memoize-one在React中的應用

編寫更快的 React 代碼(一):memoize-one 簡介

引言

不一樣類型業務要求的性能標準各不相同。若是對一個 ToB 的後臺管理系統要求首屏速度以及 SEO,顯然不合理也不必。html

第一要考慮的不是如何去優化,而是值不值得去優化,React 性能已經足夠優秀,畢竟「過早優化是魔鬼」,狀況老是「能夠,但不必」。vue

做爲一個開發人員,深刻了解工具不足之處,並擁有對其進行優化的能力,是極其重要的。react

React 性能優化大抵可分爲兩點:git

  1. 減小 rerender 次數 (immutable data、shouldComponentUpdate、PureComponent)
  2. 減輕 rerender 複雜度 (memoize-one)

本文基於 memoize-one 對 render 方法進行優化,達到減輕沒必要要 render 複雜度的效果。github

存在的問題

先看一個簡單的組件,以下所示:編程

class Example extends Component {
  state = {
    filterText: ""
  };

  handleChange = event => {
    this.setState({ filterText: event.target.value });
  };

  render() {
    const filteredList = this.props.list.filter(item =>
      item.text.includes(this.state.filterText)
    );

    return (
      <Fragment> <input onChange={this.handleChange} value={this.state.filterText} /> <ul> {filteredList.map(item => ( <li key={item.id}>{item.text}</li> ))} </ul> </Fragment> ); } } 複製代碼

該組件接收父組件傳遞的 list,篩選出包含 filterText 的 filteredList 並進行展現。緩存

問題是什麼?性能優化

在未進行任何處理的狀況下,父組件 render,總會致使子組件 render,即便子組件的 state/props 並未發生變化,若是篩選的數據量大,篩選邏輯複雜,這將是一個很重要的優化點。閉包

要達到怎樣的效果?app

  1. state(filterText)/props(list)未發生變化時,不進行 render(引言-性能優化第 1 點), 此處暫不討論
  2. state(filterText)/props(list)未發生變化時,進行 render,複用上一次計算結果

memoize-one

A memoization library which only remembers the latest invocation

基本使用

import memoize from "memoize-one";

const add = (a, b) => a + b; // 基本計算方法
const memoizedAdd = memoize(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 函數被調用獲取新的結果
// 即便該結果在以前已經緩存過了
// 但它並非最近一次的緩存結果,因此緩存結果丟失了
複製代碼

在瞭解基本使用後,咱們來對上述案例進行優化。

優化案例

import memoize from "memoize-one";

class Example extends Component {
  state = { filterText: "" };

  // 只有在list或filterText改變的時候纔會從新調用真正的filter方法(memoize入參)
  filter = memoize((list, filterText) =>
    list.filter(item => item.text.includes(filterText))
  );

  handleChange = event => {
    this.setState({ filterText: event.target.value });
  };

  render() {
    // 在上一次render後,若是參數沒有發生改變,`memoize-one`會重複使用上一次的返回結果
    const filteredList = this.filter(this.props.list, this.state.filterText);

    return (
      <Fragment> <input onChange={this.handleChange} value={this.state.filterText} /> <ul> {filteredList.map(item => ( <li key={item.id}>{item.text}</li> ))} </ul> </Fragment> ); } } 複製代碼

源碼解析

若是除去 ts 相關以及註釋,不到 20 行。memoize-one 本質是一個高階函數,真正計算函數做爲參數,返回一個新的函數,新的函數內部會緩存上一次入參以及上一次返回值,若是本次入參與上一次入參相等,則返回上一次返回值,不然,從新調用真正的計算函數,並緩存入參以及結果,供下一次使用。

僞裝這裏有一張流程圖 :)

// 默認比較前後入參是否相等的方法,使用者可自定義比較方法
import areInputsEqual from './are-inputs-equal';

// 函數簽名
export default function<ResultFn: (...any[]) => mixed>( resultFn: ResultFn, isEqual?: EqualityFn = areInputsEqual, ): ResultFn {
  // 上一次的this
  let lastThis: mixed;
  // 上一次的參數
  let lastArgs: mixed[] = [];
  // 上一次的返回值
  let lastResult: mixed;
  // 是否已經初次調用過了
  let calledOnce: boolean = false;

  // 被返回的函數
  const result = function(...newArgs: mixed[]) {
    // 若是參數或this沒有發生變化或非初次調用
    if (calledOnce && lastThis === this && isEqual(newArgs, lastArgs)) {
      // 直接返回上一次的計算結果
      return lastResult;
    }

    // 參數發生變化或者是初次調用
    lastResult = resultFn.apply(this, newArgs);
    calledOnce = true;
    // 保存當前參數
    lastThis = this;
    // 保存當前結果
    lastArgs = newArgs;
    // 返回當前結果
    return lastResult;
  };

  // 返回新的函數
  return (result: any);
}
複製代碼

另可使用decko這個庫,內置bind/memoize/debounce三個裝飾器,與React契合度很高。

拓展:斐波那契數列

下面是一個計算斐波那契數列的例子,該例子使用迭代代替遞歸,而且利用閉包緩存以前的結果。

const createFab = () => {
  const cache = [0, 1, 1];
  return n => {
    if (typeof cache[n] !== "undefined") {
      return cache[n];
    }
    for (let i = 3; i <= n; i++) {
      if (typeof cache[i] !== "undefined") continue;
      cache[i] = cache[i - 1] + cache[i - 2];
    }
    return cache[n];
  };
};

const fab = createFab();
複製代碼

總結

本文基於 React 介紹了 memoize-one 庫的相關使用及其原理,在 React 中實現了相似與 Vue 計算屬性(computed)的效果 —— 基於依賴緩存計算結果,達到減輕沒必要要 render 複雜度的效果。

從業務開發角度來說,Vue 提供的 API 極大地提升了開發效率。

React 自身解決的問題並很少,但得益於活躍的社區,工做中遇到的解決問題都能找到解決方案,而且在摸索這些解決方案的同時,咱們可以學習到諸多經典的編程思想,從而減輕對框架的依賴。

I’ve always said that React will make you a better JavaScript developer. - Tyler McGinnis

參考連接

相關文章
相關標籤/搜索