之前我修了一堂 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)。以後我就可以用自己做的番茄鐘來規劃自己的時間運用(?)
今日文章內容:
透過撰寫 Pomodoro Timer App 我們可以學到以下幾項 ReactJS 的概念:
好~馬上進入今天的主題拉!
在建置新的專案之後,一樣要依循 【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
我在 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}/>
在取得工作時間與休息時間後,我想要把這兩個數字相加,放在 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 的原理是我們學習 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
計時器就是在頁面上所有東西都載入完畢的那一刻,開始計算時間,我們該如何製作這個功能?我們的程式如何知道所有資訊都載入完成了呢?
當程式一開始先進到 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:
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:
Unmounting
This method is called when a component is being removed from the DOM:
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});
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