iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 10
1
Modern Web

Zero to hero with React.js系列 第 10

【Day 10 React】透過番茄計時器實作理解 React State and Lifecycle

之前我修了一堂 University of California, San Diego 開的線上課程 Learning How to Learn: Powerful mental tools to help you master tough subjects。這門課程有講解到 番茄鐘工作法,分析大腦的運作以及講解人類專注力的概念,教導我們如何正確地運用時間,達到最佳的工作效率。

「番茄鐘工作法」意指 25 分鐘內,只專注在目前正在進行的事項,並用 5 分鐘時間專心休息,並再次以 25 分鐘為單位,專注於一件事情的方法。

前言很長XDD
所以!!!!今天我要用 React 實作番茄計時器 (Pomodoro Timer)。以後我就可以用自己做的番茄鐘來規劃自己的時間運用(?)

今日文章內容:

  1. 建立 Pomodoro Timer App
  2. 建立 Pomodoro 組件
  3. 狀態 (State) 的敘述
  4. 介紹組件的生命週期
  5. 製作提醒的機制

透過撰寫 Pomodoro Timer App 我們可以學到以下幾項 ReactJS 的概念:

  1. State
  2. Lifecycle Methods
  3. setState
  4. clearInterval
  5. bind
  6. componentDidMount (Lifecycle Methods之一)
  7. componentWillUnmount (Lifecycle Methods之一)
  8. webpack

好~馬上進入今天的主題拉!
在建置新的專案之後,一樣要依循 【Day8 React】從拆解電子名片學習 React Components #Part2裡的步驟

$ npm init
$ npm install

install 完,我們就安裝好 babel 的運作環境,現在可以告訴 babel,請它監看我們的行為:

$ npm run babel

應該可以看到

> babel app --watch -d build
app/index.js -> build/index.js

表示 babel 有在監看我們應用程式的一舉一動。


在 Pomodoro 計時器裡,有一個 input 和一個 output

  1. input 指的是點擊開始的按鈕,時間開始計算
  2. output 是時間一到 25 分鐘,會跳出提醒休息的訊息

我在 app/index.js 裡先建立 PomodoroTimer component

class PomodoroTimer extends React.Component{
    render(){
      return(
        <div>This timer runs for 25 minutes,
        followed by rest of 5 minutes.
        <br />
        For a total time of 30 minutes.
        <br />
        There are 88 seconds elapsed.</div>
      )
    }

}

ReactDOM.render(
    <PomodoroTimer />,
    document.getElementById('app')

);

我可以在 <PomodoroTimer /> 設定兩個 props 分別為 workingTime 以及 restingTime

<PomodoroTimer workingTime={25} restingTime={5}/>

接著在 PomodoroTimer child component 裡面,透過 this.props 取得我們要的內容

This timer runs for {this.props.workingTime} minutes,
followed by rest of {this.props.restingTime} minutes.

透過 props 我們可以很彈性地更改內容,假設我今天想縮短每個工作區間,改成工作 20 分鐘,休息 10 分鐘,我可以直接在最後渲染 DOM 的 <PomodoroTimer /> 改值。

<PomodoroTimer workingTime={20} restingTime={10}/>

props 的計算

在取得工作時間與休息時間後,我想要把這兩個數字相加,放在 total time 裡面。
最簡單的做法是直接把兩個 props 相加,在 JSX 語法中的大括號({})中可以進行運算

For a total time of {this.props.workingTime + this.props.restingTime} minutes.

但為求更模組化的 coding style,我將另外寫一個總計時間的 totalTime function,所以我們可以利用

For a total time of {this.totalTime(this.props.workingTime, his.props.restingTime)} minutes.

從 totalTime() 裡面取得時間的加總,那麼在這個 function 我們就要進行兩個數字的運算

  totalTime(timeOne, timeTwo){
      return timeOne + timeTwo;
  }
  

目前頁面長這樣,前三個數字都是 render 出來的數值


State and Lifecycle

認識 State and Lifecycle 的原理是我們學習 React 的重點之一。
props 是我們傳進 component 的資訊,它是不會改變的;而 state 則是來自 componet,是隨著時間不段變動的

我們可以 props 想成是出生地點,在彰化 : )
這個出生地點一旦定下來之後就不會改變
而 state 就像是我的年齡,它是隨著時間而變動的。

我們先建立一個 constructor(),constructor 會透過 super(props) 呼叫父類別的 constructor,我們要把初始化的 state 定義在這個 constructor 裡。像我要設定時間說 0 秒開始計算可以把 {timeElapsed = 0} 指定到 this.state 上。

constructor(){
  super();
  this.state = {
      timeElapsed: 0
  };
}

而在要渲染的部分寫上

There are {this.state.timeElapsed} seconds elapsed.

現在我們可以看到 time elapsed 變成我們設定的 0


Lifecycle

計時器就是在頁面上所有東西都載入完畢的那一刻,開始計算時間,我們該如何製作這個功能?我們的程式如何知道所有資訊都載入完成了呢?

當程式一開始先進到 constructor,接著去 totalTime() 裡面計算時間總數,最後進到 render() 裡面,把要呈現的內容渲染到 DOM 裡面。這個過程超級爆炸快速,當你重新載入頁面,是不是也差不多直接看到 DOM 的結果。
我們現在將這整個程式經過的流程放慢一萬倍,監看程式渲染 DOM 的過程,我們可以看到其實是有一套 lifecycle 在運行的

官方文件:

Mounting

These methods are called when an instance of a component is being created and inserted into the DOM:

  • constructor( )
  • componentWillMount( )
  • render( )
  • componentDidMount( ) : 這就像是告知要渲染到 DOM 的內容都已經準備好,就像是 Jquery 的 document.ready

我們現在就是要用 componentDidMount( ) 確認東西都準備齊全了,那程式就會開始計時。

Updating

An update can be caused by changes to props or state. These methods are called when a component is being re-rendered:

  • componentWillReceiveProps( )
  • shouldComponentUpdate( )
  • componentWillUpdate( )
  • render( )
  • componentDidUpdate( )

Unmounting

This method is called when a component is being removed from the DOM:

  • componentWillUnmount( )

unmounting 表示再次確認,需要 render 的素材都準備齊全,所以在我們應用程式的最後需要用到這個 function

大致看過文件之後,我還是不太懂某些 function 的用法,不如就直接用範例來了解吧!
我們先新增兩個 function

componentDidMount() {
    //確認素材都準備完畢!
    this.setInterval = setInterval(this.elapseTime.bind(this),1000)
    this.setState({start: new Date()});
    //一旦 mount 完畢之後,我們就要設定現在的時間
}

elapseTime(){
   // 確認完畢觸發「開始計時」
   var currentTime = new Date();
   console.log("CURRENT" + currentTime);
   console.log(this.state.start);
}

透過 this.elapseTime.bind(this) 去呼叫並執行 elapseTime() 這個 function

現在我先用 console.log 看一下我得到的開始時間與現在時間,可以看到 DOM 每秒就更新一次,因為我剛剛 setInterval(1000),一千毫秒就是一秒。currentTime 隨著秒數而更新;開始時間則是頁面載入的時間,現在我們取得這兩個數值之後,將它們相減就可以得到已經經過的秒數了。

接著來計算 elapseTime

var timeElapsed = (currentTime - this.state.start) / 1000;
console.log("timeElapsed" + timeElapsed);

我們來看看 console.log 的結果,發現會有幾毫秒的 lag,這些延遲可能會在呼叫 API 或是計算屬性等等之間發生,如果要真的計算到很精細,需要再做很複雜的計算。這邊我就簡化使用 Math.floor 來取得最大的整數,畢竟我需要的只有秒數,基本上這已經趨近 99.99% 的精確度了XD(就讓我們忽略那幾毫秒的誤差吧⋯)

var timeElapsed = Math.floor((currentTime - this.state.start) / 1000);

OK,現在我取得到秒數了!
接下來,我們要把剛剛在 constructor 裡面寫死的 this.state = {timeElapsed: 0} 讓它隨著時間更新

elapseTime() 裡面更新 state

this.setState({timeElapsed: timeElapsed});

時間到的 alert situation

if(this.state.timeElapsed >= this.props.workingTime * 60){
    clearInterval(this.interval);
    alert("Time up for a break!");
}

if 去判斷 timeElapsed 是否超過 25 分鐘,而 timeElapsed 的單位是秒數,所以要記得把 25 分鐘 * 60,轉換成秒數。
在判斷式裡面,也要加上

clearInterval(this.interval);

讓每一次頁面刷新,原先的記錄也要清除。

完成了一個很陽春的番茄計時器!!
今天做的專案可以看這邊: Github


上一篇
【Day9 React】從拆解電子名片學習 React Props #Part3
下一篇
【Day 11 React】Events and Data Changes in React
系列文
Zero to hero with React.js30

尚未有邦友留言

立即登入留言