【摘要】本系列文章综合分析了当下流行的React库、GraphQL服务器、Relay架构模式的功能特点,并用一个具体的例子展示给大家他们如何协同工作来完成Web应用程序的开发。本文属于本系列的第一篇文章,重点介绍React库、GraphQL服务端和Relay架构各自的功能和关系分析。概述与AngularJS和Ember等框架不同,React是一个提供有限功能集的客户端库,并且该库几乎独立于构建应用程序其他功能可能需要的其他库。从根本上说,React提供了客户端UI组件功能,它提供了一种机制来创建组件、管理组件中的数据、渲染组件以及组合小部件以构建更大的组件。React可用于多种情况,无论数据来自何处、如何检索或如何将其作为大型应用程序的一部分进行管理。为了有效地处理这些问题,需要利用额外的库和模式。React应用程序中使用的常见模式是Flux。Flux被开发为MVC(模型-视图-控制器)模式的替代方案,用于管理响应操作的数据流。与MVC的双向数据流不同,Flux依赖于系统中各个组件之间的单向数据流。Flux是由Facebook创建的,因为他们的开发人员发现很难理解使用MVC模式开发的大型应用程序中数据移动的规律。与MVC模式采用的多循环数据流不同,Flux采用单循环数据流方案。数据只沿着一条线流动,即:Action->Dispatcher->Store(或Stores)->Component(即View)->Action。其中,动作(Action)代表进入系统的某种事件。这可能是用户生成的,例如请求刷新数据的按钮单击事件,或者它可能是通过Web套接字接收的消息事件。然后将此操作传递给调度程序以将其发送到所有商店。调度器实际上只不过是一种转发机制。它不理解什么是动作,它传递的数据的含义,也不理解与该动作关联的每个存储函数。它只是将操作分派给所有商店,每个商店决定是否应该处理该操作。存储组件负责维护数据的本地副本、执行业务规则并通知新数据以便刷新它们。你可以认为存储就是维护应用程序的状态,而整个Flux流程本质上就是一个状态机。因此,React组件使用状态机模式。从某种意义上说,Flux利用相同的状态机模式来构建整个应用程序。虽然Flux是一种解决数据流问题的模式,但它本身并不提供实现这种模式的技术。要使用Flux模式,开发人员必须在系统中创建除Facebook提供的调度程序之外的所有组件。创建Flux系统相对容易,但需要大量样板代码。在这方面,它面临着和Backbone.js一样的问题。启动和运行系统很容易,但最终需要大量编码。Flux的演变过程当开发人员使用Flux时,他们开始寻找将样板代码重构为可重用库的方法。此外,他们还能够识别Flux中的子模式,这使得分析应用程序中的数据流变得更加容易,并在不牺牲Flux的一般优势的情况下降低了应用程序的复杂性。这些子模式包括:将应用程序中的多个存储减少为单个存储;将调度程序和存储组合到同一个组件中(当只有一个存储时很重要);并且,能够将许多组件封装到一个容器中,在这个容器中可以在一个黑盒子中创建动作、调度和存储管理。如今,Flux的许多衍生产品不再局限于纯粹的Flux功能,而且能够避免Flux一贯的缺点,即大量的样板代码,同时保留其根本本质。目前,虽然Flux的衍生品很多,但Redux是比较流行的模式之一。Redux纯粹建立在状态机的概念和不可变数据的思想之上。在这种模式中,动作都由一个“调度程序存储”处理;这些“scheduler-store”对象使用reducer函数(它们本身是可组合的)来实现从一种状态到另一种状态的转换。这引入了很多函数式编程特性,同时极大地简化了Flux模式;一旦掌握了它,编写React应用程序就会容易得多。Relay是Facebook团队的另一个Flux衍生产品,越来越受欢迎。更多Facebook如何使用Relay以及它与Flux的关系,请参考网址https://facebook.github.io/react/blog/2015/02/20/introducing-relay-and-graphql.html。RelayFramework功能分析虽然Redux简化了应用程序的管理,但它在实际数据位置方面是不可知的。它可以使用任何数据存储系统,但同样会导致更多样板代码(尽管少于Flux)问题。所以,Relay(Facebook创建的另一个产品,它大量应用了其他JavaScript开源产品,如React、Relay、Immutable.js、GraphQL、Jest、Flow等),它的目的是去掉其中的一部分与数据访问相关的样板代码,同时还引入了另一种新的数据服务——GraphQL。GraphQL与传统REST服务的不同之处在于,它将数据视为图,并力求以层次化的方式描述图,从而使数据消费者可以指定自己需要的数据,而不是像传统REST服务那样提供一组固定的数据集无论消费者需求如何,都会提供服务。那么,Relay到底是做什么的呢?Relay是一个框架,负责通过实现操作、调度程序和存储的容器将React组件连接到GraphQL服务器。开发者不需要对动作、调度器和存储进行编程,而是可以通过RelayAPI触发这些动作并访问相应的结果。要配置一个容器,开发者必须提供GraphQL查询和变异片段(mutationfragments)来向容器描述数据的图结构;此外,Relay将负责数据管理的所有细节。Relay确实是一个框架(就像Angular),而不是一个库。它的实现可以说是透明的——需要通过React实现UI组件,通过GraphQL提供数据服务。一旦GraphQL服务器和React组件配置到位,Relay将接管并执行所有必需的操作。因此,使用Relay的关键在于掌握配置过程。此外,与框架Angular(仅对客户端有特殊要求)不同,Relay还需要一个GraphQL服务器接口,为Relay容器提供数据查询和变异操作。但是,Relay并不关心数据是如何存储的,只要通过特定的GraphQL接口提供即可。因此,Relay需要后端和前端开发团队都了解它是如何工作的,需要他们弄清楚程序的每一部分(无论是前端还是后端)是如何编码和配置的。Relay与React的配合本节,让我们从React的角度来研究Relay。程序员可以用多种语言编写和配置GraphQL服务器,并将它们部署在多个平台上。对于Node.js环境中的GraphQL实现,有一个名为graphql-relay(https://www.npmjs.com/package/graphql-relay)的包,可用于简化GraphQL服务器的编码和配置要求.在React方面,有一个名为relay-react(https://www.npmjs.com/package/react-relay)的包,可以用来配置Relay容器和路由,也可以激发action来实现数据变异Relay开发入门Relay入门有些困难。由于这项技术太新,竞争对手也很多,目前关于如何使用Relay的参考资源非常有限。在提供资源的地方,通常只有有限的例子;出于这个原因,开发人员最终被迫阅读博客文章、翻阅GitHub问题和官方产品规范,以创建一个简单的CRUD应用程序。此外,还需要搭建一个相当复杂的开发环境,需要一个配置合理的GraphQL服务器。因此,对于JavaScript/前端开发新手来说,这样的任务是相当艰巨的。作为准备工作,请先将GitHub网站(https://github.com/DevelopIntelligenceBoulder/react-flux-blog)上的仓库克隆到您的电脑上,并打开对应的文件夹blog-post-5+6。此文件夹包含完整的GraphQL/React/Relay应用程序。要启动并运行应用程序,请打开终端,导航到文件夹blog-post-5+6,然后运行以下Gulp命令。$npmi$npmi-ggulpeslinteslint-config-airbnbeslint-plugin-react@^4.3.0webpackbabel-clibabel-eslinteslint-plugin-jsx-a11y@^0.6.2$gulp$npmrunupdate-schema$gulp$gulpserver现在,请开启微软的Edge浏览器(【译者注】微软专为Windows10配置的高性能浏览器),然后导航到如下网址:http://localhost:3000。您会注意到该页面将显示一个小部件列表,装饰有Bootstrap4样式,看起来非常漂亮。([译者注]由于某些原因,原文没有提供结果快照)项目的基本开发结构是一个典型的文件夹组织。其中,可编辑的源代码文件存放在src文件夹中,最终部署的文件夹为dist(源代码文件必须复制到这里),使用这里的文件执行应用程序。复制过程是使用Gulp命令执行的;具体来说,通过组合一些简单的复制文件命令,创建一个任务来处理SASS文件,以及一个用于JavaScript的Web打印过程。其中,web打包处理机制使用Babel转译器将RelayQL、JSX和ES2015代码转换成ES5.1兼容的JavaScript代码,可以在任何浏览器中执行。ES2015和JSX翻译不是新技术,但是RelayQL的翻译是一个新话题。RelayQL和Babel-Relay插件GraphQL服务器可以利用内省机制自动生成结构(【译者注】原文中使用的词是schema,常用于数据库表设计;在数据库表设计中也广泛使用许多其他的软件技术,都使用这个词,常译为“模式”或“架构”,本文中为了区别另外两个词模式和架构,特意译为“结构”)。模式实际上是一个JSON文件,它描述了特定GraphQL服务器使用的所有类型。结构可以包含自定义类型和内置类型。Babel-Relay插件使用此结构来验证使用RelayQL编码形成的GraphQL片段。这些片段使用ES2015字符串模板进行编码,并在通过针对结构定义的验证后转换为JavaScript代码。这种验证可以有效地防止GraphQL错误的发生。配置Babel-Relay插件也是最简单的方式,直接从Relay网站(https://facebook.github.io/relay/docs/guides-babel-plugin.html)或Relaystarter生成模式(schema)从包项目(https://github.com/relayjs/relay-starter-kit)下载的工具示例。这些文件是本文对应Github仓库中使用的文件,遵循Relay官网推荐的开发模式。在RelayStarterKit项目中,我们需要使用两个文件:build/babelRelayPlugin.js和scripts/updateSchema.js。其中,UpdateSchema.js文件将用于生成结构(schema),babelRelayPlugin.js文件将使用结构文件来验证GraphQL片段和转换RelayQL代码。GraphQL与Relay一起工作通常,使用Relay需要修改标准的GraphQL服务器实现。我们可以使用一个名为graphql-relay(https://www.npmjs.com/package/graphql-relay)的包来帮助将基于Node.js的GraphQL服务器配置为Relay兼容。配置中继特定类型的GraphQL服务器需要三个主要方面:对象标识、类型连接和突变。通过使用全局唯一的ID值,对象令牌允许Relay从GraphQL服务器查询实现节点接口的任何类型。这个全局ID是base64编码的,由类型名称和一个本地ID值后跟一个冒号组成。graphql-relay库提供名为toGlobalID和fromGlobalID的函数来支持全局ID之间的转换。此外,类型名称来自类型配置中指定的GraphQL自定义类型名称。通常,本地ID值来自数据存储机制,例如关系数据库中的标签(Identity)。请参考以下代码:import{nodeInterface}from'./../node-definitions';exportconstwidgetType=newGraphQLObjectType({name:'Widget',description:'Awidgetobject',fields:()=>({id:globalIdField('Widget'),//morefields}),interfaces:()=>[nodeInterface]});上述代码中,文件node-definitions.js(及其相关文件type-registry)的作用是:通过Node接口使对象可用,提供配置和类型注册。第二个特定于中继的配置,类型连接,是在父类型与其子类型之间建立一对多关系的连接。这些连接使用特殊的连接类型结构进行管理。这些特殊的连接类型结构支持图边和游标的概念,用于限制结果集和生成结果页。连接和边缘类型可以配置为支持附加属性,例如元数据,允许控制连接或边缘特征(例如加权边缘等)。请参考以下代码:import{widgetType}from'./types/widget-type';import{connectionDefinitions}from'graphql-relay';exportconst{connectionType:widgetConnection,edgeType:WidgetEdge}=connectionDefinitions({name:'部件',nodeType:widgetType});上面代码中的ConnectionDefinitions函数用于在Relay期望的结构中创建连接类型。请参考以下代码:import{widgetConnection}from'../connections/widget-connection';//insideoffieldsfunctionofviewertypedeclarationwidgets:{type:widgetConnection,description:'Alistofwidgets',args:connectionArgs,resolve:(_,args)=>connectionFromPromisedArray(getWidgets(),args)}在上面的代码中,WidgetConnection类型是从widget-connection.js文件中导入的,用于配置查看器类型中的控件字段。graphql-relay包还提供了一个名为connectionArgs的对象,其中包含了通过Relay传入的标准参数,用于处理连接。这些参数包含用于游标操作的值。第三个也是最后一个特定于中继的配置是突变配置。graphql-relay包提供了一个名为mutationWithClientMutationId的特殊辅助方法来简化突变配置。有四个字段是必需的:变异名称、输入字段、输出字段和从中获取有效负载的字段。在GraphQL中,所有变更都将伴随查询以了解可能已更改的任何数据。Relay通过智能决定哪些数据在突变后需要刷新来进一步扩展此功能。mutationname是React-Relay应用访问GraphQL服务器时调用mutation的名称。输入字段对应于GraphQL中mutations的args参数。输出字段描述了要从突变中返回的类型的字段。***获取有效负载的字段将执行实际的数据库操作并返回一个承诺对象,该对象将延迟从GraphQL到应用程序的响应时间,直到承诺得到解决。总结React和GraphQL相结合,在Relay的协助下,将为构建Web应用程序提供一个有前途的框架。虽然开发过程需要大量的安装和配置工作,但一旦完成,开发过程就会顺利进行,消除样板代码并智能地处理数据管理问题。实践将证明Relay框架很可能成为构建下一代Web应用程序的游戏规则改变者。在本系列的下一篇文章中,我们将探索使用React和Relay来控制GraphQL资源。
