還記得在Day 8的時候,我有提到過一句話:State,只能在class Component之中使用。
但在前面的實作專案中,相信有眼尖的同學觀察到了:不管是範例還是我們自己建立的Component,通通都是Function Component。
但為什麼還能使用State?而且State的寫法也有一些不一樣。
原因就是"Hook",在進入官網的hook章節就可以看到標題:
Hook 是 React 16.8 中增加的新功能。它讓你不必寫 class 就能使用 state 以及其他 React 的功能。
這裡直接比較兩種State的使用:
Class Component
這裡一樣拿Day8的官方範例
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() { //class內的function不用寫function宣告,直接function name + 括號即可
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date()
});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Clock />);
這裡有幾個要注意的:
1.會把所有的數值都包成object然後寫到this.state之中
2.要多去繼承React.Component,還要多寫construct
3.其他地方要使用state要用 this.state.xxx
4.要寫一個render的方法在裡面return 資料
綜合上面的幾點,我認為使用class 的state使用上相對來說有點繁雜而且也沒那麼直觀。
相反的,我把上面的code改寫成 使用 Function ComponentFunction Component
ps.這裡除了借助State以外還有借助Effect的幫助
import React,{useState , useEffect } from "react";
import ReactDOM from 'react-dom';
function Clock(){
const [clock , setClock] = useState(new Date());
useEffect(() => {
let timerID = setInterval( () => {
setClock(new Date())
} , 1000)
return () => clearInterval(timerID);
});
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {clock.toLocaleTimeString()}.</h2>
</div>
);
}
const clock = ReactDOM.createRoot(document.getElementById('clock'));
clock.render(<Clock />);
上面兩段code的輸出結果是一樣的,但明顯用下面的方式可讀性高很多,也簡短了很多步驟,整體在撰寫上也變得更簡單。
另外官方也有說明使用Hook的好處,所以我就不把文件的資料複製貼上~
主要我覺得這個東西會推出真正的原因是,現存很多js的專案都是使用function來做宣告,如果為了導入React要把所有function都改寫成class,會造成導入上的困擾
因為我還是React初學者,所以我今天只針對官方的兩個預設來做解析~
首先有一個通則:Hook並不會在class之中運作,而Hook出現的目的就是用來取代class
首先,在JavaScript的世界中,我們可以把function 宣告成一個變數,React是在這個基礎之上建立的,想當然也支持這種寫法
const Example = (props) => {
// 你可以在這裡使用 Hook!
return <div />;
}
function Example(props) {
// 你可以在這裡使用 Hook!
return <div />;
}
這兩段程式碼都是可以使用Hook的。
如果要在React Function Component 知終始用State Hook,起手式就是要在import react的地方宣告(把useState這個function引入到檔案中):
import React, { useState } from 'react';
這樣就可以在下面的code中使用State Hook了。
接下來,我們在過去使用Class Component的時候會在construct之中使用setState,並把需要使用的State包成一個object;然而在State Hook之中,我們把不同的State拆分出來,不包在一起,確實的把每個State切割。
因此會使用像這樣的語法:
const [state , setState] = useState(0);
const [clock , setClock] = useState(new Date());
透過多次的宣告來區分不同的State。
基本的宣告結構如下:
const [ {State名稱} , {變更State的方法} ] = useState( {State一開始的初始值} );
因為在上面的state是直接用宣告的方式,實際上已經存在了變數;所以如果要使用state可以直接使用,不用像 Class Component 寫成 this.state.clock
State Hook 直接使用變數名稱clock
即可
在過去使用Class Component的時候,因為所有State都被打包成一個物件,所以如果要更新某一個State必須使用 更新物件的方式來針對特定數值更新this.setState({ clock: new Date()})
然而在State Hook之中,只需要呼叫最一開始設定的變更State的方法名稱,並將更新的數值當成參數丟入即可 setClock(new Date())
其實我覺得effect沒有像State一樣那麼的直觀好理解,根據官方文件:資料 fetch、設定 訂閱、或手動改變 React component 中的 DOM 都是 side effect 的範例。
當我們要使用到 關於生命週期 這個東西的時候,在Function Component必須使用Effect。
我這裡把 我們在 Day22 做的商品詳細頁前端修改一下:
原本的code:
<div className="pt-2 max-w-6xl">
<label>數量:</label>
<input type="number" value={amount} onChange={ amountChange }/>
<p id="price" className="text-red-600">總金額:{product.product_price * amount}</p>
</div>
改成effect後:
useEffect( () => {
document.querySelector("#price").innerHTML = `總金額:${product.product_price * amount}`;
} , [amount]);
<div className="pt-2 max-w-6xl">
<label>數量:</label>
<input type="number" value={amount} onChange={ amountChange }/>
<p id="price" className="text-red-600"></p>
</div>
在我認為,這裡改使用Effect Hook,是為了畫面邏輯的流程控制
Effect Hook的語法結構比State還要更簡單一些:useEffect(callback function, array)
這樣就好了。callback function 必填,array選填。
在 Effect Hook 之中的array是選填,主要是用來判斷要不要執行這個effect;array會有以下三種狀況:
1.不填,useEffect會在每次畫面渲染時都會執行。
2.空陣列[] ,useEffect只會執行一次 (初次 render 之後)
3.array中有值,useEffect則會再該值發生改變後執行
參考來源
Effect Hook 會分成兩種:一種是需要清除的Effect,另一種則是不需要清除的Effect。
官方情境舉例:
有時候,我們希望在 React 更新 DOM 之後執行一些額外的程式碼。網路請求、手動變更 DOM、和 logging,它們都是無需清除 effect 的常見範例。我們之所以這樣說,是因為我們可以執行它們,並立即忘記它們。讓我們比較一下 class 和 Hooks 如何讓我們表達這樣的 side effect。
在Class Component之中, 我們需要兩個生命週期方法來幫助我們達到目的:componentDidMount
(第一次render)、componentDidUpdate
(後續更新)。
如果使用Effect Hook,本身就可以利用array來控制,因此可以大幅度的簡化code。
官方情境舉例:
例如,我們可能想要設定對某些外部資料來源的 subscription。在這種情況下,請務必進行清除,以免造成 memory leak!讓我們比較一下我們可以如何用 class 和 Hook 做到這一點。
在Class Component 之中,我們也需要借助生命週期方法來幫助我們清除資料:componentWillUnmount
(清除)
如果使用Effect Hook,我們只需要在effect 之中回傳function,React 將在需要清除時執行它。
(詳細可以操考上面的改寫clock)
其實Hook還有其他可供使用的Api,官方文件也把Effect的文件寫得 非(篇)常(幅)詳(很)細(長)
我個人認為Effect概念上比State抽象許多,使用起來也沒那麼直觀,所以我這裡只做簡單的理解;如果要做到Effect大師,還是需要到官方文件熟讀唷~
明天我們來試試看 React 常見的前端套件
那我們就明天見啦!