Rxjs 响应式编程-第一章:响应式
Rxjs 响应式编程-第二章:序列的深刻研究
Rxjs 响应式编程-第三章: 构建并发程序
Rxjs 响应式编程-第四章 构建完整的Web应用程序
Rxjs 响应式编程-第五章 使用Schedulers管理时间
Rxjs 响应式编程-第六章 使用Cycle.js的响应式Web应用程序php
随着单页应用程序的出现,网站忽然被指望作更多,甚至与“原生”应用程序进行竞争。在尝试更快地开发Web应用程序时,开发人员意识到特定领域是瓶颈,使Web应用程序不像其本地应用程序那样快速和强大。html
在Facebook React的带领下,有几个Web框架正在使用着新技术,以便在保持代码简单和声明式的同时制做更快的Web应用程序。前端
在本章中,咱们将介绍一些开发Web应用程序的新技术,例如Virtual DOM。 咱们将使用Cycle.js,这是一个现代,简单,漂亮的框架,在内部使用RxJS并将响应式编程概念应用于前端编程。npm
Cycle.js是RxJS之上的一个小框架,用于建立响应式用户界面。 它提供了现代框架(如React)中的功能,例如虚拟DOM和单向数据流。编程
Cycle.js以反应方式设计,Cycle.js中的全部构建块都是Observables,这给咱们带来了巨大的优点。 它比其余框架更容易掌握,由于理解和记忆的概念要少得多。 例如,与状态相关的全部操做都不在路径中,封装在称为驱动程序的函数中,咱们不多须要建立新的操做。json
什么是虚拟DOM?文档对象模型(DOM)定义HTML文档中元素的树结构。 每一个HTML元素都是DOM中的一个节点,每一个节点均可以使用节点上的方法进行操做。segmentfault
DOM最初是为了表示静态文档而建立的,而不是咱们今天拥有的超级动态网站。 所以,当DOM树中的元素常常更新时,它的设计并不具备良好的性能。 这就是为何当咱们对DOM进行更改时会出现性能损失。设计模式
虚拟DOM是用JavaScript的DOM的映射。 每次咱们更改组件中的状态时,咱们都会为组件从新计算一个新的虚拟DOM树,并将其与以前的树进行比较。 若是存在差别,咱们只会渲染这些差别。 这种方法很是快,由于比较JavaScript对象很快,咱们只对“真正的”DOM进行绝对必要的更改。api
这种方法意味着咱们能够编写代码,就好像咱们为每一个更改生成了整个应用程序UI。 咱们没必要跟踪DOM中的状态。 在幕后,Cycle.js将检查每次更新是否有任何不一样,并负责有效地渲染咱们的应用程序。数组
咱们能够经过使用<script> </script>
标记将它包含在HTML页面中来使用Cycle.js,但这不是使用它的最佳方式,由于Cycle.js是以极其模块化的方式设计的。 每一个模块都尽量地自我依赖管理,而且包括几个模块。由于<script> </script>
能够轻松加载大量重复代码,从而致使没必要要的下载和更长的启动时间。
相反,咱们将使用Node Package Manager,npm和Browserify为咱们的最终脚本生成代码。 首先,咱们将建立一个项目将存在的新文件夹,并安装咱们的项目依赖项:
mkdir wikipedia-search && cd wikipedia-search npm install browserify npm install @cycle/core npm install @cycle/dom
第一个npm命令安装Browserify,它容许咱们为浏览器编写代码,就像它是Node.js应用程序同样。 使用Browserify,咱们可使用Node.js的模块加载器,它将明智地包含哪些依赖项,使代码下载尽量小。 接下来,咱们安装了cycle-core和cycle-dom,它们是Cycle.js的两个基本模块。
有了这个,咱们能够建立一个名为index.js的文件,咱们将编辑咱们的应用程序,而后使用本地Browserify二进制文件将其编译成一个名为bundle.js的文件:
touch index.js `npm bin`/browserify index.js --outfile bundle.js
上面的命令将遍历咱们的依赖树并建立一个bundle.js文件,其中包含运行咱们的应用程序所需的全部内容,包括咱们在代码中须要的任何依赖项。 咱们能够在index.html中直接包含bundle.js:
cycle/index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Wikipedia search</title> </head> <body> <div id="container"></div> <script src="bundle.js"></script> </body> </html>
在本节中,咱们将构建一个搜索Wikipedia做为用户类型的应用程序。
RxJS已经使得检索和处理远程数据变得容易了,可是,正如第4章“构建完整的Web应用程序”中所看到的那样,咱们仍然须要跳过一些环节来使咱们的DOM操做高效。
Cycle.js的目标之一是彻底消除代码中的DOM操做。 让咱们从一些基本的脚手架开始:
cycle/step1.js
var Cycle = require('@cycle/core'); ❶ var CycleDOM = require('@cycle/dom') var Rx = Cycle.Rx; ❷ function main(responses) { return { DOM: Rx.Observable.just(CycleDOM.h('span', 'Hi there!')) }; } var drivers = { ❸ DOM: CycleDOM.makeDOMDriver('#container') }; ❹ Cycle.run(main, drivers);
这段代码在屏幕上显示文字hi!,但已经有至关多的事情发生了。 重要的部分是主要功能和驱动对象。 咱们来看看这些步骤:
Cycle.js驱动程序是咱们用来引发反作用的函数。在咱们的程序中,咱们应该以任何方式修改状态。驱动程序采用从咱们的应用程序发出数据的Observable,它们返回另外一个致使反作用的Observable。
咱们不会常常建立驱动程序 - 只有当咱们须要反作用时,例如修改DOM,从其余接口读取和写入(例如,本地存储)或发出请求。 在大多数应用程序中,咱们只须要DOM驱动程序(呈现网页)和HTTP驱动程序(咱们可使用它来发出HTTP请求)。 在这个例子中,咱们将使用另外一个JSONP驱动程序。
咱们须要页面的实际内容,而不只仅是span
。 让咱们建立一个函数来建立表明咱们页面的虚拟树:
cycle/index.js
function vtreeElements(results) { var h = CycleDOM.h; return h('div', [ h('h1', 'Wikipedia Search '), h('input', {className: 'search-field', attributes: {type: 'text'}}), h('hr'), h('div', results.map(function(result) { return h('div', [ h('a', { href: WIKI_URL + result.title }, result.title) ]); })) ]); }
这个功能可能看起来有点奇怪,但不要惊慌。 它使用Virtual Hyperscript,一种用于建立虚拟DOM树的特定于域的语言。 Virtual Hyperscript包含一个名为h的方法。 h以相似于HTML的方式声明节点,但使用JavaScript语言。咱们能够经过将额外的对象或数组做为参数传递给h来向元素添加属性或将子元素附加到它们。生成的虚拟树最终将呈现为真正的浏览器DOM。
vtreeElements获取一组对象,结果,并返回一个虚拟树,表明咱们应用程序的简单UI。 它呈现一个输入字段和一个由结果中的对象组成的连接列表,最终将包含Wikipedia的搜索结果。 咱们将使用vtreeElements来呈现咱们的应用程序。
咱们可使用JSX编写咱们的UI,而不是使用h函数,JSX是一种由Facebook发明的相似XML的语法扩展,它使得编写虚拟DOM结构更容易,更易读。 咱们的vtreeElements函数看起来像这样:
cycle/index.js
function vtreeElementsJSX(results) { results = results.map(function(result) { var link = WIKI_URL + result.title; return <div><a href={link}>{result.title}</a></div> }); return <div> <h1>Wikipedia Search</h1> <input className="search-field" type="text" /> <hr/> <div>{results}</div> </div>; }
它看起来不是更好吗?JSX看起来对开发人员来讲比较熟悉,由于它相似于HTML,可是咱们能够将它与JavaScript代码一块儿编写,而且咱们能够将其视为JavaScript类型。 例如,注意咱们如何迭代结果数组,咱们直接返回一个<div>元素,使用数组元素自己中的link和result.title的值。(能够经过将它们放在大括号内来内联JavaScript值。)
因为JSX是一种语法扩展,咱们须要一个编译器将其转换为最终的JavaScript代码(它看起来很是像咱们上一节中基于h的代码)。 咱们将使用Babel。 Babel是一个编译器,它将现代JavaScript转换为可在任何地方运行的JavaScript。它还转换了一些JavaScript扩展,例如JSX,也就是以前的用例。
若是要使用JSX,则须要安装Babel并在编译项目时使用它。 幸运的是,Babel有一个名为Babelify的Browserify适配器:
npm install babelify
在每一个使用JSX的文件中,咱们须要在文件顶部添加如下行:
/** @jsx hJSX */ var hJSX = CycleDOM.hJSX;
这告诉Babel使用Cycle.js的hJSX适配器来处理JSX,而不是使用默认的React。
如今,当咱们想要编译项目时,咱们可使用如下命令:
browserify index.js -t babelify --outfile bundle.js
咱们须要一个函数来返回一个Observable of URL,它使用用户输入的搜索词来查询Wikipedia的API:
cycle/index.js
var MAIN_URL = 'https://en.wikipedia.org'; var WIKI_URL = MAIN_URL + '/wiki/'; var API_URL = MAIN_URL + '/w/api.php?' + 'action=query&list=search&format=json&srsearch='; function searchRequest(responses) { return responses.DOM.select('.search-field').events('input') .debounce(300) .map(function(e) { return e.target.value }) .filter(function(value) { return value.length > 2 }) .map(function(search) { return API_URL + search }); }
首先,咱们声明一些咱们的应用程序将用于查询Wikipedia的URL。 在函数searchRequest中,咱们获取包含应用程序中全部驱动程序的响应对象,并在DOM驱动程序中使用get方法。select(element).event(type)
的行为与fromEvent相似:它采用DOM元素的选择器和要监听的事件类型,并返回发出事件的Observable。
这时,代码的其他部分看起来应该很是熟悉,由于它包含经过咱们经常使用的运算符转换Observable值:
太棒了! 到目前为止,咱们有生成UI的功能和从该UI检索用户输入的功能。咱们如今须要添加将从维基百科获取信息的功能。
你可能已经在以前的代码中注意到main函数接受了一个咱们没有使用的参数,responses
。这些是来自run函数中的responses
。驱动程序和main函数造成一个循环(所以框架的名称):main的输出是驱动程序的输入,驱动程序的输出是main的输入。请记住,输入和输出始终是Observables。
咱们使用JSONP查询Wikipedia,就像咱们在第2章中所作的那样。咱们使用JSONP而不是HTTP来更容易在本地计算机上运行此示例,由于使用HTTP从不一样的域检索数据会致使某些浏览器由于安全缘由阻止这些请求。 在几乎任何其余状况下,尤为是在生产代码中,使用HTTP来检索远程数据。
不管如何,使用JSONP并不影响本章的要点。 Cycle有一个JSONP的实验模块,咱们可使用npm安装它:
npm install @cycle/jsonp
而后咱们在咱们的应用中使用它,以下所示:
cycle/step2.js
var Cycle = require('@cycle/core'); var CycleDOM = require('@cycle/dom'); var CycleJSONP = require('@cycle/jsonp'); var Rx = Cycle.Rx; var h = CycleDOM.h; function searchRequest(responses) { return responses.DOM.select('.search-field').events('input') .debounce(300) .map(function(e) { return e.target.value }) .filter(function(value) { return value.length > 2 }) .map(function(search) { return API_URL + search }); } function vtreeElements(results) { return h('div', [ h('h1', 'Wikipedia Search '), h('input', {className: 'search-field', attributes: {type: 'text'}}), h('hr'), h('div', results.map(function(result) { return h('div', [ h('a', { href: WIKI_URL + result.title }, result.title) ]); })) ]); } function main(responses) { return { DOM: Rx.Observable.just(CycleDOM.h('span', 'Hey there!')), JSONP: searchRequest(responses) } } var drivers = { DOM: CycleDOM.makeDOMDriver('#container'), JSONP: CycleJSONP.makeJSONPDriver() }; Cycle.run(main, drivers);
咱们但愿将searchRequest的结果插入到JSONP方法中,这样一旦用户输入搜索词,咱们就会用术语查询Wikipedia。
为此,咱们使用CycleJSONP.makeJSONPDriver建立一个新的JSONP,它将接收咱们在main的返回对象中放置在属性JSONP中的任何内容。在这以后,当咱们在输入框中引入搜索词时,咱们应该已经在查询维基百科,但因为咱们没有将JSONP输出链接到任何内容,咱们在页面上看不到任何更改。 让咱们改变一下:
cycle/step3.js
function main(responses) { var vtree$ = responses.JSONP .filter(function(res$) { return res$.request.indexOf(API_URL) === 0; }) .mergeAll() .pluck('query', 'search') .startWith([]) .map(vtreeElements); return { DOM: vtree$, JSONP: searchRequest(responses) }; }
main经过其响应参数接收全部驱动程序的输出。咱们能够在respond.JSONP中获取JSON调用的结果,这是咱们应用程序中全部JSONP响应的Observable。完成后,咱们能够转换Observable以咱们想要的形式获取搜索结果:
前面代码中最重要的一点是,在最后一步中,咱们彷佛从新绘制了咱们收到的每一个结果的整个UI。 但这里是虚拟DOM闪耀的地方。 不管咱们从新呈现页面多少次,虚拟DOM将始终确保仅呈现差别,从而使其很是高效。 若是虚拟DOM没有更改,则不会在页面中呈现任何更改。
这样咱们就没必要担忧添加或删除元素了。 咱们每次只渲染整个应用程序,咱们让Virtual DOM找出实际更新的内容。
咱们用于构建维基百科实时搜索的架构方法不只仅是另外一个框架的编程UI方法。结构化代码背后有一个设计模式,就像咱们作的那样:Model-View-Intent(MVI)。
Model-View-Intent是一个由Cycle.js建立者AndréStaltz建立的术语,用于受模型 - 视图 - 控制器(MVC)架构启发的体系结构.在MVC中,咱们将应用程序的功能分为三个部分: 模型,视图和控制器。 在MVI中,三个组件是模型,视图和意图。 MVI旨在适应像手套同样的Reactive编程模型。
MVI是被动的,意味着每一个组件都会观察其依赖关系并对依赖项的更改作出反应。 这与MVC不一样,MVC中的组件知道其依赖项并直接修改它们。 组件(C)声明哪些其余组件影响它,而不是明确更新(C)的其余组件。
MVI中的三个组件由Observables表示,每一个组件的输出是另外一个组件的输入。
该模型表示当前的应用程序状态。 它从intent中获取已处理的用户输入,并输出有关视图消耗的数据更改的事件。
视图是咱们模型的直观表示。 它采用具备模型状态的Observable,并输出全部潜在的DOM事件和页面的虚拟树。
意图是MVI中的新组件。意图从用户获取输入并将其转换为咱们模型中的操做。若是咱们从新调整和重命名咱们的代码,咱们能够在咱们的应用程序中使这三种组件更清晰:
cycle/index-mvi.js
function intent(JSONP) { return JSONP.filter(function(res$) { return res$.request.indexOf(API_URL) === 0; }) .concatAll() .pluck('query', 'search'); } function model(actions) { return actions.startWith([]); } function view(state) { return state.map(function(linkArray) { return h('div', [ h('h1', 'Wikipedia Search '), h('input', {className: 'search-field', attributes: {type: 'text'}}), h('hr'), h('div', linkArray.map(function(link) { return h('div', [ h('a', { href: WIKI_URL + link.title }, link.title) ]); })) ]); }); } function userIntent(DOM) { return DOM.select('.search-field') .events('input') .debounce(300) .map(function(e) { return e.target.value }) .filter(function(value) { return value.length > 2 }) .map(function(search) { return API_URL + search }); } function main(responses) { return { DOM: view(model(intent(responses.JSONP))), JSONP: userIntent(responses.DOM) }; } Cycle.run(main, { DOM: CycleDOM.makeDOMDriver('#container'), JSONP: CycleJSONP.makeJSONPDriver() });
经过将模型,视图和意图拆分为单独的函数,咱们使代码更加清晰。 (另外一个意图,userIntent,是JSONP驱动程序的输入。)大多数应用程序逻辑在咱们传递给main函数中的DOM驱动程序的属性中表示为这三个函数的组合:
function main(responses) { return { DOM: view(model(intent(responses.JSONP))), JSONP: userIntent(responses.DOM) }; }
它没有那么多功能!
随着咱们制做更复杂的应用程序,咱们但愿重用一些UI组件。 咱们的维基百科搜索应用程序很小,可是它已经有一些能够在其余应用程序中重用的组件。 以搜索输入框为例。 咱们绝对能够将它变成本身的小部件。
目标是将咱们的小部件封装在本身的组件中,以便咱们将其用做任何其余DOM元素。 咱们还应该可以使用咱们想要的任何属性来参数化组件。 而后咱们将在咱们的应用程序中使用它,以下所示:
var wpSearchBox = searchBox({ props$: Rx.Observable.just({ apiUrl: API_URL }) });
咱们将使用Cycle.js引入的概念构建咱们的小部件,它将一个Observable事件做为输入,并输出一个Observable,其结果是将这些输入应用于其内部逻辑。
让咱们开始构建搜索框组件。 咱们首先建立一个函数,它接受一个响应参数,咱们将从主应用程序传递任何咱们想要的属性:
cycle/searchbox.js
var Cycle = require('@cycle/core'); var CycleDOM = require('@cycle/dom'); var Rx = Cycle.Rx; var h = CycleDOM.h; var a; function searchBox(responses) { var props$ = responses.props$; var apiUrl$ = props$.map(function (props) { return props['apiUrl']; }).first(); }
searchBox接收的每一个参数都是一个Observable。 在这种状况下,props $是一个Observable,它发出一个包含Wikipedia搜索框配置参数的JavaScript对象。
检索属性后,咱们为窗口小部件定义虚拟树。 在咱们的例子中,它只是一个很是简单的输入字段:
cycle/searchbox.js
var vtree$ = Rx.Observable.just( h('div', { className: 'search-field' }, [ h('input', { type: 'text' }) ]) );
咱们但愿全部东西都是一个Observable,因此咱们将虚拟树包装在一个Observable中,它只返回一个Observable,它发出咱们传递它的值。
如今,只要用户在输入字段中键入搜索词,咱们就须要搜索框来查询Wikipedia API。 咱们重用上一节函数userIntent中的代码:
cycle/searchbox.js
var searchQuery$ = apiUrl$.flatMap(function (apiUrl) { return responses.DOM.select('.search-field').events('input') .debounce(300) .map(function (e) { return e.target.value; }) .filter(function (value) { return value.length > 3; }) .map(function (searchTerm) { return apiUrl + searchTerm; }); });
咱们仍然须要将searchQuery的输出链接到JSON驱动程序的输入。 咱们就像在正常的Cycle应用程序中那样作:
cycle/searchbox.js
return { DOMTree: vtree$, JSONPQuery: searchQuery$ };
最后,咱们不该该忘记导出搜索框小部件:
cycle/searchbox.js
module.exports = searchBox; // Export it as a module
如今咱们已准备好在您的应用程序中使用搜索框小部件。 主要方法如今看起来像这样:
cycle/index-mvi2.js
var h = CycleDOM.h; ❶ var SearchBox = require('./searchbox'); function main(responses) { ❷ var wpSearchBox = SearchBox({ DOM: responses.DOM, props$: Rx.Observable.just({ apiUrl: API_URL }) }); ❸ var searchDOM$ = wpSearchBox.DOMTree; var searchResults$ = responses.JSONP .filter(function(res$) { return res$.request.indexOf(API_URL) === 0; }) .concatAll() .pluck('query', 'search') .startWith([]); return { ❹ JSONP: wpSearchBox.JSONPQuery, ❺ DOM: Rx.Observable.combineLatest( searchDOM$, searchResults$, function(tree, links) { return h('div', [ h('h1', 'Wikipedia Search '), tree, h('hr'), h('div', links.map(function(link) { return h('div', [ h('a', { href: WIKI_URL + link.title }, link.title) ]); })) ]); }) }; } Cycle.run(main, { DOM: CycleDOM.makeDOMDriver('#container'), JSONP: CycleJSONP.makeJSONPDriver() });
如今咱们将处理用户输入和呈现搜索框的责任委托给wpSearchBox小部件,咱们能够在另外一个须要查询URL API的搜索框的应用程序中轻松地重用该小部件。 这些是主要的变化:
有了最终的代码,咱们能够看到Cycle.js的最大亮点。 框架中没有不一样的类,特殊类型或“魔术”。 这是全部无反作用的函数,它们接受Observable并输出更多的Observable。 只有这样,咱们才有一个简洁的Web应用程序框架,清晰,反应灵敏,使用起来颇有趣。 它不惜一切代价避免反作用,使咱们的Web应用程序更加健壮。
除了迫切须要更好的图形设计外,咱们的应用程序可使用一些功能,而不只仅是快速重定向到维基百科的结果:
如今您知道如何开发使用现代技术的Web应用程序而不放弃响应性理念。 本章提供了如何使用Observables和RxJS做为其余框架或应用程序的内部引擎的想法。 经过站在Observables的肩膀和活跃的生活方式,咱们能够极大地简化Web应用程序并将状态下降到最小的表达,使咱们的Web应用程序不那么脆弱和易于维护。
感谢您阅读本书。 我但愿它能帮助您从新思考开发JavaScript应用程序的方式,并挑战一些有关编程的现有概念。 这是快速,强大和反应性的软件!
关注个人微信公众号,更多优质文章定时推送