第16章 架構模式
React主要功能在于渲染HTML。可以將其看成是MVC中的V,它不會影響到組件中直接調用AJAX請求之類的操作:
var TakeSurvey=React.CreateClass({getInitialData:function(){return{survey:null};},componentDidMount:funciton(){$.getJSON('/survey/'+this.props.id,function(json){this.setState({survey:json});});},render:function(){return <div>{this.state.survey.title}</div>}
});
路由
路由在單頁面應用里為URL指定處理器函數。假設要為URL/surveys運行一個函數,函數的功能是從服務器加載用戶,然后渲染<LIstSurveys>
組件。
路由有很多種,在服務器端也存在路由,有一些路由可以在客戶端和服務器端運行。
React僅僅是一個一個的渲染類庫,沒有路由的功能,不過有很多路由模塊可以搭配React使用。
Backbone.Router:
Backbone是單頁面應用類庫,采用了MVX(Model-View-Whatever)架構。其中X指代控制器(Controller),通常指代的是路由。對Backbone來說就是如此。
Backbone是模塊化的,你可以只用它的路由功能。它可以和React很好的搭配在一起使用。例如:
var SurveysRouter =Backbone.Router.extend({routes:{"surveys":"list"},list:function(){React.renderComponent(<ListSurveys />,document.querySelector('body'));}
});
路由需要處理URL中動態的部分和queryString。Backbone.Router具備這樣的功能,示例如下:
// surveys_routers.js
varSurveysRouter=Backbone.Router.extend({routes:{"surveys":"list","surveys/:filter":"list"},list:function(filter){React.renderComponent(<ListSurveys filter={filter}/>,document.querySelector('body));},
});
在上面的例子中,給定一個比如/surveys/active這樣的URL地址,那SurveysRouter#list的參數filter就是active。
Aviator
與Backbone.Router不同,Aviator是一個獨立的路由類庫。
在Aviator中,路由定義與RoutetTarget是相互獨立的。即Aviator不關心RouteTarget的實現和行為,僅試著調用賦值在它上面的方法。
比如這樣一個RouteTarget:
// surveys_route_target.js
varSurveysRouteTarget={list:function(){React.renderComponent(<ListSurveys>,docment.querySelector('body'););}
};
與這個對象對應,需要有一份路由的配置(整個應用只能有一個唯一的路由配置),這個配置通常寫在另一個獨立的文件中。
// routes.js
Aviator.setRoutes({'/surveys':{target:UsersRouteTarget,'/':'list'}
});
//讓Aviator把url分派到RouteTarget
Aviator.dispatch();
設置RouteTarget處理函數:
// route.js
Aviator.setRoutes({'/surveys':{target:usersRouteTarget,'/':'list','/:filter':'list'}
});// surveys_route_target.js
varSurveysRouteTarget={list:function(request){React.renderComponent(<ListSurveys filter={request.param.filter}/>,doucument.querySelector('body'));}
}
Aviator有一個很棒的特性就是可以使用多個RouteTarget,例如下main這樣的路由配置:
// route.js
Aviator.setRoutes({target:AppRouteTarget,'/*':beforeAll,'/surveys':{target:UsersRouteTarget,'/':'list''/:filter':'list'}
});
對于'/surveys/active'這樣的URL,Aviator會先調用appRouteTarget.beforeAll在調用UsersRouteTarget.list——只要匹配,Action的數量并不受限制。你還可以定義路由離開時的執行函數,當用戶從匹配的路由離開時,定義過的執行函數會從內到外依次執行。
react-router
react-router不同于其他的路由,它完全是有ReactComponent構成的。
路由被定義成了組件,路由的處理器也是組件。
按照react-router的寫法路由是這樣的:
var appRouter=(<Routes location="history"><Route title="SurveyBuilder" handler={App}><Route name="list" path="/"handler={ListSurveys}/}<Route title ="Add Survey to SurveyBuilder"name="add" path="/add_survey" handler={AddSurvey}/><Route name ="edit" path="/surveys/:surveyId/edit" handler={EditSurvey}/><Route name="take" path="/surveys/:surveyId"handler={TakeSurveyCtrl} /><NotFound title="page Not Found"handler={NotFoundHandler}/></Route></Routes>
);
每一個處理器就是一個對應著特定頁面的組件。把上面的路由作為頂層的組件渲染來啟動它。
React.renderComponent(appRouter,document.querySelector('body')
);
就像其他的路由一樣,react-router也有類似的參數概念。 比如路由'/surveys/:surveyId'會把surveyId屬性傳給TakeSurveyCtrl組件。
Link是react-router提供的很酷的特性之一。你可以使用它來導航,它可以自己對應到路由上。而且它還能自動給鏈接添加active樣式,標記當前活動頁面。
使用react-router Link組件后組件看起來是這樣的:
varMainNav=React.createClass({render:function(){retrun(<nav className='main-nav' role='navigation'><ul className='nav navbar-nav'><li><Link to='list'>All Surveys </link></li><li><Link to="add" >AddSurvey</Link></li></ul></nav>);}
});
Om(ClojureScript)
Om是比較流行的React的ClojureScript接口。通過ClojureScript的不可變數據結構,Om可以飛快地重新渲染整個應用,而且每個操作都可以很容易地被存為快照,用來實現撤銷等功能。
Om組件看起來是這樣:
(ns example
(:require [om.core :as om :include-macros true][om.dom:as dom :include-macros true]))
(defn App[data owner](reifyom/IRender(render[this](dom/h1 nil (:text data)))))
(om/rootApp {text "Survey Builder"}{:targer(. js/document (querySelector "body"))})
它將被渲染為<h1>SurveyBuilder</h1>
Flux
前面的架構模式都是隨著React的開源而出現的。但Flux是React的原作者從一開始就設計好的。
Flux是Facebook引入的架構模式。它為React提供了一套單向數據流模式,這個模式很容易審查數據修改的過程和原因。實現Flux起來只需要很少的腳手架和代碼。
Flux由三個主要的部分組成:Store,Dispatcher和視圖(即React Component)。Action作為創建Dispatcher的語義化接口的輔助方法,可以當做Flux模式的第四部分。
頂層的React組件類似于一個控制視圖(Controller-View)。控制視圖的組件與Store進行交流并協助其與子組件進行通信。這與iOS開發中的控制視圖差別不大。
Flux模式里的每個部分都是獨立的,強制進行了嚴格的隔離,通過隔離來保證每個部分都易于測試。
Flux的數據流
Flux使用的是單向數據流。這在已有的MVC框架中顯得很特別,但也帶來了一些獨特的好處。因為Flux沒有用雙向綁定,所以他的應用很容易審查問題。狀態是在Store(Store是Flux中數據的擁有者)中維護的,因此很容易跟蹤。Store通過change方法傳送數據,觸發視圖的渲染。用戶的輸入行為會出發Action通過Dispatcher進行分派,以此傳送數據到專門處理特性Action的Store中。
Flux各個部分
Flux由實現特定功能的幾個部分組成。在單向數據流中,Flux的每個部分獲取上游輸入的內容進行處理,然后向下游發送它的輸出內容。
- Dispatcher——應用的中心倉庫
- Action——應用的DSL(Domain Specific Language)
- Store——業務邏輯和數據交互
- 視圖——渲染應用組件樹
Dispatcher
我們從Dispatcher開始,因為他是所有用戶交互和數據流的中心倉庫。在Flux模式當中Dispatcher是一個單例。
Dispatcher負責在Store上注冊回調以及管理它們之間的依賴關系。用戶的Action會流入Dispatcher。數據會傳送到注冊過Action的Store當中。
我們的Survey Builder中包含了一個相對簡單但有效地管理單個Store的Dispatcher。然而,隨著應用的擴張,你一定會遇到需要管理多個Store和他們之間依賴的情況。這種情況我們會在后面討論。
Action
從用戶的角度看,這是Flux的起點。每個在UI上的行為都會創建一個被發送到Dispatcher的Action。
盡管Action不是Flux模式真正的部分,但他們構成了應用的DSL。用戶操作被轉化成為有含義的Dispatcher Action——Store可以依次調用相應的行為。
Store
Store負責封裝應用的業務邏輯與應用數據交互。Store通過注冊Action來選擇相應哪些Action。Store把內部的數據通過更改時的change事件發送到React的組件當中。
保持Store嚴格的獨立非常重要。
- Store中包含應用的所有數據
- 應用其他任何部分都不知道怎么操作數據——Store是應用中唯一的數據發生改變的地方
- Store沒有賦值——所有的更改都是由Dispatcher發送到Store的。新的數據隨著Store的更改事件傳送回到應用中。
控制視圖
應用的組件層級一般會有一個頂層的組件負責與Store交互。簡單的應用只有一個控制視圖,復雜一些的應用可能會有多個。
管理多個Store
當一個Store依賴另一個的時候,數據的關系會變得復雜,比如一個Store要在另一個Store響應同一個Action之前先完成自身的調用。
第17章 其他使用場景
React不僅是一個強大的交互式UI渲染類庫,而且提供了一個用于處理數據和用戶輸入的絕佳方法。它倡導可重用并且易于測試的輕量級組件。不僅在Web應用中,這些重要的特性同樣適用于其他的技術場景。例如:
- 桌面應用
- 游戲
- 電子郵件
- 繪圖