函數組件和類組件有什么不同,在編碼過程中應該如何選擇呢?
一、什么是函數組件
定義一個組件最簡單的方式就是使用 JavaScript 函數:
import React from 'react'
const Welcome = (props) => {
return <h1>welcome, {props.name}</h1>
}
export default Welcome
這個函數接收一個 props 對象並返回一個 react 元素
二、什么是類組件
React 可以使用 ES6 class 語法去寫一個組件:
import React from 'react'
class Welcome extends React.Component {
constructor(props) {
super(props)
}
render() {
return <h1>welcome, {this.props.name}</h1>
}
}
export default Welcome
這兩個版本是等價的,它們具有相同的輸出。那么我們應該去選擇哪一種實現方式呢?
三、函數組件與類組件的區別
1、語法上
兩者最明顯的不同就是在語法上:
函數組件是一個純函數,它接收一個 props 對象返回一個 react 元素;
類組件需要去繼承 React.Component 並且創建 render 函數返回 react 元素,雖然實現的效果相同,但需要更多的代碼。
2、狀態管理
因為函數組件是一個純函數,所以不能在組件中使用 setState(),這也是為什么把函數組件稱作為無狀態組件。
如果要在組件中使用 state,可以選擇創建一個類組件或者將 state 提升到你的父組件中,然后通過 props 對象傳遞到子組件。
3、生命周期鈎子
函數組件中不能使用生命周期鈎子,原因和不能使用 state 一樣,所有的生命周期鈎子都來自於繼承的 React.Component 中。
因此,如果要使用生命周期鈎子,就需要使用類組件。
注意:在
react16.8版本中添加了hooks,使得我們可以在函數組件中使用useState鈎子去管理state,使用useEffect鈎子去使用生命周期函數。
因此,2、3 兩點就不是它們的區別點。
而從這個改版中我們也可以看出 React 團隊更看重函數組件,而且曾提及到在react之后的版本將會對函數組件的性能方面進行提升。
4、調用方式
如果 SayHi 是一個函數,React 需要調用它:
// 你的代碼
function SayHi() {
return <p>Hello, React</p>
}
// React 內部
const result = SayHi(props) // » <p>Hello, React</p>
如果 SayHi 是一個類,React 需要先用 new 操作符將其實例化,然后調用剛才生成實例的 render 方法:
// 你的代碼
class SayHi extends React.Component {
render() {
return <p>Hello, React</p>
}
}
// React 內部
const instance = new SayHi(props) // » SayHi {}
const result = instance.render() // » <p>Hello, React</p>
可想而知,函數組件重新渲染將重新調用組件方法返回新的 react 元素,類組件重新渲染將 new 一個新的組件實例,然后調用 render 類方法返回 react 元素,這也說明為什么類組件中 this 是可變的。
5、獲取渲染時的值
這一點是他們最大差異,但又常常被人們忽略。
考慮以下組件:
function ProfilePage(props) {
const showMessage = () => {
alert('Followed ' + props.user);
}
const handleClick = () => {
setTimeout(showMessage, 3000);
}
return (
<button onClick={handleClick}>Follow</button>
)
}
UserProfile 組件很簡單,就一個 Follow 按鈕,該按鈕使用了 setTimeout 模擬網絡請求。用戶點擊這個按鈕之后會彈出一個警告框。如果 props.user 為 'Dan',它將在三秒鍾后顯示 'Followed Dan'。
我們如何將其編寫為類?天真的翻譯可能像這樣:
class ProfilePage extends React.Component {
showMessage() {
alert('Followed ' + this.props.user);
}
handleClick() {
setTimeout(this.showMessage.bind(this), 3000);
}
render() {
return <button onClick={this.handleClick.bind(this)}>Follow</button>
}
}
通常認為這兩個代碼段是等效的。人們經常在這些模式之間自由重構,而沒有注意到它們的含義
但是,這兩個代碼段是完全不同的。
分別按下面的順序來操作 Follow 按鈕:
- 先點擊
Follow按鈕 - 在
3s之前更改下拉選擇項的選項 - 閱讀彈出的警告框內容
這就發現函數組件和類組件是有區別的:
函數組件:按上面所列的三個步驟操作時,當用戶在 3s 前更改下拉選擇框的選項時,h1 的用戶名會立馬改變,而 3s 后彈出的警告框中的用戶名並不會改變
類組件:按上面所列的三個步驟操作時,當用戶在 3s 前更改下拉選擇框的選項時,h1 中的用戶名會立馬改變,而 3s 后彈出的警告框中的用戶名也會改變


那么,為什么我們的類示例會這樣表現呢?
讓我們仔細看一下 showMessage 類中的方法:
showMessage() {
alert('Followed ' + this.props.user);
}
在 showMessage 方法中讀取了 this.props.user(也是我們要輸出的用戶名稱)。而 React 中的 props 是不可變的,但是 this 是可變的,而且是一直是可變的。這也是類組件中 this 的目的。React 自身會隨着時間的推移對 this 進行修改,以便在 render 函數或生命周期中讀取新的版本。
因此,如果組件在請求重新渲染時,this.props 將會改變。showMessage 方法會從新的 props 中讀取 user。所看到的效果也正是因為這個原因。
在 React 中的組件,UI 在概念上可以理解是程序當前狀態的函數,那么事件處理就是讓 UI 的渲染結果一部分一部分可視化輸出。我們的事件處理程序屬於具有特定 props 和 state 的特定渲染。但是,當回調超時的話,this.props 就會打破這種聯系。示例中的 showMessage 方法在回調時沒有綁定到任何特定的渲染,因此它會丟失真正的 props。
那么有沒有一種較好的方式可以使用正確的 props 來修復 render 和 showMessage 回調之間的聯系呢?
我們可以在事件發生的早期,將 this.props 傳遞給超時完成的處理程序來嘗試着解決這個問題。這種解決方式屬於閉包的范疇。
class ProfilePage extends React.Component {
showMessage(user) {
alert('Followed ' + user);
}
handleClick() {
cosnt {user} = this.props
setTimeout(this.showMessage.bind(this, user), 3000);
}
render() {
return <button onClick={this.handleClick.bind(this)}>Follow</button>
}
}
我們使用閉包機制將上一狀態的值保存下來待 showMessage 方法調用。即使 this.props 發生變化,但並不改變 user
這種方法雖然解決我們前面所提到的問題,但是這種方法代碼會隨着 props 的個數增加,代碼也會變得更加冗余也易於出錯。
如果我們也需要訪問 state。如果 showMessage 調用另一個方法,該方法會讀取 this.props.something 或 this.state.something。
我們又會碰到同樣的問題。所以我們必須通過 this.props 作為 showMessage 的參數來修復它們之間存在的問題。
但這么做會破壞類提供的特性,也令人難於記住或執行。
另外,在 handleClick 中內聯 alert 中的代碼並不能解決更大的問題。
我們希望以一種允許代碼分解成更多方法的方式來構造代碼,同時還可以讀取與其相關的 render 所對應的 props 和 state。
或許,我們可以在類的構造函數中綁定這些方法:
class ProfilePage extends React.Component {
render() {
// 獲取 props
cosnt props = this.props
// 它們不是類方法
const showMessage = () => {
alert('Followed ' + props.user);
}
const handleClick = () => {
setTimeout(showMessage, 3000)
}
return <button onClick={handleClick}>Follow</button>
}
}
這樣一來,函數組件和類組件所達到的效果都一樣了。在類組件中可以捕獲渲染時的 props。效果上看上去是一樣了,但看起來怪怪的。如果在類組件中的 render 中定義函數而不是使用類方法,那么還有使用類的必要性?
