做者:珂珂(滬江前端開發工程師)
本文原創,轉載請註明做者及出處。
原文地址:https://hackernoon.com/thinking-in-redux-when-all-youve-known-is-mvc-c78a74d35133#.u2jqlinjnjavascript
當咱們在Spoil打算推出咱們本身的移動端應用時,頭一個須要做出的決定就是:咱們應該使用哪一種編程語言?通過一番討論,咱們最終作出的決定是:React-Native。學習一門新的「語言」或者框架並非個大問題,可是老兄我得告訴你,React-Native和Redux確確實實是塊難啃的骨頭。這篇文章沒有介紹React-Native是如何工做的(由於那確實不是最難的部分)。下面幾段文字的目的在於幫助任何人完成從「Thingking in MVC」到「Thinking in Redux」的轉換。但願能對你有所幫助。前端
一旦你開始學習React-Natvie(或React),在有人向你說起Redux以前,你大概只落後了3個stack overflow的問題,或者medium.com上幾篇博客的距離。java
你固然很高興了。你開始理解state和props的區別了,你知道了componentDidMount是幹啥的了,你甚至懂得了怎樣合理地建立一個可複用組件。可是突然間,你發現本身到了egghead.io網站上,這裏的一些傢伙正討論着stores、reducer compositions、action,還有將state映射到props。react
你同時也意識到,以前你能夠這麼作:編程
$(「.my-button」).click();
讓一個按鈕乾點什麼;如今?3個小時可能你的一個按鈕啥也幹不了。redux
若是你是從MVC(或者MVVC)的世界過來的,你習慣了使用models,views和controllers。可是在Redux中,咱們用actions、reducers、stores和components來解決問題。從MVC「遷移」到Redux是比較困難,但這裏是個人作法:react-native
Actions = Controller
把你的Actions想象成controller。不管什麼時候你想讓你的App產生一些活動的時候(好比:載入數據、將isLoading標誌從true變爲false等等),那麼你須要分發一個action。就像在MVC中,你須要調用一個controller。api
Reducer = Model
某種程度上吧。你的reducers將會掌管應用程序的當前狀態(好比: 用戶信息、api載入的數據、須要展現的數據)。當一個action被調用時,reducer來決定須要作些什麼。在MVC中你可能有一個帶setName()方法的model,在Redux中,你將會有一個reducer,它負責處理一個action,並將name設置到state中去。架構
特別感謝 Prabin Varma*。將store講的更生動形象。mvc
Stores = ???
store在Redux中很特別,在MVC中難以找和它等價的東西。可是不用擔憂。store是深藏在幕後被當心保管的東西,就像是一個容器,存儲了全部爲state服務的reducer集合。它有一個方法來得到當前的狀態,而且暴露出方法來訂閱state的變更(使用「connect()」方法)。這就是Redux容許你調用action,並能將它們像props同樣傳入組件的祕密了。
Components = Views
組件是有些相似於你的智能視圖。它們負責展現你從state中拿到的信息。我建議將你的組件分爲兩部分:一部分只是做爲展現部分(木偶組件),另外一部分負責處理全部的action和state變動(智能組件)。
MVC和Redux之間一個主要的不一樣點就是:MVC中的數據可以雙向流動,但在Redux中,數據被限制爲只能單向流動。
經典MVC。那時的人生尚未如此艱難。
如你所見(以及從經驗中瞭解到的)在上面的圖表中,數據可以雙向流動。你在view層按下了一個button,它會向你的controller發送一個信息,致使model的更新。model改變了一些值,並將值返還給controller,而後controller刷新了view。灰常簡單!
Redux數據流。人生變得糟透了。
在Redux中事情有些不一樣。假如你有一個組件,而後你想在按鈕被按下的時候作些事情。那麼你該從何開始呢?我是這麼作的:
下面是個解釋以上概念的簡單代碼示例。在這個例子中,我將會展現如何編輯一個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前端高效開發實戰》已在亞馬遜、京東、噹噹開售。