不一樣類型業務要求的性能標準各不相同。若是對一個 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 並未發生變化,若是篩選的數據量大,篩選邏輯複雜,這將是一個很重要的優化點。markdown
要達到怎樣的效果?閉包
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