React Hooks讓咱們的工做方方面面都變得更好。可是但有時候一些性能問題也是很棘手的。咱們能夠運用Hooks編寫高性能應用,可是前提是你對於下面的這些問題有清醒的認知。react
Memoization:在計算機科學中,記憶化(英語:memoization而非memorization)是一種提升程序運行速度的優化技術。經過儲存大計算量函數的返回值,當這個結果再次被須要時將其從緩存提取,而不用再次計算來節省計算時間。 記憶化是一種典型的時間存儲平衡方案。 --維基百科typescript
React在大多數使用場景中已經足夠高性能。若是你的應用足夠快而且沒有任何渲染問題,那麼就不必往下看了。不要嘗試去解決假想的渲染問題,因此在提升性能以前,確認下你是否是熟悉React Profiler
。數組
若是你已經明確知道加載慢的問題在哪,Memoization是最好的嘗試方法。緩存
React.memo
是提升性能工具,同時也是一個HOC(高階組件)。它和React.PureComponent
很類似,只不過它應用於函數組件而不是class組件。若是函數組件根據給定的相同的props渲染相同的結果,React會記住這些,跳過渲染組件,而且複用最後一次渲染結果。函數
默認狀況下它會對複雜的props對象進行淺比較。若是你想要控制比較過程,也能夠提供一個自定義的比較函數做爲鉤子函數的第二個參數。工具
咱們來舉一個不使用Memoization的例子,而後看爲何這樣會形成問題。性能
function List({ items }) {
log('renderList');
return items.map((item, key) => (
<div key={key}>item: {item.text}</div>
));
}export default function App() {
log('renderApp'); function List({ items }) {
log('renderList');
return items.map((item, key) => <div key={key}>item: {item.text}</div>);
}
export default function App() {
log('renderApp');
const [count, setCount] = useState(0);
const [items, setItems] = useState(getInitialItems(10));
return (
<div>
<h1>{count}</h1>
<button onClick={() => setCount(count + 1)}>inc</button>
<List items={items} />
</div>
);
}
const [count, setCount] = useState(0);
const [items, setItems] = useState(getInitialItems(10)); return (
<div>
<h1>{count}</h1>
<button onClick={() => setCount(count + 1)}>
inc
</button>
<List items={items} />
</div>
);
}
複製代碼
每次點擊inc
,renderApp
和renderList
都會打印,甚至List
中沒有任何改變。若是這棵數據樹足夠大,那麼它會很容易引起性能問題。咱們須要減小渲染次數。flex
const List = React.memo(({ items }) => {
log('renderList');
return items.map((item, key) => <div key={key}>item: {item.text}</div>);
});
export default function App() {
log('renderApp');
const [count, setCount] = useState(0);
const [items, setItems] = useState(getInitialItems(10));
return (
<div>
<h1>{count}</h1>
<button onClick={() => setCount(count + 1)}>inc</button>
<List items={items} />
</div>
);
}
複製代碼
在這個例子中Memoization作了不少工做,而且減小了不少渲染次數。在加載過程當中renderApp
和renderList
都打印了,可是點擊inc
的時候只有renderApp
打印了。優化
咱們來作一個小修改,在全部List
條目中添加上inc
按鈕。記住,向組件中傳入回調函數來記憶化組件會形成微妙的bug。spa
function App() {
log('renderApp');
const [count, setCount] = useState(0);
const [items, setItems] = useState(getInitialItems(10));
return (
<div>
<div style={{ display: 'flex' }}>
<h1>{count}</h1>
<button onClick={() => setCount(count + 1)}>inc</button>
</div>
<List items={items} inc={() => setCount(count + 1)} />
</div>
);
}
複製代碼
在這個例子中,咱們的Memoization失敗了。由於咱們使用了內聯匿名函數,每次渲染的時候都會生成新的渲染結果,這樣React.mome
就失效了。因此在咱們記住這個組件以前,咱們要想辦法記住這個函數。
十分幸運的是,React有兩個內建鉤子能夠提供上面的功能:useMemo
和useCallback
。useMemo
對於複雜計算頗有用,useCallback
對於傳遞迴調函數給須要提升性能的子組件十分有用。
function App() {
log('renderApp');
const [count, setCount] = useState(0);
const [items, setItems] = useState(getInitialItems(10));
const inc = useCallback(() => setCount(count + 1));
return (
<div>
<div style={{ display: 'flex' }}>
<h1>{count}</h1>
<button onClick={inc}>inc</button>
</div>
<List items={items} inc={inc} />
</div>
);
}
複製代碼
在這個例子中,咱們的Memoization又失敗了。renderList
在每次inc
被點擊的時候都會調用。useCallback
的默認行爲是函數實例每次掛載的時候都計算新值。由於內聯匿名函數每次渲染的時候都會建立新實例,默認設置下useCallback
無效。
const inc = useCallback(() => setCount(count + 1), [count]);
useCallback
接收的第二個參數是一個輸入變量數組,當且僅當這些輸入變量變化的時候,useCallback
會返回新值。在這個例子中,當count
變化的時候useCallback
會返回新值。每次count
在渲染的時候改變,useCallback
都會跟着改變。這樣也沒有實現很好的記憶功能。
const inc = useCallback(() => setCount(count + 1), []);
useCallback
也能夠接收一個空數組做爲第二個參數,這樣內聯匿名函數就只會調用一次。此次代碼實現了記憶功能,點擊按鈕會打印renderApp
,主要按鈕的功能也正常,可是內部inc
按鈕中止正常工做了。
計數器從0增長到1後就中止工做了。匿名函數被建立一次,調用了屢次。由於匿名函數被建立的時候count是0,他的表現就像下面這樣: const inc = useCallback(() => setCount(1), []);
這個問題的根本緣由在於,咱們試圖同時讀寫state
。幸運的是,react
提供瞭解決上面問題的方法:
const inc = useCallback(() => setCount(c => c + 1), []);
useState
返回的setters能夠將函數做爲參數,這個函數中能夠讀取上一state
的值。在這個例子中,Memoization正確工做,沒有bug。
const [count, dispatch] = useReducer(c => c + 1, 0);
useReducer memorization
和useState
中的例子同樣正確運行了。由於dispatch
要保證渲染過程當中參考對象一致,就不須要useCallback
了,dispatch
使得代碼更少的犯Memoization相關錯誤。
useReducer
更適合管理複雜對象或者下一state
依賴上一state
的狀況。使用useReducer
的常見模式是和useContext
一塊兒使用,避免在大型組件樹中顯示傳遞迴調。 我比較推薦用useState
來管理組件內部數據,useReducer
適合管理須要在父子組件中傳遞的特殊雙向數據。
總之,
React.memo
和useReducer
是最好的朋友;React.memo
和useState
是時而產生衝突的兄弟,會形成一些問題;謹慎使用useCallback
。
生活的一部分是工做,工做的一部分是解決問題取悅生活,因此好好生活,好好工做,好好熱愛(●ˇ∀ˇ●)