iT邦幫忙

2022 iThome 鐵人賽

DAY 8
0

今天要進入到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
這個做法就叫做向下資料流

https://ithelp.ithome.com.tw/upload/images/20220912/20145703wXYEQelDBU.png

官方文件寫的這句話會讓人更疑惑不知道他在講甚麼,所以我建議直接到CodePen看看立刻就能懂囉!

今天的資料量其實很龐大,看文件、找資料 + 吸收轉化 也是花了很多時間。
如果我有理解錯的地方,大神可以在底下留言來讓我知道

明天 開始 會把官方文件的 主要概念 6 ~ 12 開始精簡化XD


上一篇
Day 7 React Element、Component and Prop
下一篇
Day 9 關於React的其他特性 -1
系列文
跳脫MVC,Laravel + React 建立電商網站30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言