例項講解基於 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 比喻成兩種乘客。這是一趟環路巴士:
-
js巴士
從store車站
出發,載上state乘客
,state乘客
到達某個container車站
下車並把自己展示出來 -
過了一會,有一個
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的對比:
-
容器的state我們是從store中的總state直接獲得的,注意 AppleBusket.jsx 靠後面這段程式碼:
function select(state) { return { state: state.appleBusket } }
select是一個state篩選器, 功能是選擇總state中的 appleBusket 作為本容器的state。而這個state的格式我們會在其對應的reducer中規定(因為我們需要在reducer中提供對應state的預設值)
-
普通顯示元件的state格式由元件開發人員自己約定即可,並在mockState 區域給出例子。當別人要使用你的顯示元件時,必須根據你規定的格式傳入state資料。在元件裡面,我們一般會實現如下這樣一個自動切換器,當