經過前幾天的介紹後,我們知道了如何設計 component 以及設計 component 上的一些問題,今天我們要來了解 hook 到底是怎麼解決相關問題,以及怎麼提升效能的。
HOOK 就像我們昨天說到的,是 React 在去年釋出的新 API ,讓 function 也可以使用生命週期函數來接取資料以及擁有 state,這是因為它使用了 useState 和 useEffect 兩種方法,這邊我們將同時說明並改造之前的 class component 。
這個函數可以讓 function 也擁有 state,他同時創建 state 和專屬用來改變這個 state 的 function。
我們將前幾天的小練習改造成 hook 吧!
首先我們一次性將useEffect、useState import 進 app.js
import React, { useState,useEffect } from "react";
接下來,先把 componentDidMount、componentWillUnmount、componentDidUpdate 先註解掉,等等才會用到他們
註: 註解快捷鍵 ctrl+/
建立兩個 useState
const [count,setCount] = useState(0)
const [memory,setMemory] = useState("infinity")
這樣我們的 state 就設定好了
註:const 是一種常用來宣告物件的方式,未來會針對宣告做詳細說明。
接下來將 class component 和 app 改為 function component,並刪除 constructor 與 render,要注意不要刪錯喔。
function App() {
return (
<div>
<h1>Hello World</h1>
<ElderBrother />
</div>
)
}
function ElderBrother() {…}
將 function 也改為 const 宣告,並使用 useState 自己的 function 更新資料。
const addCount = () => {
setCount(count+1)
};
const handleChange = () => {
setMemory( "i'm gooooood")
setCount(count+1)
};
把 component 內的 props 和 state 改為我們設定的
<YoungerBrother paper={memory} handleChange={handleChange}/>
<h3>{count}</h3>
useState 的部分就大概完成了,畫面會長這樣。
我們來試試看 change。
功能上沒甚麼問題。
這個函數可以看成 componentDidMount、componentWillUnmount、componentDidUpdate 的綜合版,他跟 useState一樣,可以多次宣告,達到關注點分離,不過在我們這個例子會比較看不出效果XD,等後面寫系列專案就可以看出差別。
現在我們來試試看怎麼使用 useEffect,我們先用一個簡單的 console.log()測試一下
function ElderBrother() {
const [count,setCount] = useState(0)
const [memory,setMemory] = useState("infinity")
useEffect(()=>console.log('useEffect'))
const addCount = () => {...};
const handleChange = () => {...};
return (...);
}
我們來看一下 devtool 的結果
很清楚地出來了。
那你可能會問說,那我要怎麼分辨甚麼時候是 mount、updating、unmount。
在觸發一次 change 試試看。
這邊可以發現,當 updating 時也會觸發 useEffect。
那我們可以在 useEffect 抓取準確的 state 嗎?我們修改一下 console.log。
useEffect(()=>console.log(`useEffect count:${count}`))
重新執行之後多案幾次 change 試試看。
就算我們沒有綁定 change 的 callback function, 也能及時抓的到 count 的值。
這是因為,在每次更新 state 觸發 re-render 時,react 都會安排一個 different effect 來替代上一個,這根據 react 的 DOM 更新規則,讓 useEffect 的動作更像是 render 結果的一部分,所以它才能抓到當下更新之後的 count。
而每一個 useEffect 更新的結果都是獨立的,我們可以透過更新 count 跟 memory 來試試差別。
我們需要拆開 change 的功能 ,讓兩個 state 各自更新。
const ChangeCount = () => {
setCount(count+1)
};
const ChangeMemory =(e)=>{
setMemory(e.target.value)
}
這邊將 addCount 和 handleChange 統一命名,不做好這部分的話,你以後要看 code 會想垂死自己。
現在實做一下畫面,將 state 顯示在父 component,等等比較會直覺一點。
<div>
<h1>我是 Hook</h1> //原本是 "我是 class " 這邊也改一下才不會搞混。
<YoungerBrother paper={memory} ChangeCount={ChangeCount} ChangeMemory={ChangeMemory}/>
<h3>{count}</h3>
<h3>{memory}</h3>
</div>
子 component 只留 change 的實作。
<div>
<h1>我是 function</h1>
<p>
<button onClick={props.handleChange}>change</button>
</p>
<input value={props.paper} onChange={props.ChangeMemory} />
</div>
最後,我們來更改 useEffect,並添加依賴( Dependency ),這樣的話就可以確保只有在依賴對像被更新時才觸發 useEffect,並且達到關注點分離的效果,白話一點,這樣做比較看得懂。
useEffect(() => console.log(`useEffect count:${count}`),[count]);
useEffect(() => console.log(`useEffect memory:${memory}`),[memory]);
註:Dependency,意思是當依賴的物件被更改時,會觸發被依賴的動作,會在架構設計時詳細介紹。
畫面會長這樣。
觸發 ChangeCount。
觸發 ChangeMemory
可以發現 useEffect 可以處理各自的 state ,再也不用擔心會生命週期混雜著不同的邏輯了
當然,你也可以把 Dependency 設置為 "empty array" 就長這樣 => [],這樣的話 useEffect 就只會在 mount 被觸發。
而 unmount 可以在 useEffect 使用 return 觸發,這邊以 count 的 useEffect為例。
useEffect(() => {
console.log(`useEffect count:${count}`);
return function unSubscript() {
console.log("clean");
};
}, [count]);
我們添加一個 console.log測試,按下change。
會發現 unmount 被觸發了 !?
回想一下我們剛剛講的,"在每次更新 state 觸發 re-render 時,react 都會安排一個 different effect 來替代上一個",所以就會觸發 unmount,大概流程會像下圖這樣。
最後來比較一下 hook 和 class component
class ElderBrothers extends Component {
constructor(props) {
super(props);
this.state = {
memory: "infinity",
count:0
};
}
componentDidMount() {
console.log(
"memory : " + this.state.memory + ", count : " + this.state.count
);
}
componentWillUnmount() {
this.unSubscript();
console.log("component will unmount");
}
componentDidUpdate() {
console.log("DidUpdate => memory : " + this.state.memory + ", count : " + this.state.count);
}
ChangeCount = () => {
this.setState({count:this.state.count + 1});
};
ChangeMemory = (e) => {
this.setState({memory:e.target.value});
};
unSubscript() {
console.log("clean");
};
render() {
return (
<div>
<h1>我是 class</h1>
<YoungerBrother
paper={this.state.memory}
ChangeCount={this.ChangeCount}
ChangeMemory={this.ChangeMemory}
/>
<h3>{this.state.count}</h3>
<h3>{this.state.memory}</h3>
</div>
);
}
}
function ElderBrother() {
const [count, setCount] = useState(0);
const [memory, setMemory] = useState("infinity");
//--- count---//
const ChangeCount = () => {
setCount(count + 1);
};
useEffect(() => {
console.log(`useEffect count:${count}`);
return function unSubscript() {
console.log("clean");
};
}, [count]);
//------------------//
//---memory---//
useEffect(() => console.log(`useEffect memory:${memory}`), [memory]);
const ChangeMemory = (e) => {
setMemory(e.target.value);
};
//-------------------//
return (
<div>
<h1>我是 class</h1>
<YoungerBrother
paper={memory}
ChangeCount={ChangeCount}
ChangeMemory={ChangeMemory}
/>
<h3>{count}</h3>
<h3>{memory}</h3>
</div>
);
}
應該可以看出兩者之間的差別,hook 的可讀性明顯比 class component 好很多,越大型的專案其中的差異會越來越明顯。
我是 Chris,我們今天簡單介紹了 useState 和 useEffect,但還有很多 hook api 我們今天沒有接觸到,我們會在接下來的系列專案中盡量帶到,明天我們將補一下之前沒講到的部分觀念。