从前端开发人员的角度来看,GraphQL 是一个数据层范式,它支持乐观更新、React 组件旁边的声明性数据获取和所见即所得数据。 它由 Facebook 推广,它提供的不仅仅是 RESTful API。 本文附带一个来自生产环境的示例,展示了如何在不影响后端开发人员的情况下将现有的 RESTful API 包装到前端 GraphQL API 中。
在我从外包商那里接手的一个前端项目中,我看到了很多有趣的遗物,比如在页面之间复制和粘贴的无用导入,以及许多带有 .bak 但是一个与另一个页面没有太大区别的旧页面,孩子般的组件命名等。 API 的使用尤其令人印象深刻,如下所示:
import * as mapi from '../../lib/mapi';// ..省略不相关的** componentwillmount() mapipie() then(json => )mapi.whoami() then(json => )
它看起来有点臃肿,因为里面有很多重复**。
这看起来有点令人困惑,因为我们看不到 piedata 中的内容。 如果你有更深的层次,很容易脱身cannot read property 'machine' of undefined
或undefined is not an object
,尤其是当您还使用 ES6 计算属性值时,调试时间会变得有点长。
还有很多其他问题,例如必须对 UI 中的数据进行非 null 检查。
这里的问题是,我们将数据置于一种状态,该状态在 null 时不提供类型检查和默认值,例如 props。
虽然使用 Redux 这样的数据流框架可以解决大部分问题,但由于现在是重构的时候了,重要的是让这个重构迭代尽可能长地存在——解决干扰开发人员理解的大问题。 我最感兴趣的是数据对我的可见性:
我希望在编写 UI 时能够看到数据的样子,最好数据看起来与我的 UI 结构完全一样,这样将数据绑定到 UI 的过程就变得简单愉快了。 」
现状是,每次绑定数据都要用邮递员请求后端看看返回的数据是什么样子的,然后调用这个API,并从返回的数据中取出一些我需要的数据绑定到UI上,很弱三千取一勺。由于业务变化,我必须同时在一个 UI 组件中使用多个 API。
我喜欢在常用词中将字母大写,例如:deviceid
,而后端叔叔喜欢小写,例如dviceid
。哦,我的上帝,他在拼写时错过了一个 e!
理想情况下,后端应该快速跟进业务更改,拆分 API,然后翻阅婴儿名称列表,为每个 API 提供一个新的有意义的名称。 但显然后端不会放下手头的很多东西来配合你改造API,毕竟数据为什么要跟着UI长一张脸,你觉得你的UI很帅吗? 此外,如果三天后需求再次发生变化怎么办? 让后端再次重写其余的端点,他会用他的皮大衣鞭打你。
同时使用 Redux 和 GraphQL 可以解决这些问题。
过去在REST中,我们根据业务将数据划分为不同的路径,相当于根据第一个版本的业务需求,给每一堆数据一个当时容易理解的名称,然后希望以后每个业务都使用一个
古代外包人员的方法export function whoami() export function pie() export function entry() export function districtpie(id) pie );export function siteoverview(id) /overview`);export function sitepie(id) /pie`);export function cabinetsswitches(id) /cabinets/switches`);export function sitewarningquantity(id) `
在 GraphQL 中,我们将数据表示为一棵树,并且可以直接请求它的任何叶子。
与REST类似,我们也可以有易于理解的名称,但是我们可以直接观察更小粒度的数据,而不是幻想路径中隐藏了哪些数据,如下所示:
重构的 API 帐户 WhoamiType UserType API 信息条目 API 数据站点概述 战区信息,指示地球上有哪些据点或子战区,并且战区有自己的**比例饼图数据 它还可以表示据点的数据,其中可以包括比例饼图和运输线列表等数据 类型 PowerentityType
我们可以在注释中看到每个数据类型与原始REST端点之间的关系,一个数据类型可以从多个原始RESTful数据源拼凑而成(例如集成多个微服务),或者一个REST数据源可以根据常识重组成多个数据类型,这样三个月后重读时就可以理解了。
这种转换有两个直接的好处:首先,您可以直接看到数据源的样子! 不再有邮递员请求抓取数据,因为你不记得每个数据终结点上有哪些字段可用。 二是它提供了静态类型检查,所以你可以在写类型的时候考虑每个类型应该是什么样子,然后把检查数据可用性的工作放在一边,专注于业务......
还有两个间接的好处:一个是她写得好像在写 FlowType 或者 TypeScript,你现在正在给数据添加类型注解,几个月后你开始学习 Rust 的时候,你会有宾至如归的感觉,你可以在 21 分钟内从初学者变成精通。 第二个是可以对数据端点进行标注,可以使用?? 黑色问号运算符声明性地表达你对业务的困惑,也可以与 ! 划船运算符描述数据项的不可或缺性。
我们上面用来声明类型的语言叫做 GraphQL,GitHub 正在将他们的 API 迁移到 GraphQL,所以你可以检查它是如何编写的。 它通常以模式编写js 文件,如下所示:
export const typedefinitions = `schema # ..省略其他类型的声明
它写在多行文本第一行内的第一行上,以便更容易查看调试的行号。 让我们将其导出为常量类型定义。
写完数据类型声明后,我们还需要写架构JS 继续解释如何获取这些数据类型中的每一种以及它们对应的数据。 我们使用分析函数来做到这一点:
// schema.jsexport const resolvers = , args, context) ,username(, args, context) ,password(, args, context) ,token(, args, context) ,id(, args, context) ,name(, args, context) ,companyid(, args, context) ,companyname(, args, context) ,departmentid(, args, context) ,departmentname(, args, context) ,role(, args, context) ,// ..省略其他分析功能,**以实物为准};
正如我们所看到的,我们实际上为每个我们可能需要的字段提供了一个函数作为数据源,当我们在 UI 中需要几个字段时,我们发出的请求会在这里触发一些函数,从而只返回我们需要的字段对应的数据,然后数据会被类型检查并返回到我们发出请求的 UI。
这可能就是在 UI 中使用它的感觉:
function mapstatetoprops(state) ;const mainpagedata = gql` query mainpagedata($token: string!) const queryconfig = ) => ( 我们配置 GraphQL hoc 在其 props pollinterval: 10000, }props: (=> (connect(mapstatetoprops) Redux classic usage@graphql(MainPageData, QueryConfig) graphql hocexport 默认类 main extends 组件; static defaultprops = , render()
事实上,graphql 数据端点也是 schemajs 中的数据类型,以及解析函数,应该是后端叔叔写的。 不过,后台大叔有家庭,有自己的生活,你不应该为了你更开心亲自写UI而要求他放弃生活中的很多美好事物,我们不能这么残忍。
更好的方法是插入一个简单的 GraphQL 数据端点作为 UI 层和数据层之间的中介。
在这个中介中,我们可以将原本一次性返回一大块数据的 API 拆分为细粒度的数据类型,可以纠正后端返回的数据中的错别字,可以检查数据是否为空,我们来看一个例子:
// rest2graphqlinterface.以下大部分都是js中的例程,你只需要修改部分模型导入apolloclient,从'apollo-client';import from 'graphql';import from 'lodash';import from './schemagenerator';import from './schema';import from './models';修改此部分 import houtaidashuconnector from'./houtaidashuconnector';const typedefs = [.rootschema];const resolvers = merge(rootresolvers);const executableschema = makeexecutableschema();const serverconnector = new houtaidashuconnector();const rest2graphqlinterface = ),其中你引入了自己的连接器 },graphqlrequestvariables );const client = new apolloclient();export default client;
然后将 Redux 提供程序替换为仅限 GraphQL 成员的 ApolloProvider:
从。
成为。
这样一来,获取数据的链就大致串在一起了,你会发现数据是这样的:
舅舅后端的 RESTful API ->连接器 ->模型 ->解析器函数 -ExecutableSchema ->rest2graphqlinterface ->ApolloClient ->redux -> UI 的道具 -> UI
正如我们之前所看到的,当我们创建rest2graphqlinterface时,我们写道:
user: new user(),
其中 user 是模型。
为什么会这样写?
其实所有的数据采集,也就是请求给后台大叔的RESTful API,我们可以把它放在分析函数里,但是我们会写很多重复的**,而且每一点数据我们就要抓一次,这是浪费资源,一个弱水三千拿一勺喝的概念。
因此,我们抽象出一层模型:
// models.jsexport class user ) async getloginstatus(token) catch (error) async getallmetadata(token) getmetadata(field, token) }
如果我们希望模型专注于数据缓存和数据清理,我们必须将网络请求的部分抽象为连接器:
export default class houtaidashuconnector token=$`;return promise.try(()=> fetch(`$/$$`then(checkstatus) .then(response => response.json())then(json => return json.data; }
这样一来,我们就可以通过切换连接器,轻松切换我们是连接到内网设置的测试服务器,还是连接到外网的生产服务器,而不会影响模型和架构中的逻辑(当然,其实还是会受到影响的,因为你连接了测试服务器进行更新**以适应最新的业务)。
从那里,我们在客户端设置了一个轻量级的 GraphQL 服务器,作为我们和后端之间的中介。 基于进程内测试,这对性能没有明显影响,良好的缓存逻辑甚至可以加快页面加载速度。
使用此技术后,数据的采集不会写入componentwillmount()
它不是隐藏在 redux reducer 中,不是隐藏在 redux-saga 生成器中,而是真正深入草根,就在我们的 UI 旁边,爱房子和黑,一石二鸟,所见即所得,腰部柔软易推。
这种客户端-graphql-server的方式也非常适合控制物联网设备,毕竟在物联网设备中设置server-side-graphql-server是不切实际的,使用RESTful API编写物联网控制会比较臃肿,在客户端将这些数据转换为graphql是一种可行的方法。
如果您对内容有任何疑问,可以添加China GraphQL用户组302490951进行交流。 比如有个同志看到我之前写过关于接力的教程,就问我接力好不好,我什么都知道:不好用,所以现在改用apollostack
wrapping a rest api in graphql apollo-client issue #