1. 程式人生 > >例項講解基於 React+Redux 的前端開發流程

例項講解基於 React+Redux 的前端開發流程

前言:在當下的前端界,react 和 redux 發展得如火如荼,react 在 github 的 star 數達 42000 +,超過了 jquery 的 39000+,也即將超過前幾年比較火的angular 1 的 49000+;redux 的 star 數也要接近 20000,可見大家對其的熱情程度,究竟是什麼魔力讓大家為之瘋狂呢?讓我們上車,親自體驗一波試試~~本文章偏向於講解redux流程。

宅印前端基於 react + redux 的模式開發,我們指定了一套分工明確的並行開發流程。下面通過一個 “蘋果籃子” 例項,來看看整個應用開發流程。

首先,我們來看看這個例項的原型:

看到這個水果籃子的樣子,大家應該可以明白它的功能:你可以做兩件事 -- 摘蘋果和吃蘋果。當你摘蘋果時,應用會向後臺傳送ajax請求索取蘋果,每個蘋果有兩個屬性:編號和重量。當你吃蘋果掉時,不用告訴後臺,在前端偷偷吃掉就好啦~ 同時蘋果籃子會顯示當前的蘋果量和已經吃掉的蘋果量。好!那下面我們來開工!

下面先來總體瞭解一下 redux 應用的基本原理,一圖勝千言:

可見整個redux流程的邏輯非常清晰,資料流是單向迴圈的,就像一個生產的流水線:

store(存放狀態) -> container(顯示狀態) -> reducer (處理動作)-> store

這一點對精細化分工協作很有好處。

我們來看看這三個概念:

  • store是應用的狀態管理中心,儲存著是應用的狀態(state),當收到狀態的更新時,會觸發視覺元件進行更新。

  • container是視覺元件的容器,負責把傳入的狀態變數渲染成視覺元件,在瀏覽器顯示出來。

  • reducer是動作(action)的處理中心, 負責處理各種動作併產生新的狀態(state),返回給store。

NOTE:從物件的包含關係上講,reducer 是store的一部分,但在邏輯上我們把它分出來,這樣會比較容易理解整個redux流程。

我們可以做個形象的比喻,把 js 比喻成巴士,把 store, container, reducer 比喻為三個車站,再把 state 和 action 比喻成兩種乘客。這是一趟環路巴士:

  1. js巴士 從 store車站 出發,載上 state乘客 , state乘客 到達某個 container車站 下車並把自己展示出來

  2. 過了一會,有一個 action乘客 上車了, js巴士 把 action乘客 送到 reducer車站 ,在這裡 action乘客 和 state乘客 生了一個孩子 new state ,js巴士把 new state 送回了 store車站(好像是人生輪迴→_→)

redux 只是定義了應用的資料流程,只解決了 “資料層”(model layer) 的問題,一般還會使用 react, angular 等作為“顯示層” (UI layer) 來一起使用,我們專案採用 react 作為顯示框架。

在開始之前,這裡先提供一些介紹react和redux的參考資料,如果在下文遇到哪些點不理解,可以來這裡翻看參考資料:

下文的展示的js程式碼,會用到少量簡單的 es6 語法,可以在遇到時參考這裡,或自己查詢資料:

我們會使用 babel 編譯器把es6語法編譯成es5, 所以大家不必擔心瀏覽器相容性問題,可以大膽使用es6。

應用的基礎配置工作由前端開發主管負責,大家不必詳細理解。

按任務分工來分步講解

按照開發的內容,我們把前端團隊分為兩個小組: “佈局組” 和 “邏輯組”,每個小組有2種任務,一共4種任務。

  • 佈局組- 負責 contianer、component 部分

    • 任務1: 靜態佈局 - 使用 HTML + CSS 靜態佈局

    • 任務2: 動態佈局 - 使用 JSX 語法對靜態佈局做動態渲染處理

  • 邏輯組- 負責 action、reducer 部分

    • 任務1: action 開發 - 製作 redux 流程的 action

    • 任務2: reducer 開發 - 製作 redux 流程的 reducer

佈局組要求對 HTML + CSS 佈局比較熟悉,只需要會簡單的 js 即可, 不需要完整地理解redux流程。

邏輯組要求對 js 比較熟悉,最好可以比較完整地理解redux流程, 但基本不需要涉及HTML + CSS佈局工作。

接下來,先給出我們教程相關的 src 目錄。這裡大家可以先一掃而過,大概瞭解即可

  • src 原始碼資料夾 : 包含專案原始碼,我們基本都在這個資料夾下做開發

    • containers 容器資料夾 :存放容器元件,比如 “蘋果籃子”

    • components 元件資料夾 :存放普通顯示元件,比如 “蘋果”

    • actions actions資料夾 :存放可以發出的action

    • reducers reducers資料夾 :存放action的處理器reducers

    • services 服務資料夾 :存放經過封裝的服務,如 ajax服務 , 模擬資料服務

    • styles 樣式資料夾 :存放應用的樣式,如css, scss

    • images 圖片資料夾 : 存放圖片資源

    • apis 開發介面資料夾 : 存放開發介面文件

下面正式開始啦,先從佈局組開始。

佈局組

任務1:靜態佈局

  • 能力要求:只需要會使用 HTML + CSS (SASS)進行佈局即可

  • 任務內容:1. 蘋果籃子元件(容器元件) 2. 水果元件(顯示元件)

redux 的元件分為兩類,一類是 容器元件 container ,一類是 普通顯示元件 component 。容器負責接收store中的state和併發送action, 大多數時候需要和store直接連線,容器一般不需要多次使用,比如我們這個應用的蘋果籃子。普通元件放在容器裡面,由容器確定顯示的時機、數量和內容,普通元件一般會多次使用。

1. 蘋果籃子容器 AppleBasket.jsx + appleBasket.scss

蘋果籃子元件的原型如下:

對於容器,我們使用 React 元件類 的方式書寫,這裡主要是靜態的jsx程式碼,相當於html。

下面是 AppleBasket.jsx

import React from 'react';
import { connect } from 'react-redux';

class AppleBusket extends React.Component {
  
  render(){
    return (
      <div className="appleBusket">
        <div className="title">蘋果籃子</div>
        
        <div className="stats">
            <div className="section">
                <div className="head">當前</div>
                <div className="content">0個蘋果,0克</div>
            </div>
            <div className="section">
                <div className="head">已吃掉</div>
                <div className="content">2個蘋果,480克</div>
            </div>            
        </div>
                    
        <div className="appleList">
            <div className="empty-tip">蘋果籃子空空如也</div>
        </div>
        
        <div className="btn-div">
            <button>摘蘋果</button>
        </div>
        
      </div>
    );
  }

}

export default connect()(AppleBusket);

同時靜態佈局開發人員還要負責書寫樣式,宅印的樣式使用sass 書寫, 下面是的例子是 appleBasket.scss , 大家可以做參考:

.appleBusket{
    width: 400px;
    margin: 20px;
    border-radius: 4px;
    border: 1px solid #ddd;
    >.title{
        padding: 6px 0px;
        text-align: center;
        color: #069;
        font-size: 20px;
        //font-weight: 600;
    }
    >.stats{
        width: 100%;
        $border: 1px dashed #ddd;
        border-top: $border;
        border-bottom: $border;
        padding: 10px 0px;
        .section{
            display: inline-block;
            width: 50%;
            padding-left: 8px;
            .head{
                padding: 6px 0px;
                font-size: 16px;
                color: #069;
            }
            .content{
                font-size: 20px;
                font-weight: 200;
            }
            &:first-of-type{
                border-right: 1px solid #f0f0f0;
            }
        }
    }
    >.appleList{
        padding: 10px 0px;
        .empty-tip{
            text-align: center;
            font-size: 16px;
            color: #ccc;
            padding: 20px 0px;
        }
    }
    >.btn-div{
        text-align: center;
        button{
            color: #fff;
            background-color: #096;
            border: none;
            font-size: 14px;
            padding: 6px 40px; 
            border-radius: 6px;
            margin: 20px auto;
        }
    }
}

2. 蘋果元件 AppleItem.jsx + appleItem.scss

蘋果元件的樣子如下:

普通元件的定義方法和容器類似,只是其不需要使用redux聯結器來封裝,如下:

AppleItem.jsx

import React from 'react';

class AppleItem extends React.Component {

    render() {

        return (
            <div className="appleItem">
                <div className="apple"><img src="../images/apple.png" alt=""/></div>
                <div className="info">
                    <div className="name">紅蘋果 - 1號</div>
                    <div className="weight">265克</div>
                </div>
                <div className="btn-div"><button>吃掉</button></div>
            </div>
        );

    }


}

export default AppleItem;

樣式檔案 appleItem.scss 在此省略。

哪些顯示元素要作為容器,哪些要作為普通元件,沒有百分之百確定劃分規則,大家根據自己的理解把它劃分到某一類即可。

這些就是任務一的內容,書寫靜態佈局,基本都是html+css工作,不需要涉及js程式碼。

任務2:動態渲染

寫完了靜態佈局後,接下來要進行動態渲染啦~

動態渲染聽起來很高大上,其實意思就是根據一個stete資料物件來顯示內容而已。首先需要確定其state的結構。 容器 的state 是 store 中state的一部分,前端管理員會事先約定好其資料結構,並在對應的reducer中給出,只要去那裡複製一份出來即可。 普通元件 的state只要自己定義即可,並在檔案中說明清楚。

1. 容器的動態渲染

下面看看“蘋果籃子”的動態佈局,我們去 appleBasketReducer.js 可以得到水果籃子的 state 的結構如下:

{
    isPicking : false,
    newAppleId: 1,
    apples: [
        /*{
            id: 0,
            weight: 235,
            isEaten: false
         }*/
    ]
}

我們這個資料結構把它 “例項化”,如下這樣放在我們改成寫的 AppleBasket.jsx 中,然後我們開始書寫我們的動態渲染程式碼啦,如下:

import React from 'react';
import { connect } from 'react-redux';

import AppleItem from '../components/AppleItem';

class AppleBusket extends React.Component {

    render() {
        
        let { state } = this.props;
        
        //這部分從對應的 appleBasketReducer.js 中拷貝
        let mockState = {
            isPicking : false,
            newAppleId: 3,
            apples: [
                {
                    id: 1,
                    weight: 235,
                    isEaten: true
                },
                {
                    id: 2,
                    weight: 256,
                    isEaten: false
                }
            ]
        };
        
        //是否開啟模擬資料的開關,註釋這行程式碼關閉模擬資料
        state = mockState;
        
        
        //對 state 做顯示級別的轉化
        let stats = {
            appleNow: {
                quantity: 0,
                weight: 0
            },
            appleEaten: {
                quantity: 0,
                weight: 0
            }
        };
        
        state.apples.map(apple => {
            let selector = apple.isEaten ? 'appleEaten':'appleNow';
            stats[selector].quantity ++;
            stats[selector].weight += apple.weight;
        })
        
        
        return (
            <div className="appleBusket">
                <div className="title">蘋果籃子</div>

                <div className="stats">
                    <div className="section">
                        <div className="head">當前</div>
                        <div className="content">
                            {stats.appleNow.quantity}個蘋果,
                            {stats.appleNow.weight}克
                        </div>
                    </div>
                    <div className="section">
                        <div className="head">已吃掉</div>
                        <div className="content">
                            {stats.appleEaten.quantity}個蘋果,
                            {stats.appleEaten.weight}克
                        </div>
                    </div>
                </div>

                <div className="appleList">
                    { state.apples.map(apple => <AppleItem state ={apple} />) }
                </div>

                <div className="btn-div">
                    <button>摘蘋果</button>
                </div>

            </div>
        );
    }

}

function select(state) {
    return {
        state: state.appleBusket
    }
}

export default connect(select)(AppleBusket);

可見,動態佈局的工作要求只是在 HTML + CSS 佈局的基礎上,再加上 JSX 語法能力即可。

2. 普通顯示元件的動態渲染

普通顯示元件的動態渲染和容器類似,只是這裡的state可以自己規定,並給出示例的mockState(模擬state),使用元件的人按照示例傳入資料即可使用。

AppleItem.jsx 的更新如下:

import React from 'react';

class AppleItem extends React.Component {

    shouldComponentUpdate(nextProps) {
        return nextProps.state != this.props.state;
    }

    render() {

        let { state, actions } = this.props;

        /**
         * 這個區域是 mock 資料區,也作為元件文件,請書寫清楚
         * //在元件釋出時,請註釋掉,提高效能
         */
        let mockState = {
            id: 1,
            weight: 256,
            isEaten: false
        };
        
        let mockActions = {
            eatApple : id => console.log('eatApple',id)
        };

        /**
         * 開關這行程式碼,用於切換裝入的資料來源。(為了開關的方便,請把兩句程式碼合成一行)
         * 在開發階段開啟,使用內部 state 和 action, 開發完成後請註釋關閉
         */
        state = mockState; actions = mockActions;

        if (state.isEaten) return null;

        return (
            <div className="appleItem">
                <div className="apple"><img src="../images/apple.png" alt=""/></div>
                <div className="info">
                    <div className="name">紅蘋果 - {state.id}號</div>
                    <div className="weight">{state.weight}克</div>
                </div>
                <div className="btn-div"><button onClick={() => actions.eatApple(state.id) }>吃掉</button></div>
            </div>
        );

    }


}

export default AppleItem;

容器和普通顯示元件state的對比:

  1. 容器的state我們是從store中的總state直接獲得的,注意 AppleBusket.jsx 靠後面這段程式碼:

    function select(state) {
        return {
            state: state.appleBusket
        }
    }

    select是一個state篩選器, 功能是選擇總state中的 appleBusket 作為本容器的state。而這個state的格式我們會在其對應的reducer中規定(因為我們需要在reducer中提供對應state的預設值)

  2. 普通顯示元件的state格式由元件開發人員自己約定即可,並在mockState 區域給出例子。當別人要使用你的顯示元件時,必須根據你規定的格式傳入state資料。在元件裡面,我們一般會實現如下這樣一個自動切換器,當