(2024/04/06更新) 因應React在18後更新了許多不同的語法,更新後的教學之後將陸續放在 新的blog 中,歡迎讀者到該處閱讀,我依然會回覆這邊的提問
在前一章,我們發現當變數name被改變時,我們的按鍵並沒有被更新。這是因為React 的 virtual DOM 只有在兩個東西被改變時才會更新,第一個是先前提過的props,第二個則是state。
state是class component的預設好會去檢查的一個特殊的member data。就如同上述所言,當state被改變時,會進入re-render的update程序,更新畫面。
接下來我們會用state,來設定並改變以下這個進度條的寬度:
import React, { Component } from 'react';
class App extends Component{
constructor(props) {
super(props);
}
render(){
return(
<div>
<div className="progress-back" style={{backgroundColor:"rgba(0,0,0,0.2)",width:"200px",height:"7px",borderRadius:"10px"}}>
<div className="progress-bar" style={{backgroundColor:"#fe5196",width:"10%",height:"100%",borderRadius:"10px"}}></div>
</div>
</div>
);
}
}
export default App;
state的結構也是一個物件,建立的方法很簡單,在建構子裡宣告即可:
constructor(props) {
super(props);
this.state={ 變數名稱A: 初始化值, 變數名稱B: 初始化值, (...類推)};
}
/* 排版習慣為 */
constructor(props) {
super(props);
this.state={
變數名稱A: 初始化值,
變數名稱B: 初始化值,
(...類推)
};
}
讀取的方式和props一樣,也就是:
this.state.變數名稱
例如在下面的程式碼(App.js)中,我們用percent
這個state去控制progress-bar中style的width:
import React, { Component } from 'react';
class App extends Component{
constructor(props) {
super(props);
this.state={ // 宣告state物件,內包含一個變數percent
percent:"30%"
}
}
render(){
return(
<div>
<div className="progress-back" style={{backgroundColor:"rgba(0,0,0,0.2)",width:"200px",height:"7px",borderRadius:"10px"}}>
<div className="progress-bar" style={{backgroundColor:"#fe5196",width:this.state.percent,height:"100%",borderRadius:"10px"}}></div>
</div>
</div>
);
}
}
export default App;
執行結果:
state這個變數是read-only的,我們並不能用this.state.變數=值
直接修改state,而是必須要透過React預寫好的函式setState()
來更改。(註:跟使用其他函式一樣,前面要加上this)
例如:
changePercent(){
this.setState({ percent:"70%" })
}
state是一個物件,所以丟入setstate()
中的也必須是一個物件(也就是範例中的{ percent:"70%" }
)。
接續上面的範例,我們用changePercent去修改剛剛progress-bar的寬度
import React, { Component } from 'react';
class App extends Component{
constructor(props) {
super(props);
this.state={
percent:"30%"
}
this.changePercent=this.changePercent.bind(this); //綁定changePercent
}
changePercent(){ //加入changePercent函式
this.setState({percent:"70%"})
}
render(){
return(
<div>
<div className="progress-back" style={{backgroundColor:"rgba(0,0,0,0.2)",width:"200px",height:"7px",borderRadius:"10px"}}>
<div className="progress-bar" style={{backgroundColor:"#fe5196",width:this.state.percent,height:"100%",borderRadius:"10px"}}></div>
</div>
</div>
);
}
}
export default App;
然後加入一個按鍵去觸發changePercent
:
<button onClick={this.changePercent}>轉換百分比 </button>
所有的程式碼:
import React, { Component } from 'react';
class App extends Component{
constructor(props) {
super(props);
this.state={
percent:"30%"
}
this.changePercent=this.changePercent.bind(this); //綁定changePercent
}
changePercent(){ //加入changePercent函式
this.setState({percent:"70%"})
}
render(){
return(
<div>
<div className="progress-back" style={{backgroundColor:"rgba(0,0,0,0.2)",width:"200px",height:"7px",borderRadius:"10px"}}>
<div className="progress-bar" style={{backgroundColor:"#fe5196",width:this.state.percent,height:"100%",borderRadius:"10px"}}></div>
</div>
<button onClick={this.changePercent}>轉換百分比 </button>
</div>
);
}
}
export default App;
執行之後,當按下按鍵,粉紅色的部分就會變成寬占滿70%:
(我不是gif,所以不會動)
假設目前state為
this.state={
percent: 20,
mounted: false
}
當我們使用this.setState({percent: 70})
時,mounted並不會從state中被移除。
比較需要說明的是「不存在的state會被建立」,這句話的意思是我們不一定要在constructor中建立state/所有state。當setState被呼叫時,如果它發現有member data不屬於目前的state,就會自動建立它;如果在constructor連state的宣告都沒寫,就會自動建立state。
例如在這個狀態下:
this.state={
percent: 20,
mounted: false
}
以下的函式就代表在改變percent為40的同時,創造一個叫做counter的state
this.setState({percent:40,counter:0});
新的state:
this.state={
percent: 40,
mounted: false,
counter:0
}
前面提過setState中「存在但沒有被寫到的state」不會被移除,那移除其中一個state要怎麼做呢?答案很簡單,把該state指定為undefined
就可以:
this.setState({mounted: undefined});
這樣mounted就會不存在於我們的state。
this.setState({變數名稱})
當寫成this.setState({變數名稱})
時,setState會自動去找state中有沒有和該變數同名字的member data。如果有,就會把它設定為變數值。如果沒有,就會自動在state中建立同名字的member data。
let counter=5;
this.setState({counter});
/* 如果目前的state有counter,把它指定為5。如果沒有,則創造一個叫counter的state */
要特別注意的是,如果在state裡面宣告物件,修改時並不能夠單獨修改物件的單一屬性。
例如:
this.state={
styleData:{
width: "30%",
height: "50%"
}
}
如果我們使用
this.setState({ styleData:{width:"70%"} });
state裡面的styleData並不會保留height屬性,而是直接變成只有width:"70%"
的物件。如果想要只更改state內的物件的單一屬性並保留其他屬性,可以這樣寫:
this.setState({
styleData:{
width: "70%",
height: this.state.styleData.height
}
});
setState
通常需要花一點細微的時間,雖然不會察覺,但是由於這點加上js的非同步特性,在setState
後面用到state的函式常常會拿到改變前的state值。
這個時候可以搭配setState
預設的第二個參數。(感謝 神Q超人 大大的留言補充,自己寫這系列的同時也在參考他的在 React 生態圈內打滾的一年 feat. TypeScript )
setState
的第二個參數可有可無,它是個function,當state被設定完之後,就會執行。我們可以利用這個參數來作想在state改變後的事情:
this.state={
percent: 20
}
this.setState({percent: 70},()=>{
console.log(this.state.percent);// 這樣會印出70
}
)
回頭去查了一下原始官方文件,setState的第一個參數也是一個函式,只是因為當我們給予的是物件而不是函式時,會自動讓物件淺層合併至新的 state 中,所以我們才能直接寫this.setState(物件)
。
完整的setState應該長這樣:
this.setState((state, props) => {
/* 第一個參數函式 */
return {新的state};
},()=>{
/* 第二個參數函式,state改變後執行 */
});
終於把前面一直提過的state講完了,另外雖然這裡提了相當多有關setState
的語法,不過使用時建議還是統一以免造成同事困擾XD
下一篇會來講現在火紅的React hook中的useState。
setState()
可以有兩個參數ㄛ,如果要取得改變後的值可以在第二個 Function 中作 callback
:
this.setState({ name: 'hi' },
() => {
// 這裡可以取得改變後的 state
console.log(this.state);
}
);
這樣子就不用使用 Promise 了~
啊啊好,自己不知道這個用法
等等會趕快補上,感謝大大指點 orz