16.6以前,函數組件沒有像
shouldComponentUpdate
這樣的方法,也沒有相似PureComponent
這種解決方案,避免不了函數組件裏面全部的代碼再次的執行,要依靠外面的條件渲染來控制,或者是高階組件。以前的話,選擇使用函數組件的狀況是一些比較簡單的又比較純的組件,只是負責展現的。並且函數組件最終編譯babel結果是隻執行createElement
那一步;class組件同樣有生命週期要實例化,最終通過Babel成es5代碼的時候還很長前端
當16.6的memo問世,函數組件就有了相似PureComponent
和shouldComponentUpdate
的解決方案,memo的使用方法:react
const C = (props) => {
return <section>那一晚上{props.name}的嫂子真美</section>
}
export default React.memo(C)
複製代碼
當父組件執行render的時候,避免不了C組件的渲染和C函數的執行(若是不在外面加判斷的話:{isShowC && <C />}
)。當到了C組件的時候,會淺比較C組件先後props值。若是props每個屬性值都同樣,會跳過函數組件C的執行,減小了沒必要要的渲染,達到了性能優化。數組
第二個參數,是一個函數,該函數傳入參數是新props和上次props,咱們能夠在函數裏面作判斷邏輯,控制返回值。當咱們讓函數return true的時候,告訴了react這兩個props是同樣的,不用從新執行整個函數組件;反之false的時候會從新執行該組件性能優化
memo(IfEqual, () => false);
複製代碼
好比這行代碼,判斷函數一直返回false,memo
包住的IfEqual
組件不管怎樣都會從新執行babel
當咱們用上了memo,就能夠根據業務來進行優化了:函數
React.memo(C, (nextProps, prevProps) => {
// 作咱們想作的事情,相似shouldComponentUpdate
})
複製代碼
咱們都知道,js中函數不是簡單數據類型,也就是說function(){}
和function(){}
是不同的,與{}
和{}
不同同理。那麼咱們傳入props.onClick
(即便是長得同樣的內容徹底同樣),先後props.onClick
都不能劃上等號性能
<div>
<IfEqual onClick={() => {}} /> </div>
複製代碼
以爲inline function很差看,那前面定義一下,實際上仍是逃不了同一個事情:它們是不同的。此次是由於,函數組件的渲染,也就是執行,每一次從新執行,函數做用域裏面一切都是從新開始。這就至關於上一次組件渲染const handleClick = () => {}
,後面渲染又一次const handleClick = () => {}
,它們都不是同一個東西學習
export default () => {
const handleClick = () => {}
return (
<div> <IfEqual onClick={handleClick} /> </div> ) } 複製代碼
這種狀況下,咱們能夠用memo第二個參數來拯救多餘一次的渲染的局面:優化
// props: { a: 1, onClick: () => {} }
// 在咱們知道onClick是作同一個事情的函數的前提下,不比較onClick
React.memo(C, (nextProps, prevProps) => nextProps.a === prevProps.a)
複製代碼
最後,先後props的onClick
,它們只有一種狀況是同樣的——把聲明抽到組件外面去ui
const handleClick = () => {}
export default () => {
return (
<div> <IfEqual onClick={handleClick} /> </div> ) } 複製代碼
這時候,有沒有想起class組件裏面老是onClick={this.handleClick}
呢?this.handleClick
一直都是同一個函數。這種狀況,子組件爲函數組件的時候,包一層memo就能夠實現purecomponent的效果
函數組件把函數定義寫在外面,是能夠解決問題。可是,若是handleClick依賴組件內部的一些變量,那handleClick又不得不寫在裏面(固然利用引用類型能夠解決)。或者仍是正常寫,靠memo第二個參數來控制要不要從新渲染子函數組件。可是不管怎樣,都存在一個問題,就是那一塊代碼寫在裏面呢,都沒法避免代碼的執行和函數的從新定義,好比
function a(){
const b = function(){
console.log(1)
// 不少不少代碼
}
}
a()
a() // 函數b又被定義了一次
複製代碼
若是咱們經過依賴來肯定先後兩次是否是同一個函數,咱們能夠用函數記憶來實現整個功能
let prev
let prevDeps
function memorize(fn, deps) {
// 先後依賴一致,不用從新計算直接返回上次結果
if (prev && isEqual(deps, prevDeps)) {
return prev
}
prevDeps = deps
prev = fn
return fn
}
function a(){
const b = memorize(function(){
console.log(1)
// 不少不少代碼
}, [])
}
a()
a() // 函數b又被定義了一次
複製代碼
相似函數記憶的原理,後來有了useCallback
的出現,多了一種新的解決方案,根據依賴生成一個函數:
const handleClick = useCallback(() => {
console.log(dep)
}, [dep])
複製代碼
當dep不變,每一次函數組件的執行,handleClick都是同一個函數。若是dep變了,那麼handleClick又是一個新的函數
export default () => {
// 沒有依賴,永遠是同一個函數
const handleClick = useCallback(() => {}, []);
// 依賴a,從新執行函數組件,a不變的,是同一個函數
// a變了handleClick是新的函數
const handleClick1 = useCallback(() => {}, [a]);
return (
<div> <IfEqual onClick={handleClick} /> </div> ) } 複製代碼
react組件也是一個函數,那其實useCallback
還能夠作一個函數組件:
export default () => {
const handleClick = useCallback(() => {}, []);
const Cpn = useCallback(({ name }) => {
return <button onClick={handleClick}>{name}</button>
}, [handleClick]);
return (
<div> <Cpn name="hi" /> </div> ) } 複製代碼
固然這只是一個簡單的場景,若是用了hooks,尚未解決問題或者暫時沒有想到優雅的封裝技巧,想用高階組件的時候,不妨嘗試一下useCallback
const a = useMemo(() => memorizeValue, deps)
複製代碼
當deps不變,a的值仍是上次的memorizeValue,省去了從新計算的過程。若是memorizeValue是一個函數,和useCallback是同樣的效果:
useCallback(fn, inputs) <=> useMemo(() => fn, inputs)
咱們能夠試一下同步執行的代碼,當時間很是長的時候,useMemo能夠發揮它的做用了:
// 強行更新組件
const useForceUpdate = () => {
const forceUpdate = useState(0)[1];
return () => forceUpdate(x => x + 1);
}
// 一個很耗時間的代碼
function slowlyAdd(n) {
console.time('add slowly')
let res = n;
for (let i = 0; i < 2000000000; i++) {
res += 1;
}
console.timeEnd('add slowly')
return res;
}
// useMemo記憶結果的一個自定義hook
function useSlowlyAdd(n) {
const res = useMemo(() => {
return slowlyAdd(n);
}, [n])
return res;
}
export default () => {
const [count, add] = useState(1);
const forceUpdate = useForceUpdate();
const handleClick = useCallback(() => {}, []);
useSlowlyAdd(count) // 第一次這裏會耗不少時間,頁面卡死一陣
return (
<> <button onClick={forceUpdate}>更新頁面</button> <button onClick={() => add(count + 1)}>+</button> </> ) } 複製代碼
第一次進來,頁面暫時沒有任何反應一陣,這是由於slowlyAdd佔用了js主線程。當咱們點擊‘更新頁面’更新的時候,頁面並無卡死,並且組件也從新渲染執行了一次。當咱們點擊+,頁面又開始卡死一陣。
這是由於點擊+的時候,修改了useMemo的依賴n,n變了從新計算,計算耗費時間。若是點擊更新頁面,沒有修改到依賴n,不會從新計算,頁面也不會卡
固然,useMemo也能夠作高階組件,用起來的時候,能夠寫成reactElement的形式了:
const HOC = useMemo(() => <C />, deps)
複製代碼
有以下的組件,Big是一個10w個節點的組件,每個節點都綁定事件
const handleClick = useCallback(() => {}, []);
export default () => {
return (
<div> <Big onClick={handleClick} /> </div> ) } 複製代碼
若是Big組件沒有memo包住,首次掛載和再次渲染父組件性能以下:
若是Big組件有memo包住並且props被認爲是同樣的狀況下,首次掛載和再次渲染父組件性能以下:
總結一下對於props的某個屬性值爲函數的時候,如何作到子組件不從新執行多餘渲染:
關注公衆號《不同的前端》,以不同的視角學習前端,快速成長,一塊兒把玩最新的技術、探索各類黑科技