今天要進入到react的"狀態",時間安排上看起來有點來不及,後面關於react的篇幅會做一些縮減(因為還有Laravel),不然可能沒時間完成最後的小專案。
今天還是一樣拿官方範例出來做臨摹跟理解~
State 最基本的功能就是在react之中去共享數據,使用state的好處是:所有跟state有關係的部分都可以因為state的更新而一起做出改變。
最後是最重要的一點:我們透過前面的文章可以知道「Component」會透過資料改變來更新UI(re-render),然而 Component的資料來源有兩種:
1. 透過props從外部傳送進Component的
2. Component的state,而State類似於 prop,但它是私有(private)且由 component 完全控制的,簡單來說就是:State是屬於Component內部所使用的
昨天有說到「官方文件在這裡有埋下一個伏筆:Function 和 Class component 兩者都擁有額外的特性」
這裡就出現了Class Component的額外特性了 --- State,只能在class Component之中使用。
在昨天介紹element的範例時,官方利用更新element的方式來變更UI,官方在這個章節提供了不同的示範:
第一個方法是封裝Clock Component更新Props,透過Props的更新
所以程式碼會改成:
const root = ReactDOM.createRoot(document.getElementById('root'));
//這裡將clock從 element 取出 封裝成一個 Component
function Clock(props) {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {props.date.toLocaleTimeString()}.</h2>
</div>
);
}
function tick() {
root.render(<Clock date={new Date()} />);
}
setInterval(tick, 1000);
謎之音:不就是把東西在另外拆一個function出來嗎?
沒錯XD,我覺得react的文件看到現在,官方很想塑造 "單一職責原則的觀念" ,所以這邊才把clock另外抓出來封裝,透過這種方式來明確的達到複用性:
const root = ReactDOM.createRoot(document.getElementById('root'));
function Clock(props) {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {props.date.toLocaleTimeString()}.</h2>
</div>
);
}
function tick() {
root.render(
<div>
<Clock date={new Date()} />
//這裡再多叫一次
<Clock date={new Date()} />
</div>
);
}
setInterval(tick, 1000);
我們直接叫兩次clock就可以知道好處了。
這樣做並沒有問題,不過想法上有一些怪怪的;照理來說,我們想要每秒更新clock,但是clock的時間應該是存在於clock之中不應該由props去決定更新的Clock要長怎樣。
為了達到我們的目的:只需要呼叫clock 就會自動更新時間這件事情
我們必須要借助state的力量。
在使用state之前,我們要先把剛剛function component改成class component
官方五步驟,看好跟著做:
1. 建立一個相同名稱並且繼承 React.Component 的 ES6 class。
2. 加入一個 render() 的空方法。
3. 將 function 的內容搬到 render() 方法。
4. 將 render() 內的 props 替換成 this.props。
5. 刪除剩下空的 function 宣告。
基本上我們就直接照著步驟改一次,需要補充的我會直接寫在code 之中
class Clock extends React.component{ //ES6的class 繼承 react.component
render(){ //一個沒有參數的空方法render
return ( //要回傳的內容
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2> //注意,這裡要改成"這個class的props,而不是單純用function的props"
</div>
);
}
}
但現在我們還是沒有把date換成state,到這裡如果直接執行會有繼承brabrabra的問題
原因是因為我們在這個class之中少一個建構式
所以加入:
constructor(props) {
super(props); //super是繼承父層的property
// Clock 元件顯示到畫面時,需要知道目前的時間是什麼
// 所以我們需要先初始化好 state 資料
this.state = {date: new Date()};
}
這個時候就可以把render之中return 的this.prop.date 改成 this.state.date
因為在construst之中把date 設定到this.state裡面了
這個時候我們執行一次完整的code會發現,畫面只有當下的時間並不會每秒更新廢話,因為這個從頭到尾只有執行一次阿
所以在這邊我們要幫這個Component設定一個生命週期
每當Clock Component render到Dom的時候叫做mount
每個clock DOm要被移除的時候叫做unmount
Unmount非常重要:在具有許多 component 的應用程式中,當 component 被 destroy 時,釋放所佔用的資源是非常重要的。
我們先寫一個用來變更state的function。注意:除了在construct之中,其餘地方不可使用this.state.xxx = yyyy
其餘地方要更新state要使用"setState function"
所以我們這裡寫個簡單的function 來固定時間更新state
changeState(){
this.setState(
date: new Date()
)
}
接著寫一個componentDidMount,用來呼叫changeState
componentDidMount(){
//this在這裡註冊了一個timerID的屬性
this.timerID = setInterval(
() => this.tick(),
1000
);
}
最後一個componentWillUnmount,用來殺掉被Dom移除的Component
關於clearIntervial語法可參考這裡
componentWillUnmount() {
//每一秒都會有新的State被更新,所以要回來把舊的component移除
clearInterval(this.timerID);
}
所以官方最後的成品是這樣:
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 />);
因為clock Component 用State的方式更新,所以不需要再丟props給Component了
另外官方文件有特別提到幾點關於state需要注意的事項:
1. State 的更新可能是非同步的(官方寫可能,但我認為可以直接把"可能"拿掉)
2. State 的更新將會被 Merge
1.關於state的更新是非同步這個概念,主要是react因為效能考量,會將多次的setStatus合併成一次來更新
這就會導致一個問題:
handleAsyncProblem(){
this.setStatus({
counter: this.status.counter + this.props.increment,
});
this.setStatus({
counter: this.status.counter + this.props.increment,
//這時候這裡拿到的this.status.counter 不一定是上面更新過的
});
}
因此官方文件延伸了一個解決這個問題的方式,就是讓 setStatus 可以接受 function 來解決這個問題
這個function可以接受兩個parameter:第一個參數是 "接收先前的status"白話文就是,第一個參數會確保目前的status是最新的
接下來的第二個參數是this.props
所以上面的要修改成以下寫法:
handleAsyncProblem(){
this.setStatus ( (prev_status , prop) => {
counter: prev_status.counter + prop.increment
})
}
2.State 的更新將會被 Merge
白話文就是:state之中可能有多個的獨立屬性,我們可以單獨操作個別屬性而不影響到沒改變的屬性,確保資料的完整性。
如果看不太懂得白話文的同學們,可以看一下官方文件的範例,因為這個範例我想不到需要解釋的地方XD
向下資料流是個很特別的概念,我們前面講了那麼多的state、props、component.....等。
但我們沒有講到state可以互相傳遞!!剛最上面有說每個component的staus是private的且由各自的component完全控制
這代表著其他component沒辦法互相操作state嗎?
按照道理來說是,不過官方有給另一條思路,那就是把component的state當成props再傳給其他的child component
這個做法就叫做向下資料流
官方文件寫的這句話會讓人更疑惑不知道他在講甚麼,所以我建議直接到CodePen看看立刻就能懂囉!
今天的資料量其實很龐大,看文件、找資料 + 吸收轉化 也是花了很多時間。
如果我有理解錯的地方,大神可以在底下留言來讓我知道
明天 開始 會把官方文件的 主要概念 6 ~ 12 開始精簡化XD