距离Facebook发布新版Relay(Relay Modern)已经快一年时间了,但相关的中文资料与实践案例依然不是不少。究其缘由,可能和官方文档不够详细有关。本文经过对GraphQL与Relay的浅析,但愿能下降其上手难度,同时也便于判断,本身的业务是否适合使用Relay框架。
什么?直接上代码?猴~能够Github克隆Relay应用模版,里面整合了先后端和路由,基本能知足常见App的需求。html
GraphQL是一套独立的数据查询系统,关于它的介绍与使用,官方网站已有比较详细的介绍,同时,如今已有中文版能够参考。对于基本概念,建议直接阅读官网,本文不作详细介绍。前端
之因此名称中包含有Graph,是由于GraphQL采用了图结构的查询方式。以一个例子来看:
咱们想要设计一个公司的内部人员管理系统,假设一种最简单的场景,至少会包含部门和员工两大信息。以图的结构来表示他们的关系的话,可能会是这样:git
{ 员工(ID: "022") { 姓名 职位 所属部门 { 名称 } 同事 { ID 姓名 职位 } } } 复制代码
正常状况下,服务端返回的结果是:github
{ 员工 { 姓名: "L某", 职位: "员工", 所属部门:{ 名称: "前端部" } 同事:[ { ID: "022", 姓名: "S某", 职位: "经理" }, { ID: "033", 姓名: "Y某", 职位: "总监" } ] } } 复制代码
能够看到,根据查询请求,员工的信息以Json的格式所有返回出来了。对应到图中,实际上是提取了蓝色的这部分信息: 数据库
{ 员工(ID: "022") { 姓名 职位 ... } 部门(名称: "设计部") { 名称 } } 复制代码
返回结果:redux
{ 员工 { 姓名: "L某", 职位: "员工", ... } 部门 { 名称: "设计部" } } 复制代码
在实际的应用开发中,也一般采用以上这样的设计结构。
到此,咱们来总结一下。GraphQL经过查询语句,将图结构的数据,提取成了树状结构做为结果返回。这里的图结构的数据,对应的即是GraphQL的类型系统。而如何将图中的节点,也就是定义的各个类型相互关联,须要经过具体的逻辑代码来实现。官方和社区也提供了各类语言的GraphQL库。
那么还有个问题,若是我想修改数据该怎么办呢?
为解决这个问题,GraphQL引入了Mutation的概念。咱们能够把Mutation看做是一种特殊的查询,你须要为它定义名称、参数、返回数据,并在具体的代码逻辑中,完成它的具体数据操做。详细能够参考官方文档。segmentfault
官方网站简单介绍了GraphQL的一些优势,不过,你可能更想知道,相比其余的API设计模式,GraphQL有什么优点呢?
根据我实践下来的理解,GraphQL主要解决了面向前端的API在开发和后期维护中,常会遇到的一些矛盾点。下面,咱们以RESTful API为参照,来具体看一下。后端
再来看上一节的例子,像获取员工所有信息这样的场景在应用开发中仍是比较常见的,若是换成RESTful的API,会是怎么样的呢?我想,通常会有两种作法:设计模式
咱们先看第一种作法。这种方式有几点明显的弊端:
首先是信息的冗余。若是我在其余地方只须要显示员工基础信息,不包含具体的部门或同事信息,那就存在了数据的冗余。
其次,从后端角度,会带来维护上的成本。因为前端的展现需求相对多变,极可能会形成许多再也不使用的API,而这些API又每每不敢轻易移除。又或者,在新需求中,极可能会新增与现有API重复度较高的API,形成后端业务代码的冗余。
再者,即便考虑经过参数的方式,能使得返回值有可选择性,确实能够增长必定的灵活度,但又不可避免的增长了参数的复杂性。
再看第二种作法。经过这种细粒度的模块划分方式,相对第一种来讲,减轻了后端代码的维护成本,但却对前端极不友好。好比,一个列表中的某个字段,后端只提供了单独查询的方式,当列表数据量特别大的时候,请求数也大大增长,将直接影响前端性能与用户体验。此外,大量的异步请求无疑增长了前端代码复杂度,从而提升了前端的维护成本。
而GraphQL只须要定义好类型及对应的数据处理方式,暴露给查询根节点,前端能够随意按需请求,很好的解决了以上的矛盾。数组
GraphQL用来做为BFF层(Backend For Frontend)有其先天的优点,最主要在于其面向前端的友好性设计。
除了上面提到的,在查询请求中,请求数的减小对前端体验上的提高以外,Mutation一样减小了请求次数。在Restful API中,除了GET请求,其它的请求完成后,前端一般还须要再发出一个GET请求,来拉取变动后的数据。若是在返回结果中,为前端的显示界面而增长了一部分数据,又会破坏后端代码的可复用性。而在GraphQL的Mutation中,返回的数据彻底能够根据前端界面的数据需求来决定,并且只需一次请求便可。结合Relay框架,还能够定义理想化更新来减小Loading界面出现的次数,进一步提升用户体验。(这一点会在下一节具体展开)
在当下流行的先后端分离开发模式下,先后端开发者的沟通成本也是影响项目进度的一大要素。一般,为了下降沟通成本,后端开发者须要提早定义API文档,前端会根据API文档来MOCK数据以开发前端界面。后端开发者还须要经过各类工具,如PostMan来进行测试。在先后端完成开发后,还须要作联调对接。
对于GraphQL来讲,schema自身就是很好的文档。同时,官方还提供了一个相似于PostMan的工具GraphiQL,能够有助于开发中的调试。
GraphQL与Relay框架结合后,还能发挥出更大优点,好比先后端一致的类型校验、前端缓存等,具体请看下一节内容。
Relay是一套基于GraphQL和React的框架,它将这二者结合,在原来React组件的基础上,进一步将请求封装进组件。
官方提供了一个TodoMVC的demo能够参考,基本涵盖了CRUD操做。
Relay框架提供了QueryRenderer这样一个高阶组件(HOC)来封装React组件和GraphQL请求。这个组件接受四个Props:environment、query、variables以及render。 environment须要配置网络请求和Store;query接受的即是GraphQL请求;variables接受GraphQL请求中须要的变量,最后render用来定义组件的渲染。
假设咱们要开发一个显示员工基本信息的Relay组件,那么它可能会是这样的:
<QueryRenderer environment={environment} query={graphql` query StaffQuery($id: ID!) { 员工(ID: $id) { ID 姓名 职位 } } `} variables={{ id: '011' }} render={({error, props}) => { if (error) { return <div>{error.message}</div>; } else if (props) { return <div>工号:{props["员工"]["ID"]};姓名:{props["员工"]["姓名"]};职位:{props["员工"]["职位"]};</div>; } return <div>Loading...</div>; } } /> 复制代码
如今咱们已有了一个展现员工基本信息的组件,若是咱们如今要在这个组件的基础上,进一步封装出一个员工列表的组件,该怎么办呢?
参照React组件的方式,能够建立一个新的组件,接收一个包含员工ID数组的props,在这个新的组件内部,根据ID数组Map多个员工信息的Relay组件。
这样彷佛能够,但问题是,若是有10个ID,那这样一个组件也就会发出10个GraphQL请求,显然违背了GraphQL的设计理念。
固然也能够建立一个新的Relay组件:query中直接请求一组员工数据,渲染出列表。但这样就失去了组件的复用性,由于很显然,这个新组件中,显示每条员工信息的逻辑和样式,跟单个员工信息的组件是一致的。
这里,Relay提供了一个Fragment的HOC组件,它接受两个Props:component和fragmentSpec。
component接受React组件,用来处理具体的组件视图和逻辑;fragmentSpec则是接受一段GraphQL Fragment。所谓Fragment,对应到上一节的图中,就是节点的某一部分。好比:
fragment 员工信息 on 员工 {
ID
姓名
职位
}
复制代码
在请求中,就能够这样引入Fragment:
{ 员工(ID: "022") { ...员工信息 } } 复制代码
那么回到Relay中,能够这样建立一个员工信息的Fragment组件:
createFragmentContainer( class 员工信息 extends React.Component { render() { return <div>工号:{this.props.data["员工"]["ID"]};姓名:{this.props.data["员工"]["姓名"]};职位:{this.props.data["员工"]["职位"]};</div>; } }, graphql` fragment 员工信息 on 员工 { ID 姓名 职位 } `, ) 复制代码
有了这样一个员工信息的Fragment组件后,咱们能够再建立员工信息列表的组件:
<QueryRenderer environment={environment} query={graphql` query StaffListQuery($ids: [ID]!) { 员工(IDs: $ids) { ...员工信息 } } `} variables={{ id: ['011', '022', '033'] }} render={({error, props}) => { if (error) { return <div>{error.message}</div>; } else if (props) { return <员工信息 data={this.props.data} />; } return <div>Loading...</div>; } } /> 复制代码
这样一来,组件实际的请求仍是只有一条,但员工信息的组件获得了成功复用,若是在其余组件中,须要显示员工信息,也一样只须要将该Fragment组件引入便可。
除了基本的Fragment Container,Relay还提供了Refetch Container和Pagination Container组件,前者在原Fragment组件的基础上,注入了refetch方法,以便知足组件须要更新数据的场景(如:用户主动点击数据列表的刷新按钮);然后者,则添加了若干分页的操做,这里就不具体展开了。
在QueryRenderer中配置的environment里面,主要包含的是网络请求和Store。这里的Store于Redux的Store不太一致。Redux中主要用来统一管理组件的State,而Relay Store则记录的是Record。这里的Record,其实就是GraphQL的每一个Type,或者对应于上一节的图中的每一个节点。
当Relay框架收到GraphQL返回的数据后,会为每个节点数据记录一个ID,并在Relay Store中存为一个Record。同时,Relay也为这些Record提供了CRUD的方法。具体能够参考官方文档。
为了便于在组件中发起GraphQL Mutation操做,Relay提供了commitMutation方法。除了发起Mutation以外,利用Relay Store,能够方便的定位页面数据并进行更新,还可以实现理想更新,进一步提高用户体验。
到这里,基本已经涵盖了Relay的所有功能。从上文能够看出,在GraphQL原有的优点基础上,Relay还带来了如下两点优点:
除此以外,结合Flow框架的类型检测,Relay能够很好地根据后端提供的schema作类型校验,避免一些潜在的Bug。
经过以上的介绍和分析,相信你对GraphQL与Relay已经有了大体的了解,我认为,Relay比较适合的场景,是那种前端数据展现类别众多,且变化较大的应用,好比社交网站。但具体是否在项目中应用,仍是须要结合需求实际来决定。