React
推出後,前後出現了三種定義組件的方式,分別是:css
React.createClass
建立組件React.Component
建立組件相信你們在平常中使用的最多的仍是函數式組件和 React.Component
組件吧,今天就簡單的說下函數式組件的兩個優化方法。react
在談到函數式組件以前咱們先看一個概念 - 純函數
。數組
何爲純函數?dom
引用一段維基百科的概念。函數
在程序設計中,若一個函數符合如下要求,則它可能被認爲是純函數:性能
此函數在相同的輸入值時,需產生相同的輸出。函數的輸出和輸入值之外的其餘隱藏信息或狀態無關,也和由I/O設備產生的外部輸出無關。測試
該函數不能有語義上可觀察的函數反作用,諸如「觸發事件」,使輸出設備輸出,或更改輸出值之外物件的內容等。優化
能夠看到,純函數有着相同的輸入一定產生相同的輸出,沒有反作用的特性。this
同理,函數式組件的輸出也只依賴於 props
和 context
,與 state
無關。spa
this
(相信不少同窗被 this
煩過)state
)class
,沒有 constructor
、extends
等代碼,代碼簡潔,佔用內存小。this
shouldComponentUpdate
,不能避免重複渲染。這裏先看一個例子,代碼以下,也可點擊這裏進行查看。
import * as React from "react";
import { render } from "react-dom";
import "./styles.css";
function App() {
const [n, setN] = React.useState(0);
const onClick = () => {
setN(n + 1);
};
return (
<div className="App">
<div>React.memo demo-1</div>
<button onClick={onClick}>update {n}</button>
<Child />
</div>
);
}
const Child = () => {
console.log("render child");
return <div className="child">Child Component</div>;
};
const rootElement = document.getElementById("root");
render(<App />, rootElement);
複製代碼
它實現了什麼?就一個很簡單的東西,一個父組件包了一個子組件。
這個你們判斷一下,當我點擊父組件的 button
更新 N
的時候,子組件中的 log
會不會執行?
按照通常的思惟來看,你父組件更新關我子組件什麼事?我子組件的 DOM
又不用更新,既然沒有更新,那還打什麼 log
。
但實際效果是,每點擊一次 button
,子組件的 log
就會執行一次,雖然子組件的 DOM
沒更新,但並不表明子組件的渲染函數沒有執行。
如下是執行的效果圖。
針對上述狀況,class
組件可使用 shouldComponentUpdate
來進行優化,可是函數式組件呢?React
一樣也提供了一個優化方法,那就是 React.memo
。
memo
即 memorized
,意思是記住。若是輸入的參數沒有變,依據純函數的定義,輸出也不會變,那麼直接返回以前記憶的結果不就好了。
const Child = React.memo(() => {
console.log("render child");
return <div className="child">Child Component</div>;
});
複製代碼
將 Child
使用 React.memo
處理一下,這樣的話,不管你點擊多少次父組件的 button
,子組件的 log
都不會執行。
完整代碼點這裏。
效果圖以下:
咱們將上述代碼稍微改一下, 讓 Child
接受一個匿名函數,看看會產生什麼後果。完整代碼點這裏。
import * as React from "react";
import { render } from "react-dom";
import "./styles.css";
function App() {
const [n, setN] = React.useState(0);
const onClick = () => {
setN(n + 1);
};
return (
<div className="App">
<div>React.useCallback demo-1</div>
<button onClick={onClick}>update {n}</button>
<Child onClick={() => {}} />
</div>
);
}
const Child = React.memo((props: { onClick: () => void }) => {
console.log("render child");
return <div className="child">Child Component</div>;
});
const rootElement = document.getElementById("root");
render(<App />, rootElement);
複製代碼
觀察代碼能夠看到也沒啥變化嘛,只是子組件接受了一個空函數。
那麼問題又來了,此次點擊 button
子組件的 log
會執行嗎?
看到這裏各位同窗應該會想,每次都傳一個空的匿名函數,props
也沒變啊,那就不用從新渲染唄。具體結果如何,來看下效果:
能夠看到每次點擊 button
時,子組件的 log
依舊會再次執行。那麼這是爲何呢?
由於每次點擊 button
更新父組件的時候,會從新生成一個空的匿名函數,雖然它們都是空的匿名函數,可是它們不是同一個函數。
函數是一個複雜類型的值,JavaScript
在比較複雜類型的值時,是對比它們的內存地址。不是同一個函數,那麼內存地址也就不一樣, React
會認爲子組件的 props
發生了變化,子組件將從新渲染。
那麼怎麼保證子組件每次都接受同一個函數呢?
很簡單。既然父組件在更新的時候會從新生成一個函數,那麼我把函數放到父組件外面不就能夠了嘛,這樣父組件在更新的時候子組件就會接受同一個函數。
代碼以下。也可點擊這裏查看。
import * as React from "react";
import { render } from "react-dom";
import "./styles.css";
const childOnClick = () => {};
function App() {
const [n, setN] = React.useState(0);
const onClick = () => {
setN(n + 1);
};
return (
<div className="App">
<div>React.useCallback demo-2</div>
<button onClick={onClick}>update {n}</button>
<Child onClick={childOnClick} />
</div>
);
}
const Child = React.memo((props: { onClick: () => void }) => {
console.log("render child");
return <div className="child">Child Component</div>;
});
const rootElement = document.getElementById("root");
render(<App />, rootElement);
複製代碼
效果圖以下:
這樣子看起來好像解決了子組件每次都接受不一樣的函數致使從新渲染的問題,可是好像哪裏不對勁,實現也不優雅。
缺點
若是子組件的函數依賴父組件裏面的值,那麼這種方式就不可行。
怎麼辦呢?若是能將函數也 memorized
就行了。
React
在 16.8.0
的版本中正式推出了 Hooks
,其中有一個 Hook
叫作 useCallback
,它能將函數也 memorized
化。
useCallback
接受兩個參數,第一個參數是一個函數,第二個參數是一個依賴數組,返回一個 memorized
後的函數。只有當依賴數組中的值發生了變化,它纔會返回一個新函數。
看看使用 useCallback
後的代碼,也能夠點擊這裏查看。
import * as React from "react";
import { render } from "react-dom";
import "./styles.css";
function App() {
const [n, setN] = React.useState(0);
const [m, setM] = React.useState(0);
const childOnClick = React.useCallback(() => {
console.log(`m: ${m}`);
}, [m]);
return (
<div className="App">
<div>React.useCallback demo-3</div>
<button
onClick={() => {
setN(n + 1);
}}
>
update n: {n}
</button>
<button
onClick={() => {
setM(m + 1);
}}
>
update m: {m}
</button>
<Child onClick={childOnClick} />
</div>
);
}
const Child = React.memo((props: { onClick: () => void }) => {
console.log("render child");
return (
<div className="child">
<div>Child Component</div>
<button onClick={props.onClick}>log m</button>
</div>
);
});
const rootElement = document.getElementById("root");
render(<App />, rootElement);
複製代碼
在上述代碼中,子組件接受一個使用了 useCallback
的函數,它的依賴參數是 m
,只有當 m
發生了變化,子組件接受的函數纔會是一個從新生成的函數。也就是說,不管點擊多少次更新 n
的 button
,子組件都不會更新,只有點擊更新 m
的 button
時,子組件纔會更新。
看看效果如何:
實際效果符合咱們的預期。
看到這裏有些同窗就會想了,若是使用 useCallback
的時候,傳一個空數組做爲依賴數組,那麼子組件就再也不受父組件的影響了,即便你父組件的 m
變化了,我子組件依舊不會從新渲染,這樣子豈不是性能更好?話很少說,咱們來測試一下就行了。代碼點擊這裏查看,效果以下:
能夠看到,雖然子組件確實沒重複渲染了,但一樣的也致使一個問題,打印出來的 m
永遠都是 0,再也讀取不到更新後的 m
的值。
由此能夠得出結論,傳一個空數組做爲依賴數組的後果就是,子組件接受的函數裏面的參數永遠都是初始化使用 useCallback
時的值,這樣的結果並非咱們想要的。
因此歪心思仍是少來了。
隨着 React
正式推出 Hooks
,帶來一系列新的特性,極大地加強了函數式組件的功能,利用這些新特性能夠實現和 class
組件同樣的效果。
有了 React Hooks
,咱們能夠拋棄沉重的 class
組件,使用更加輕便,性能更加優異的函數式組件,所以掌握一些函數式組件的優化方法對咱們使用函數式組件開發是很是有用處的。
React Hooks
無論你香不香,反正我是先香了。
默默說一句,Vue 3.0
也會推出函數式組件。