1。 引言

為什麼要了解 Function 寫法的元件呢?因為它正在變得越來越重要。

那麼 React 中

Function Component 與 Class Component 有何不同?

how-are-function-components-different-from-classes 這篇文章帶來了一個獨特的視角。

順帶一提,以後會用 Function Component 代替 Stateless Component 的說法,原因是:自從 Hooks 出現,函式式元件功能在不斷豐富,函式式元件不再需要強調其無狀態特性,因此叫 Function Component 更為恰當。

2。 概述

原文事先申明:並沒有對 Function 與 Classes 進行優劣對比,而僅僅進行特性對比,所以不接受任何吐槽。

這兩種寫法沒有好壞之分,效能差距也幾乎可以忽略,而且 React 會長期支援這兩種寫法。

Capture props

對比下面兩段程式碼。

Class Component:

class ProfilePage extends React。Component {

showMessage = () => {

alert(“Followed ” + this。props。user);

};

handleClick = () => {

setTimeout(this。showMessage, 3000);

};

render() {

return

}

}

Function Component:

function ProfilePage(props) {

const showMessage = () => {

alert(“Followed ” + props。user);

};

const handleClick = () => {

setTimeout(showMessage, 3000);

};

return

}

(線上 Demo)

這兩個元件都描述了同一個邏輯:點選按鈕 3 秒後

alert

父級傳入的使用者名稱。

如下父級元件的呼叫方式:

那麼當點選按鈕後的 3 秒內,父級修改了

this。state。user

,彈出的使用者名稱是修改前的還是修改後的呢?

Class Component 展示的是修改後的值:

精讀《Function VS Class 元件》

Function Component 展示的是修改前的值:

精讀《Function VS Class 元件》

那麼 React 文件中描述的

props

不是不可變(Immutable) 資料嗎?為啥在執行時還會發生變化呢?

原因在於,雖然

props

不可變,是

this

在 Class Component 中是可變的,因此

this。props

的呼叫會導致每次都訪問最新的

props

而 Function Component 不存在

this。props

的語法,因此

props

總是不可變的。

為了便於理解,筆者補充一些程式碼註解:

Function Component:

function ProfilePage(props) {

setTimeout(() => {

// 就算父元件 reRender,這裡拿到的 props 也是初始的

console。log(props);

}, 3000);

}

Class Component:

class ProfilePage extends React。Component {

render() {

setTimeout(() => {

// 如果父元件 reRender,this。props 拿到的永遠是最新的。

// 並不是 props 變了,而是 this。props 指向了新的 props,舊的 props 找不到了

console。log(this。props);

}, 3000);

}

}

如果希望在 Class Component 捕獲瞬時 Props,可以:

const props = this。props;

,但這樣的程式碼很蹩腳,所以如果希望拿到穩定的

props

,使用 Function Component 是更好的選擇。

Hooks 也具有 capture value 特性

看下面的程式碼:

function MessageThread() {

const [message, setMessage] = useState(“”);

const showMessage = () => {

alert(“You said: ” + message);

};

const handleSendClick = () => {

setTimeout(showMessage, 3000);

};

const handleMessageChange = e => {

setMessage(e。target。value);

};

return (

<>

);

}

(線上 Demo)

在點選

Send

按鈕後,再次修改輸入框的值,3 秒後的輸出依然是

點選前輸入框的值

。這說明 Hooks 同樣具有 capture value 的特性。

利用

useRef

可以規避 capture value 特性:

function MessageThread() {

const latestMessage = useRef(“”);

const showMessage = () => {

alert(“You said: ” + latestMessage。current);

};

const handleSendClick = () => {

setTimeout(showMessage, 3000);

};

const handleMessageChange = e => {

latestMessage。current = e。target。value;

};

}

只要將賦值與取值的物件變成

useRef

,而不是

useState

,就可以躲過 capture value 特性,在 3 秒後得到最新的值。

這說明了利用 Function Component + Hooks 可以實現 Class Component 做不到的 capture props、capture value,而且 React 官方也推薦 新的程式碼使用 Hooks 編寫。

3。 精讀

原文 how-are-function-components-different-from-classes 從一個側面講述了 Function Component 與 Class Component 的不同點,之所以將 Function Component 與 Class Component 相提並論,幾乎都要歸功於 Hooks API 的出現,有了 Hooks,Function Component 的能力才得以向 Class Component 看齊。

關於 React Hooks,之前的兩篇精讀分別有過介紹:

精讀《React Hooks》

精讀《怎麼用 React Hooks 造輪子》

但是,雖然 Hook 已經發布了穩定版本,但周邊生態跟進還需要時間(比如

useRouter

)、最佳實踐整理還需要時間,因此不建議重構老程式碼。

為了更好的使用 Function Component,建議時常與 Class Component 的功能做對比,方便理解和記憶。

下面整理一些常見的 Function Component 問題:

非常建議完整閱讀 React Hooks FAQ。

怎麼替代 shouldComponentUpdate

說實話,Function Component 替代

shouldComponentUpdate

的方案並沒有 Class Component 優雅,程式碼是這樣的:

const Button = React。memo(props => {

// your component

});

或者在父級就直接生成一個自帶

memo

的子元素:

function Parent({ a, b }) {

// Only re-rendered if `a` changes:

const child1 = useMemo(() => , [a]);

// Only re-rendered if `b` changes:

const child2 = useMemo(() => , [b]);

return (

<>

{child1}

{child2}

);

}

相比之下,Class Component 的寫法通常是:

class Button extends React。PureComponent {}

這樣就自帶了

shallowEqual

shouldComponentUpdate

怎麼替代 componentDidUpdate

由於

useEffect

每次 Render 都會執行,因此需要模擬一個

useUpdate

函式:

const mounting = useRef(true);

useEffect(() => {

if (mounting。current) {

mounting。current = false;

} else {

fn();

}

});

更多可以檢視 精讀《怎麼用 React Hooks 造輪子》

怎麼替代 forceUpdate

React 官方文件提供了一種方案:

const [ignored, forceUpdate] = useReducer(x => x + 1, 0);

function handleClick() {

forceUpdate();

}

每次執行

dispatch

時,只要

state

變化就會觸發元件更新。當然

useState

也同樣可以模擬:

const useUpdate = () => useState(0)[1];

我們知道

useState

下標為 1 的項是用來更新資料的,而且就算資料沒有變化,呼叫了也會重新整理元件,所以我們可以把返回一個沒有修改數值的

setValue

,這樣它的功能就僅剩下重新整理元件了。

更多可以檢視 精讀《怎麼用 React Hooks 造輪子》

state 拆分過多

useState

目前的一種實踐,是將變數名打平,而非像 Class Component 一樣寫在一個 State 物件裡:

class ClassComponent extends React。PureComponent {

state = {

left: 0,

top: 0,

width: 100,

height: 100

};

}

// VS

function FunctionComponent {

const [left,setLeft] = useState(0)

const [top,setTop] = useState(0)

const [width,setWidth] = useState(100)

const [height,setHeight] = useState(100)

}

實際上在 Function Component 中也可以聚合管理 State:

function FunctionComponent() {

const [state, setState] = useState({

left: 0,

top: 0,

width: 100,

height: 100

});

}

只是更新的時候,不再會自動 merge,而需要使用

。。。state

語法:

setState(state => ({ 。。。state, left: e。pageX, top: e。pageY }));

可以看到,更少的黑魔法,更可預期的結果。

獲取上一個 props

雖然不怎麼常用,但是畢竟 Class Component 可以透過

componentWillReceiveProps

拿到

previousProps

nextProps

,對於 Function Component,最好透過自定義 Hooks 方式拿到上一個狀態:

function Counter() {

const [count, setCount] = useState(0);

const prevCount = usePrevious(count);

return (

Now: {count}, before: {prevCount}

);

}

function usePrevious(value) {

const ref = useRef();

useEffect(() => {

ref。current = value;

});

return ref。current;

}

透過

useEffect

在元件渲染完畢後再執行的特性,再利用

useRef

的可變特性,讓

usePrevious

的返回值是 “上一次” Render 時的。

可見,合理運用

useEffect

useRef

,可以做許多事情,而且封裝成 CustomHook 後使用起來仍然很方便。

未來

usePrevious

可能成為官方 Hooks 之一。

效能注意事項

useState

函式的引數雖然是初始值,但由於整個函式都是 Render,因此每次初始化都會被呼叫,如果初始值計算非常消耗時間,建議使用函式傳入,這樣只會執行一次:

function FunctionComponent(props) {

const [rows, setRows] = useState(() => createRows(props。count));

}

useRef

不支援這種特性,需要寫一些冗餘的函判定是否進行過初始化。

掌握了這些,Function Component 使用起來與 Class Component 就幾乎沒有差別了!

4。 總結

Function Component 功能已經可以與 Class Component 媲美了,但目前最佳實踐比較零散,官方文件推薦的一些解決思路甚至不比社群第三方庫的更好,可以預料到,Class Component 的功能會被五花八門的實現出來,那些沒有被收納進官方的 Hooks 乍看上去可能會眼花繚亂。

總之選擇了 Function Component 就同時選擇了函式式的好與壞。

好處是功能強大,幾乎可以模擬出任何想要的功能

壞處是由於可以靈活組合,如果自定義 Hooks 命名和實現不夠標準,函式與函式之間對接的溝通成本會更大。

討論地址是:精讀《Stateless VS Class 元件》 · Issue #137 · dt-fe/weekly

如果你想參與討論,請

點選這裡,每週都有新的主題,週末或週一釋出。前端精讀 - 幫你篩選靠譜的內容。

關注

前端精讀微信公眾號

精讀《Function VS Class 元件》

special Sponsors

DevOps 全流程平臺

版權宣告:自由轉載-非商用-非衍生-保持署名(創意共享 3。0 許可證)