Redux教程3:新增倒數計時

玄農發表於2016-01-07

前面的教程裡面,我們搭建了一個簡單紅綠燈示例,通過在console輸出當面的倒數計時時間;由於介面上不能顯示倒數計時,使用者體驗並不良好,本節我們就新增一個簡單的倒數計時改善一下。

作為本系列的最後一篇文章,將示例如何處理多個Redux、React的情形;

1、建立Counter類

我們定義倒數計時的類名為 Counter ,建立所需要的檔案(夾):

mkdir actions/counter reducers/counter stores/counter components/counter views/counter

touch constants/Counter.js actions/counter/index.js reducers/counter/index.js stores/counter/index.js components/counter/index.js components/counter/redux.js components/counter/index.less  components/counter/demo.js views/counter/index.hbs

建立 Counter 的 Redux 和 React 元件的過程就相當於重複了一下之前兩篇文章的過程,程式碼也不復雜,我這邊也就不貼上了。可自行參考程式碼,程式碼託管在 https://github.com/boycgit/demos/tree/master/traffic;

可以通過 http://localhost:3000/counter/redux 檢驗是否正常執行;

gif
(這個是gif圖,如果沒動畫請點選在新視窗開啟)

在假設使用者已經編寫上面的程式碼檔案的基礎上,我們繼續講解如何將 Counter 和 Light 兩個元件聯合起來。

2、建立入口檔案

Redux的三個原則之一 : 單一store,單一reducer。我們建立兩個檔案,分別整合之前所寫的 reducer 和 store 。

2.1、reducer入口檔案

建立 reducers/traffic.js檔案,作為 主reducer 入口檔案:

import { combineReducers } from `redux`
import light from `./light/`
import count from `./counter/`

const rootReducer = combineReducers({
    light,
    count
});

export default rootReducer

這裡包含了最佳實踐法則, 將不同的狀態轉移關係寫進不同的js檔案,最後彙總到 index.js 中(這裡名為traffic.js,地位是一樣的) ,比如後期如果多出一種 “汽車的狀態轉移” 關係,只要新建對應的js檔案,然後再在index.js中的combineReducers 函式中多新增一行配置即可;

詳細的概念及作用請參考Redux的中文文件 Reducer

2.2、store入口檔案

建立 stores/traffic.js檔案,作為 主store 入口檔案:

import { createStore } from `redux`
import rootReducer from `../reducers/traffic`

export default function trafficStore(initState){
    return createStore(rootReducer,initState);
}

可以看到並沒有什麼工作量,只是多了幾行程式碼而已;

3、建立應用

前面建立的 Counter 和 Light 算是元件,將兩者結合起來,可以視作一款小應用了(假設應用名為traffic);

為了方便管理,專門建立 App 資料夾來存放應用,並建立應用相關的等輔助內容(比如檢視等):

mkdir app app/traffic views/app

touch app/traffic/index.js app/traffic/index.less views/app/index.hbs views/app/traffic.hbs

核心是 app/traffic/index.js 檔案,其餘檔案只是其輔助作用,這邊也不重點講解,可自行到git clone後檢視;

3.1、初始化

在 app/traffic/index.js 中引入 Counter 和 Light 元件並設定初始化值:

import React, {Component, PropTypes} from `react`
import {render} from `react-dom`
import { Provider, connect } from `react-redux`
import { bindActionCreators } from `redux`
import * as LightActions from `../../actions/light/`
import * as CounterActions from `../../actions/counter/`
import Light from `../../components/light/`
import Counter from `../../components/counter/`
import trafficStore from `../../stores/traffic`

// 初始化狀態
let initLight = {
    light:{
        color:`green`,
        time:`5`
    }
}
let initCount = {
    count:{
        num : parseInt(initLight.light.time)
    }
}
let initState = Object.assign({},initLight,initCount);

// 宣告store
let store = trafficStore(initState);
  • 初始化的時候,我們從綠燈開始;
  • 倒數計時的時間來自於 initLight.light.time ,這樣在初始化狀態的時候關聯起來兩個元件
  • 將兩個元件的狀態(initLight,initCount)合併成 initState ,傳給應用的 store,以完成 應用store的初始化

3.2、建立React元件,並連結到Redux

緊接著,使用 connect 方法連結 Redux 和 React元件:

...

class App extends Component{
    // 佔位
}

// 宣告 connect 連線
// 將 redux 中的 state傳給 App
function mapStateToProps(state){
    return{
        light:state.light,
        count:state.count
    }
}

// 繫結多個actions
function mapDispatchToProps(dispatch){
    let boundLight = bindActionCreators(LightActions,dispatch);
    let boundCount = bindActionCreators(CounterActions,dispatch);
    return{
        actions : Object.assign({},boundLight,boundCount)
    }
}

// 宣告 connect 連線
App = connect(mapStateToProps,mapDispatchToProps)(App);

// 真正的連線
render(
    <Provider store={store}>
        <App />
    </Provider>,
    document.getElementById(`demo`)
)

形式和上篇提到的類似,細節略微有些不同:

  • mapStateToProps中返回的物件有兩個屬性 light 和 count,在React元件中 對應 this.props.light 、 this.props.count
  • mapDispatchToProps 中現將兩個元件的方法先和dispatch繫結,合成一個物件之後再賦值,這樣在React元件中使用 this.props.actions 可以呼叫這兩個元件的所有的actions創造函式;
  • 最後使用 <Provider> 注入 store 例項;

3.3、完善App元件內容

最後,繫結store之後完善 App類 的程式碼,大部分的邏輯和前一篇的類似:


...

class App extends Component{
    _bind(...methods){
        methods.forEach((method)=>this[method] = this[method].bind(this));
    }
    constructor(){
        super();
        this._bind(`changeColor`,`handleClick`,`autoChange`);
        this.state = {
            timeId : null
        }
    }
    changeColor(light,actions){ // 紅路燈變換規則
        switch(light.color){
            case `red`:
                actions.changeGreen();
                break;
            case `green`:
                actions.changeYellow();
                break;
            case `yellow`:
                actions.changeRed();
                break;
            default:
                actions.changeRed();
        }       
    }
    autoChange(){ // 自動更改紅綠燈
        const { light,count, actions } = this.props;

        let _self = this;

        actions.countDown();

        let curState = store.getState();
        if(curState.count.num < 1){
            this.changeColor(light,actions);
            curState = store.getState();
            actions.countInit(parseInt(curState.light.time));
        }
        // 自動更改
        this.state.timeId = setTimeout(function(){
            _self.autoChange();
        },1000);

    }
    handleClick(e){  // 用點選模擬紅路燈
        if(this.state.timeId){
            clearTimeout(this.state.timeId);
            this.state.timeId = null;
        } else {
            this.autoChange();
        }

    }
    render(){
        // 通過connect 注入 redux 的 dispatch 方法
        const { light,count, actions } = this.props;

        return (
            <div id="traffic" onClick={this.handleClick}>
                <Light light={light}/>
                <Counter num={count.num}/>
            </div>
        )
    }
}

// 宣告 connect 連線
...

變換的邏輯都在 autoChange 方法中

  • 使用 actions.countDown(); 讓倒數計時減1,通過 store.getState();獲取更新後的狀態,因為如果直接使用count.num 獲取的是 更新之前 的狀態;
  • 當 curState.count.num 小於 0 的時候,呼叫 this.changeColor(light,actions); 更改紅綠等的顏色,同時將 新的紅綠燈的time值初始化 Counter 元件,這樣就完成了兩者的繫結

3.4、預覽效果

在 http://localhost:3000/app/traffic 中檢視效果,效果正如此係列文章第一篇開頭所展示的那樣,紅綠燈搭配倒數計時執行:

result
(這個是gif圖,如果沒動畫請點選在新視窗開啟)

紅綠燈初始狀態是 綠燈5s,繼而迴圈 黃燈3s -> 紅燈7s -> 綠燈5s -> 黃燈3s -> …

就這樣, Counter 和 Light 融洽地結合起來,完美,happy ending~

4、總結

到這裡,Redux 的入門教程算是完結;整個過程下來,你可以體會得到,React只需要關注逐漸的展示就行了,所有狀態的管理交由 redux 即可,這種繫結恰好體現了 容器元件和展示元件相分離 的開發思想: 只在最頂層元件(如路由操作)裡使用 Redux;內部元件應該像木偶一樣保持“呆滯”,所有資料都通過 props 傳入 。

這裡需要再強調一下:Redux 和 React 之間沒有關係。Redux 支援 React、Angular、Ember、jQuery 甚至純 JavaScript。

此係列文章僅僅是個開始,後續Redux還有許多高階內容需要學習,比如非同步資料流等處理。少年,加油吧~


相關文章