當我們在Spoil打算推出我們自己的移動端應用時,頭一個需要作出的決定就是:我們應該使用哪種編程語言?經過一番討論,我們最終做出的決定是:React-Native。學習一門新的“語言”或者框架並不是個大問題,但是老兄我得告訴你,React-Native和Redux確確實實是塊難啃的骨頭。這篇文章沒有... ...
作者:珂珂(滬江前端開發工程師)
本文原創,轉載請註明作者及出處。
原文地址:https://hackernoon.com/thinking-in-redux-when-all-youve-known-is-mvc-c78a74d35133#.u2jqlinjn
當我們在Spoil打算推出我們自己的移動端應用時,頭一個需要作出的決定就是:我們應該使用哪種編程語言?經過一番討論,我們最終做出的決定是:React-Native。學習一門新的“語言”或者框架並不是個大問題,但是老兄我得告訴你,React-Native和Redux確確實實是塊難啃的骨頭。這篇文章沒有介紹React-Native是如何工作的(因為那確實不是最難的部分)。下麵幾段文字的目的在於幫助任何人完成從“Thingking in MVC”到“Thinking in Redux”的轉換。希望能對你有所幫助。
React-Natvie 和 Redux?
一旦你開始學習React-Natvie(或React),在有人向你提及Redux之前,你大概只落後了3個stack overflow的問題,或者medium.com上幾篇博客的距離。
你當然很高興了。你開始理解state和props的區別了,你知道了componentDidMount是幹啥的了,你甚至懂得了怎樣合理地創建一個可復用組件。但是忽然間,你發現自己到了egghead.io網站上,這裡的一些家伙正討論著stores、reducer compositions、action,還有將state映射到props。
你同時也意識到,之前你可以這麼做:
$(“.my-button”).click();
讓一個按鈕乾點什麼;現在?3個小時可能你的一個按鈕啥也幹不了。
作一些類比
如果你是從MVC(或者MVVC)的世界過來的,你習慣了使用models,views和controllers。但是在Redux中,我們用actions、reducers、stores和components來解決問題。從MVC“遷移”到Redux是比較困難,但這裡是我的做法:
Actions = Controller
把你的Actions想象成controller。無論何時你想讓你的App產生一些活動的時候(比如:載入數據、將isLoading標誌從true變為false等等),那麼你需要分發一個action。就像在MVC中,你需要調用一個controller。
Reducer = Model
某種程度上吧。你的reducers將會掌管應用程式的當前狀態(比如: 用戶信息、api載入的數據、需要展示的數據)。當一個action被調用時,reducer來決定需要做些什麼。在MVC中你可能有一個帶setName()方法的model,在Redux中,你將會有一個reducer,它負責處理一個action,並將name設置到state中去。
特別感謝 Prabin Varma*。將store講的更生動形象。
Stores = ???
store在Redux中很特別,在MVC中難以找和它等價的東西。但是不用擔心。store是深藏在幕後被小心保管的東西,就像是一個容器,存儲了所有為state服務的reducer集合。它有一個方法來獲得當前的狀態,並且暴露出方法來訂閱state的變動(使用“connect()”方法)。這就是Redux允許你調用action,並能將它們像props一樣傳入組件的秘密了。
Components = Views
組件是有些類似於你的智能視圖。它們負責展示你從state中拿到的信息。我建議將你的組件分為兩部分:一部分只是作為展示部分(木偶組件),另一部分負責處理所有的action和state變更(智能組件)。
從MVC思想轉換至Redux思想
MVC和Redux之間一個主要的不同點就是:MVC中的數據能夠雙向流動,但在Redux中,數據被限製為只能單向流動。
經典MVC。那時的人生還沒有如此艱難。
如你所見(以及從經驗中瞭解到的)在上面的圖表中,數據能夠雙向流動。你在view層按下了一個button,它會向你的controller發送一個信息,導致model的更新。model改變了一些值,並將值返還給controller,然後controller刷新了view。灰常簡單!
Redux數據流。人生變得糟透了。
在Redux中事情有些不同。假如你有一個組件,然後你想在按鈕被按下的時候做些事情。那麼你該從何開始呢?我是這麼做的:
- 定義你的Action
- 定義你的Reducer
- 在你的Component中將Actions像props一樣定義
- 把它們放到View上
下麵是個解釋以上概念的簡單代碼示例。在這個例子中,我將會展示如何編輯一個text input,然後當有用戶按下按鍵時它將會調用action來保存內容。
首先,從Action文件開始
export const MODIFY_NAME = "MODIFY_NAME";
export const SAVE_NAME = "SAVE_NAME";
/**
* 這是當用戶按下按鍵的時候,我們從組件上所調用的action。
用戶每輸入一個字元,都會帶著input中新的value值去調用這個action。
註意函數中的type和payload欄位,我們將在reducer中用到它們,去用新的value值“修改”我們的model。
**/export function modifyName(name){ return {
type: MODIFY_NAME,
payload:{
name
}
}
}
/**
這是當用戶按下保存姓名按鈕的時候,我們從組件上所調用的action。
註意我們是如何將value傳入的。這麼做是因為reducer已經持有了該value值。
另外,這裡也沒有payload。這麼做的原因是因為reducer並不需要。在reducer那一步中,不需要額外的信息。
同時,一般這麼做將調用一個api終端以及諸如此類的東西,但是為了簡潔,我沒有將其包含進來。
**/export function saveName(){ return{
type: SAVE_NAME
}
}
現在看我們的Reducer。總的來說,reducer需要處理傳入的actions。
// 導入我們早先定義好的actions文件
import * as constants from '../actions.js';
/**
初始狀態被用來定義你的reducer。
通常你將會把它設置為預設值和空字元串。需要這麼做的理由是,當要使用這些值的時候,你至少保證它們有一個預設值。把它當做一個預設構造器吧。
**/const initialState = {
name:'',
isSaved: false
}
/**
這個reducer是負責“監聽”輸出的action。我們早些定義的saveName和modifyName函數,將會在這裡被調用。action參數則是上面函數中定義的將要被return出來的值(type和payload)。
**/function reducer(state=initialState,action){ switch (action.type){/**
在Redux中state是不可變的。你必須時刻返回一個新的,所以這裡使用ES6的展開運算符將傳入的state中的值拷貝過來。
**/
case constants.MODIFY_NAME: return {
...state,
name:action.payload.name
} case constants.SAVE_NAME: return {
...state,
isSaved:!state.isSaved
}
}
}
export default name;
註意到constants.MODIFY_NAME 和 constants.SAVE_NAME 是如何與我們在action中將要返回出來的對象的type欄位對應上的。正是以這種方式,reducer才能得知action的身份。
現在來定義我們的“智能”組件。這意味著這個組件將會定義所有對action的調用。
/** App首頁 **/
‘use strict’;
import React, { Component } from ‘react’;
import { connect } from ‘react-redux’;
import Name from ‘./presentational/Name’;
import * as actions from ‘./actions/name’;
/**
實際的值(name和isSaved)和調用action所需的function都以props的形式傳入。**/
class NameContainer extends Component {
render() {
return (
<Name
name = {this.props.name}
isSaved = {this.props.isSaved}
modifyName = {this.props.modifyName}
saveName = {this.props.saveName}
/>
);
}
}
/**
這麼做是為了得到reducer中保存的值,並且把它們返回給component。這樣我們才能通過this.props來調用它們
**/
const mapStateToProps = (state,ownProps) =>{
/**
使用Redux的stores,它允許我們僅僅通過state.name來獲得reducer中的值。註意name是如何通過reducer導出的。
**/
const { name, isSaved } = state.name;
return {name,isSaved };
}
/**
在mapStateToProps函數中,我們將state中的變數映射成property來傳入我們的展示組件。在mapDispatchToProps函數中,我們將action處理函數映射到我們的容器,這樣我們就能將它們傳入到展示組件中去了。
**/
const mapDispatchToProps = (dispatch) => { return {
modifyName:(name)=>{
dispatch(actions.modifyName(name))
},
saveName:()=>{
dispatch(actions.saveName())
},
}
/**
如你所見,我們能夠將函數和變數以props的方式傳入我們的容器全依賴於此。是react-redux中的connect函數神奇的實現了這些功能。
**/
export default connect(mapStateToProps,mapDispatchToProps)(NameContainer);
現在到了最簡單的部分,創建一個與用戶交互的展示組件(MVC里的V)。
/**
*
木偶組件將會使用傳入的props,這些是用戶的行為在智能組件上產生的數據
*/‘use strict’;
import React, { Component } from ‘react’;
import {Text,TextInput, TouchableOpacity} from ‘react-native’;
import { Actions, ActionConst } from ‘react-native-router-flux’;
class Name extends Component
render() { return ( <View>
<TextInput
multiline={false}
//from the component above
value={this.props.name}
placeholder=”Full Name”
//from the component above
onChangeText={(name)=>this.props.modifyName(name)}
/>
<TouchableOpacity onPress= {()=>{this.props.saveName()}>
<Text>Save</Text>
</TouchableOpacity>
</View>
);
}
}
export default Name;
就是這樣了!雖然你仍然需要做一些基礎的的模版設置填充,但是我希望這解釋清楚瞭如何以redux的方式進行思考。
有些問題曾經讓我掉到坑裡一段時間(比如:信息傳到了哪?怎麼傳的),因此我希望節省你們的時間,減輕你們的頭疼。
如果你希望看到我們團隊使用React-Native 和 Redux搭建出來的app是什麼樣的,那麼在這兒查看我們的app吧(https://spoil.co/app)。
iKcamp原創新書《移動Web前端高效開發實戰》已在亞馬遜、京東、噹噹開售。