GraphQL 概述: 使用React前端框架创建一个 to-Do List API

原文出处 GraphQL Overview: Build a to-Do List API with a React Front-End — SitePoint

设想你想要参考食谱烤一个蛋糕。你将需要一些原料,并且一些合适的量。如果你能拿一个盒子装好你烘焙所需要的各种原料 ,并且已经称量好匹配菜谱的份量,那肯定会让烘焙更简单。如果你把前端 UI设想成一块蛋糕的话,那这就是GraphQL所做的事。

在本教程中我们将写一个小的GraphQL server 来响应Todo List app的请求。你也可以 在众多app中来挑选,但是这些日子我开始使用React做项目,所以我将会选择React来做前端框架。不过,你也可以挑选你用得习惯的任何js框架。

GraphQL

GraphQL 允许我们定义 一个查询来提供一个通用的接口在客户端和服务端之间来请求和处理数据。它用一种查询语言来处理,允许客户端使用一种直观和灵活的语法和来按照客户端程序的设计和需求来构建和组装数据。

这使客户端从服务端 检索数据更加的高效。举个栗子,设想从一个 GraphQL的实例中, 客户端除了title和id 其它的字段一概不要,那么这个模型应该是长这样:

query Query {
  todos {
    id,
    title
  }
}

结果数据(JSON)是:

Which produces the resulting data (in JSON):

{
  "data": {
    "todos": [
      {
        "id": 1446412739542,
        "title": "Read emails"
      },
      {
        "id": 1446412740883,
        "title": "Buy orange"
      },
      {
        "id": 1446412741215,
        "title": "Fix garbage"
      }
    ]
  }
}

大概我们的展示demo中没有保存数据。这背后的原因是每次我们都启动服务,在内存中存储的Todo(s)数组变为空了。我们将在下面的内容中展示如何向数组中添加数据。

如你所见,返回的格式已经被替换成了客户端已经定义和描述过了的查询格式。就像文章“GraphQL 概述 – GraphQL 和 Node.js 入门 ”中规定的。

GraphQL的查询都像是没有属性的JSON对象,GraphQL 不是一种语言特性 这点被提到 很重要,它只是在客户端和服务端中间的一种规范。如果使用通用的语言,任何的客户端都能和任何服务端通信。

介绍 GraphQL.js

GraphQL.js 是一种基于js的GraphQL参考模型,它提供了两个重要的功能:

  1. 创建一种类型的语法模型(schema)。
  2. 应该该类型的语法(schema)的查询 。

需要创建一个匹配代码基层的GraphQL 类型语法(schema)。在接下来的代码中,我们定义一个简单的语法(schema)。它有一种类型和一个汇总的Todo(s)列表(每个列表元素有含有三个字段),额外的,它还提供了服务于该类型语法(schema)和查询结果。

var graphql = require ('graphql');

// Here is some dummy data to make this piece of code simpler.
// It will be changeable after introducing mutation.
var TODOs = [
  {
    "id": 1446412739542,
    "title": "Read emails",
    "completed": false
  },
  {
    "id": 1446412740883,
    "title": "Buy orange",
    "completed": true
  }
];

var TodoType = new graphql.GraphQLObjectType({
  name: 'todo',
  fields: function () {
    return {
      id: {
        type: graphql.GraphQLInt
      },
      title: {
        type: graphql.GraphQLString
      },
      completed: {
        type: graphql.GraphQLBoolean
      }
    }
  }
});

var queryType = new graphql.GraphQLObjectType({
  name: 'Query',
  fields: function () {
    return {
      todos: {
        type: new graphql.GraphQLList(TodoType),
        resolve: function () {
          return TODOs;
        }
      }
    }
  }
});

module.exports = new graphql.GraphQLSchema({
  query: queryType
});

让我们现在看一下给我们一个JSON数据结果的js文件的代码:

var graphql = require ('graphql').graphql
var express = require('express')
var graphQLHTTP = require('express-graphql')
var Schema = require('./schema')
var query = 'query { todos { id, title, completed } }'

graphql(Schema, query).then( function(result) {
  console.log(JSON.stringify(result));
  // Prints
  // {
  //   "data":{
  //     "todos":[
  //       {
  //         "id":1446412739542,
  //         "title":"Read emails",
  //         "completed":false
  //       },
  //       {
  //         "id":1446412740883,
  //         "title":"Buy orange",
  //         "completed":true
  //       }
  //     ]
  //   }
  // }
});

var app = express()
  .use('/', graphQLHTTP({ schema: Schema, pretty: true }))
  .listen(8080, function (err) {
    console.log('GraphQL Server is now running on localhost:8080');
  });

下面的代码提供了跟上面同样 的执行结果,cURL在本例中并非是强制执行来得到 更长远的优势 的。它只是一种不用在浏览器中击中例子 来检索数据的方式 。请注意万一你是一个Windows 用户, 你可以使用Windows的命令提示符来执行cURL例子,此外, 这里你还可以找到好的资源来在你的系统里安装cURL.

$ curl -XPOST -H "Content-Type:application/graphql"  -d 'query { todos { title } }' http://localhost:8080
{
  "data": {
    "todos": [
      {
        "title": "Read emails"
      },
      {
        "title": "Buy orange"
      }
    ]
  }
}

关于语法(schema)的一个重要的事情 ,自从它描述了用户可以使用的API,它就假定数据已经存储 了。数据存储和描述的方式是一种实现细节。

React

React 是由Facebook和Instagram来开发 的一种用来创建用户界面 JavaScript 库。很多人会认为React是MVC模型中的V, 官方文档中是这样规定的:

我们做出React是为了解决一个问题:创建大型应用时,加载数据超时。根源在于构建可重用的组件。实际上,本质就是构建组件库。

如果你需要一个React指南,你可以阅读下面的资料:

一个简单的React 组件

React 组件通过render()方法来获取输入数据并将返回结果渲染展示。这里是使用JSX(跟XML语法相似)的例子. JSX是一个非必须项。JSX是一种更像是XML的JavaScript 语法扩展,你可以使用React将简单的JSX语法转化。

输入数据可以通过this.props来向render()渲染的组件传值。下面是关于如何创建一个React 组件的简单的例子 并且在 CodePen中可用.

var Application = React.createClass({
  render: function() {
    return 
      { this.props.text }
      { this.props.id }
    ;
  }
});

和适当的这些预编译的代码,这些未编译过的JavaScript代码由JSX编译器生成。

"use strict";
var Application = React.createClass({
  displayName: "Application",
  render: function render() {
    return React.createElement(
      "div",
      null,
      this.props.text,
      this.props.id
    );
  }
});

一个简单的React组件

如果你想探究更多关于React 组件,可以花一分钟 看一下这个视频关于组件状态的介绍.

一次关于本例子的彩排

首先,我的们需要 一个服务端 (运行正常的)来接收我们从Todo List应用发出的GraphQL请求。这个服务端已经在上面写好了。

开启我们的服务,在命令行中执行:

$ git clone https://github.com/sitepoint-editors/todo-graphql-server.git
$ cd todo-graphql-server
$ npm install
$ npm start

本地启服务

你必须已经安装Node v4.0.0以其更高的版本,因为 服务端的代码 使用了在老版本中并不支持的ES2015 特性

任何以/graphql结尾的POST请求 将会与我们的GraphQL语法(schema)发生执行冲突。测试一下是否正常运行,输入以下 代码 :

$ curl -XPOST -H "Content-Type:application/graphql"  -d 'query { todos { title } }' http://localhost:8080
{
  "data": {
    "todos": []
  }
}

还是没有数据保存。所以我们每次重启服务,在内存中的存储了todo(s) 数组数据都会被清空。当然 ,我们不单单想只读空数组,我们还需要添加和更新数据。这种接收异常类型的操作,在GraphQL中被 称作修改mutations),定义一个修改(mutations)跟定义一个查询一样,也会返回一个类型的的值。这个想法是无论什么变量发生了变化 ,就返回什么。

var MutationAdd = {
  type: new GraphQLList(TodoType),
  description: 'Add a Todo',
  args: {
    title: {
      name: 'Todo title',
      type: new GraphQLNonNull(GraphQLString)
    }
  },
  resolve: (root, {title}) => {
    TODOs.push({
      id: (new Date()).getTime(),
      title: title,
      completed: false
    });
    return TODOs;
  }
};

var MutationType = new GraphQLObjectType({
  name: 'Mutation',
  fields: {
    add: MutationAdd
  }
});

export var Schema = new GraphQLSchema({
  query: QueryType,
  mutation: MutationType
});

上面的箭头符号 (=>) 参考定义函数的新语法, ES2015中最有趣的部分之一。

正如Clay Allsopp所写的这篇题为“Your First GraphQL Server” 文章中所写,

修改(mutation)与查询之间的一个有意义的区别是转换是串行的,但是查询没有这样的规定(实际上,GraphQL鼓励服务端为独立查询开发固有的并行模型)。GraphQL说明书提供 了这个关于修改(mutation)查询的一个集合例子必须按照下面的顺序在服务端执行:

{
  first: changeTheNumber(newNumber: 1) {
    theNumber
  },
  second: changeTheNumber(newNumber: 3) {
    theNumber
  },
  third: changeTheNumber(newNumber: 2) {
    theNumber
  }
}

因此,在请求结束, theNumber 字段值一定是2。在这个快速的修改(mutation)的介绍之后,我们可以最终在服务端添加一个todo

$ curl -XPOST -H "Content-Type:application/graphql" -d 'mutation { add (title: "Clean garage") { id, title } }' http://localhost:8080
{
  "data": {
    "add": [
      {
        "id": 1446443172937,
        "title": "Clean garage"
      }
    ]
  }
}

服务端运行

是不是相当的酷?我们除了这个添加(add)修改还有更多的修改(mutation):toggle, toggleAll, destroy, clearCompleted,和save。一个值得注意的事是我们在所有的修改中传递参数,所以有的字段都 可接收参数。追加参数是相当简单,并且它们都可以被resolve函数捕获。

今天的结尾,我们有两种类型的查询:

  • 一种是从服务端取数据(get);
  • 另一种是操作创建, 更新, 删除create, update, delete)数据。

服务正常运行后,我们已经准备好来用我们基于React的Todo List了。 React TodoMVC例子一个分支就像本文一开始提到 的那样,下载,并执行:

$ git clone -b react-graphql https://github.com/sitepoint-editors/todomvc.git
$ cd todomvc
$ npm install
$ node server.js

浏览器中输入地址http://localhost:3000来运行应用。这个代码对比之前的版本有两个主要的变更。首先,服务端的TodoModel已经被修改了。

react component model

第二,我们本地创建了一个服务端代理来直接使用GraphQL 请求我们创建的服务。 更多的细节 ,查看下面的图片。

server proxy

而且,你能 在这 找到一个demo。

graphql overview

总结

如你在本文所看到 的,GraphQL和GraphQL.js是Facebook在2015年最新发布的开源技术 ,它核心的想法是 UI知道组件需要渲染的数据的详细集合。

Igor Ribeiro Lima

作者

Igor Ribeiro Lima