不一樣類型業務要求的性能標準各不相同。若是對一個 ToB 的後臺管理系統要求首屏速度以及 SEO,顯然不合理也不必。html
第一要考慮的不是如何去優化,而是值不值得去優化,React 性能已經足夠優秀,畢竟「過早優化是魔鬼」,狀況老是「能夠,但不必」。vue
做爲一個開發人員,深刻了解工具不足之處,並擁有對其進行優化的能力,是極其重要的。react
React 性能優化大抵可分爲兩點:git
本文基於 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
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