1. 程式人生 > >React函式式元件和類元件[Dan]

React函式式元件和類元件[Dan]

> 一篇對`Dan`的 How Are Function Components Different from Classes? 一文的個人閱讀總結,內容來自於此。強烈推薦閱讀 [Dan Abramov.的部落格](https://mobile.twitter.com/dan_abramov)。 函式式元件和Class元件有什麼不同? `Dan`很直接的給出了答案: **函式式元件捕獲了渲染所用的值。(Function components capture the rendered values.)** 直接看結論可能有點不知所云。 ## `class`元件可能引發的"錯誤"
看一個元件,使用`setTimeout`模擬網路請求,點選`button`之後警告提示關注某人(`user`),`user`從`props`中讀取。 該元件的`function`版本: ```jsx function ProfilePage(props) { const showMessage = () =>
{ alert('Followed ' + props.user); }; const handleClick = () => { setTimeout(showMessage, 3000); }; return ( ); } ``` `class`版本: ```jsx class ProfilePage extends React.Component { showMessage = () => { alert('Followed ' + this.props.user); }; handleClick = () =>
{ setTimeout(this.showMessage, 3000); }; render() { return ; } } ``` 頁面元件程式碼: ```jsx class App extends React.Component { state = { user: 'Dan', }; render() { return ( <>

Welcome to {this.state.user}’s profile!

(function)

(class)

Can you spot the difference in the behavior?

) } } ``` 對於`class`元件,*在選中狀態是`userA`的時候,點選`follow button`之後立馬將`select`切換其他人(`uerB`),三秒之後的彈出框是`follow`的**`userB`**。(這個動作會在後面多次提及)* 在選中`userA`的時候點選關注,目的就是關注`userA`,但是`class`元件最後彈出框顯示的關注`userB`,這顯然不符合預期。 ## 為什麼、如何解決
如果上面例子使用`function`元件,彈出框顯示的就會是正確的,雖然切換到了選中的`userB`,但是彈出框顯示的仍然是點選的那一刻關注的`userA`。 *`function`元件好像記下了點選那一刻時候的狀態?* `class`元件在這個場景中錯誤的原因是`class`元件每次三秒後從`this.props.user`中讀取資料,此時的`this.props.user`已經變了,已經是切換後的**新的`this.props.user`**資料。雖然**React中`props`不可變,但是`this`是可變的**。 類元件會隨著時間推移改變,在渲染方法和生命週期方法中得到的是最新的例項。而函式式元件的事件處理程式就是渲染結果的一部分,**事件處理程式屬於一個擁有特定的`props`和`state`的渲染**。 也就是說函式式元件保持了事件處理程式與那一次渲染`props`的`state`之間的聯絡,本身就是正確的。
來修復類元件中的這個問題: 1. 可以在點選的時候就讀取並記錄當下的`state`或`props`,三秒後讀取記錄的資料(而不是讀`this.props.xx`)再彈出。 方案可行但是擴充套件極差,在其他多個變數也這樣做的時候逐層記錄或傳遞非常繁複。 2. 閉包。 > 閉包維持了一個可能隨時間變化的變數,而此處我們要維持的是React的`props`或`state`,React設計中這都是不可變的。讓閉包來維持不變的`state`和`props`,此時再去捕獲這些值,就是一致的。 在`render`函式中使用閉包: ```jsx class ProfilePage extends React.Component { render() { // Capture the props! const props = this.props; // Note: we are *inside render*. // These aren't class methods. const showMessage = () => { alert('Followed ' + props.user); }; const handleClick = () => { setTimeout(showMessage, 3000); }; return ; } } ``` 渲染的時候這些需要使用的`props`已經被捕獲(就像上面`方案1`的記錄,在`render`的時候就已經讀取記錄下了)。此時表現彈出內容就會是點選時候的那個`userA`了。
`class`元件的這個問題是修復了,但是在`render`函式中新增那麼多的函式,且並沒有掛載到`class`上,有點奇怪? 其實去掉`class`,這就是函式式元件的形式了: ```jsx function ProfilePage(props) { const showMessage = () => { alert('Followed ' + props.user); }; const handleClick = () => { setTimeout(showMessage, 3000); }; return ( ); } ``` `React`將他們作為引數傳遞,`props`在渲染時被捕獲了。不同於`class`元件的`this`,這裡的`props`不會被改變。 點選事件處理函式,該函式屬於具有正確`user`值的一次渲染,事件處理函式和其他回撥函式也能讀到這個值。 回頭看這個結論,是不是更好理解一點了: **函式式元件捕獲了渲染所使用的值** ## 函式式元件使用最新的`props`和`state`
函式式元件捕獲了特定渲染的`props`和`state`。但是我們如果又想和`class`元件一樣讀取最新的`props`和`state`呢? **useRef** > Dan 老師:在函式式元件中,你也可以擁有一個在所有的元件渲染幀中共享的可變變數。它被成為“ref” > > `this.something`就像是`something.current`的一個映象。他們代表了同樣的概念。 每一次的渲染結果可以視為一個渲染幀,共享的變數設定為`ref`,包含`DOMRef`和`class`中的例項變數的功能,可以說是非常強大了。 需要最新的`props`和`state`值,可以使用`useRef`建立的變數來記錄,通過`useEffect`可以在值變化的時候自動追蹤。 ```jsx function MessageThread() { const [message, setMessage] = useState(''); // 保持追蹤最新的值。 const latestMessage = useRef(''); useEffect(() => { latestMessage.current = message; }); const showMessage = () => { alert('You said: ' + latestMessage.current); }; ``` **React函式總是捕獲他們的