第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应用中,这些重要的特性同样适用于其他的技术场景。例如:
- 桌面应用
- 游戏
- 电子邮件
- 绘图