State
在 React 裡面最重要的觀念就是它的 state 會對應到一個 UI,用 7-11 比喻,就好像我們要檢查某品牌飲料的數量時,State 就會是後台的紀錄,而這個 State 會對應到架上的數量。只是在 React 裡面,一但 state 發生變動、就會自動 call render()。
現在要開始學習怎麼幫 Component 加上 state,有兩種方法可以新增 state:
☞ 第一種:直接寫
直接寫在 Component 裡面,缺點是因為這是比較新的語法,所以需要安裝額外的 plugin @babel/plugin-proposal-class-properties 才支援,嫌麻煩的話可以用另一種寫法。
class App extends Component {
state = {
counter : 1
}
}
☞ 第二種:寫在 constructor 裡面
還記得物件導向的寫法吧,把 state 加在 constructor 裡面,且因為 App 是繼承自 Component,所以要記得加上 super() 去呼叫 Component 的 constructor,不然會報錯。
class App extends Component {
constructor() {
super(); // => 記得呼叫 parent 的 constructor,很重要
this.state = { // => 幫 App 加上 state
counter : 1
}
}
render() {
return (
<div>
<p>{this.state.counter}</p>
</div>
)
}
}
改變 State
要改變 Component 的狀態 state 必須要 call this.setState(),裡面傳一個 object,因為我們無法直接對 this.state.counter 做改變:
class App extends Component {
constructor() {
super();
this.state = {
title: '點我改變 state',
counter : 1
}
}
render() {
return (
<div>
<h1 onClick={() => {
// this.state.counter++; => 無法這麼做
this.setState({
counter: this.state.counter + 1 // => 更改 state 必須 call this.setState()
})
}}>{this.state.title}</h1>
// => 顯示目前 state
<p>{this.state.counter}</p>
</div>
)
}
}
而因為這樣的寫法會看起來有點混亂,所以通常會把改變 state 的程式抽出一個 function,在這邊寫成一個 handleClick:
class App extends Component {
constructor() {
super();
this.state = {
title: '點我改變 state',
counter : 1
}
}
// => 控制 App 的 state
handleClick() {
this.setState({
counter: this.state.counter + 1
})
}
render() {
return (
<div>
// => 當 click 再呼叫 this.handleClick
<h1 onClick={this.handleClick.bind(this)}>{this.state.title}</h1>
<p>{this.state.counter}</p>
</div>
)
}
}
- 這邊在呼叫
handleClick()
的時候,要加上一個.bind(this)
因為 this 的值是在呼叫 function 的當下決定的,在宣告的時候我們不能確定 this 的值是什麼,所以當我們執行按下 Title 執行 this.handleClick 的 function 時,就只是單純執行一個 function,而不是以 App 去呼叫 this.handleClick。而因為打包出來的 bundle.js 是在嚴格模式下 "use strict";,所以 this 當然就是 undefined。
所以才要利用 bind()
來把 this 指定回 App 這個 Component。
先使用 bind() 把 this 值決定好,無論怎麼呼叫 this.handleClick 這個 function,this 的值都會是 App 這個 Component 本身。
- 但如果使用的是 arrow function 情況會不同,因為 arrow function 中的 this 是在定義 function 的時候就決定好的,所以外層的 this 是什麼、arrow function 的 this 就是什麼
Props
相較於 state 比較像自己內部的狀態,還有一個屬性叫做 props,可以當作是外部傳進來的狀態。用 7-11 比喻,就好像我們要讓某牌飲料上架之前,要先以超商原本的配置來將飲料做歸類,假設原本超商有三台冰箱,放飲料的是 A、B 兩台,我想要把這款飲料放在 A 冰箱的第 3 層,我可能就在這牌飲料的「後台紀錄」先設定它之後要上架的位置是 A-3,這就是我在外部給它傳入的狀態。
回到 React 來舉例:假設我們有一個用來製作按鈕的 Component,我們希望在表單下方放上兩個按鈕,我們要用相同的 Component,但是一個按鈕上面寫的是「確定」一個按鈕上面寫的是「返回」。如此一來我們就要在放上這個 Component 的時候,從外部設定說我希望它顯示的文字為何。這個從外部設定狀態的動作,就是用 props
上面程式碼寫的 Counter,我們把它抽出來獨立成一個 Component,那這樣要怎麼傳入改變後的狀態給 Counter 這個 Component 呢?
class Counter extends Component {
render() {
// => 拿到剛剛傳進來的 number 屬性值
return <div>{this.props.number}</div>;
}
}
class App extends Component {
constructor() {
super();
this.state = {
title: '點我改變 state',
counter : 1
}
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({
counter: this.state.counter + 1
})
}
render() {
return (
<div>
<h1 onClick={this.handleClick}>{this.state.title}</h1>
// => 用 number 當成一個屬性傳進去 Counter 這個 Component
<Counter number={this.state.counter}/>
</div>
)
}
}
運作流程就是:
- 原來的 App 的 state.counter => 1
- 畫面輸出 1
- 點了一下 Title 觸發 this.handleClick,改變了 App 的 state
- state.counter => 2
- 然而 App 的 state 只要一改變,就會重新觸發 App 的 render()
- 畫面輸出 2
記得 React 的好處:資料改變了,畫面會自動改變
props.children
相較於剛剛指定一個屬性名稱,其實還有另一種方式傳遞 props.children
,好處是可以傳任意元素進去( 字串、HTML 元素、Component… )。
class Title extends Component {
render() {
console.log(this.props.children); // => 輸出會看到兩個 props 屬性:(2) [{…}, "I'm Title"]
return (
this.props.children
)
}
}
class App extends Component {
render() {
return (
<div>
<Title>
<h1>I'm h1 title</h1> // => 夾在標籤底下的都是 props.children 的內容
{`I'm Title`}
</Title>
</div>
)
}
}
小記
這篇文章講了兩個重點 State & Props,一個是在「內部」設定狀態,一個由「外部」決定狀態,兩個狀態之間是不會相互擾的。
就像飲料要上架之前,先從外部設定狀態為(它上架的位置在 A-3,價格設定為 30 元/瓶),上架之後可以改變內部狀態(架子上面有 10 瓶,庫存有 300 瓶)
在 React 裡面,只要 state 一改變,就會更新 UI ,也就是觸發 render()