一篇文章帮你理清GraphQL的核心概念(译)

一年多之前就据说了GraphQL,前段时间接触了一个海归团队(创始人就来自Facebook),技术栈使用Graphql+Apollo+React,在他们的指导下试用了一下以为真心酷。遂花了半个多月了解了GraphQL的主要思想和基本用法,碰巧看到一篇文章)把GraphQL的核心概念讲的比较清晰易懂,依据该文,大体翻译以下,若是你也对GraphQL感兴趣,欢迎一块儿来讨论。node

什么是GraphQL:git

给API设计的一种查询语言,一个依据已有数据执行查询的运行时,为你的API中的数据提供一种彻底且容易理解的描述,使得API可以更容易的随着时间而演变,还支持强大的开发者工具。github

虽然名字叫作GraphQL 可是和数据库自己并无直接关系。数据库

GraphQL的特征:express

  • 可描述性的:使用GraphQL,你获取的都是你想要的数据,很少也不会少;
  • 分级性的:GraphQL自然遵循对象间的关系,经过一个简单的请求,咱们能够获取到一个对象及其相关的对象,好比说,经过一个简单的请求,咱们能够获取一个做者和他建立的全部文章,而后能够获取文章的全部评论;
  • 强类型的:使用GraphQL的类型系统,咱们能够描述可以被服务器查询的可能的数据,而后确保从服务器获取到的数据和咱们查询的一致;
  • 不作语言限制:并不绑定于某一特定的语言,实际上如今已经有一些不一样的语言有了实践;
  • 兼容于任何后台:GraphQL不限于某一特定数据库,可使用已经存在的数据,代码,甚至能够链接第三方的APIs.
  • 好检讨的:GraphQL服务器可以查询架构的细节。

GraphQL的核心包括Query,Mutation,Schemas等等,每一个概念下又有一些子概念,下面分别作简单的介绍:npm

Query

Queries用作读操做,也就是从服务器获取数据。Queries定义了咱们对模式执行的行为。下面是一个简单的查询及相应的结果:json

// Basic GraphQL Query

    {
      author {
        name
        posts {
          title
        }
      }
    }
// Basic GraphQL Query Response

    {
      "data": {
        "author": {
          "name": "Chimezie Enyinnaya",
          "posts": [
            {
              "title": "How to build a collaborative note app using Laravel"
            },
            {
              "title": "Event-Driven Laravel Applications"
            }
          ]
        }
      }
    }

查询和响应具有相同的结构。浏览器

对query结果的解释

若是一个操做没有type,GraphQL默认会把这些操做看作queryquery还能够拥有名字,虽然是可选的,可是能够帮助识别某个query是作什么的。bash

query也能够拥有注释,注释以#开头。服务器

Field

Field是咱们想从服务器获取的对象的基本组成部分。上述代码中name就是author对象的一个Field.

Argument

和普通的函数同样,query能够拥有参数,参数是可选的或需求的。参数使用方法以下:

{
  author(id: 5) {
    name
  }
}

须要注意的是,GraphQL中的字符串须要包装在双引号中。

Variables

除了参数,query还容许你使用变量来让参数可动态变化,变量以$开头书写,使用方式以下:

query GetAuthor($authorID: Int!) {
      author(id: $authorID) {
        name
      }
    }

参数能够拥有默认值:

query GetAuthor($authorID: Int! = 5) {
      author(id: $authorID) {
        name
      }
    }

参数也能够是可选的或必须的,好比上述的$authorID变量是必须的,它的定义中包含了!。详细信息可见schema中。

Allases

别名,好比说,咱们想分别获取ID5和7,咱们能够用下面的方法:

{
      author(id: 5) {
        name
      }
      author(id: 7) {
        name
      }
    }

因为存在相同的name,上述代码会报错,要解决这个问题就要用到别名了Allases

{
      chimezie: author(id: 5) {
        name
      }
      neo: author(id: 7) {
        name
      }
    }

获取的结果以下:

# Response

    {
      "data": {
        "chimezie": {
          "name": "Chimezie Enyinnaya"
        },
        "neo": {
          "name": "Neo Ighodaro"
        }
      }
    }

Fragments

Fragments是一套在queries中可复用的fields。好比说咱们想获取twitterHandlefield,咱们能够按下面这样作:

{
      chimezie: author(id: 5) {
        name
        twitterHandle
      }
      neo: author(id: 7) {
        name
        twitterHandle
      }
    }

可是若是fields过多,就会显得重复和冗余。Fragments在此时就能够起做用了。如下是使用Fragments的语法:

{
      chimezie: author(id: 5) {
        ...authorDetails
      }
      neo: author(id: 7) {
        ...authorDetails
      }
    }

    fragment authorDetails on Author {
      name
      twitterHandle
    }

Directives

Directives提供了一种动态使用变量改变咱们的queries的方法。如本例,咱们会用到如下两个directive:

  • @include:只有当if中的参数为true时,才会包含对应fragmentfield
  • @skip:当if中的参数为true时,会跳过对应fragmentfield

这两个directive都接受一个布尔值做为参数;

实例以下:

query GetAuthor($authorID: Int!, $notOnTwitter: Boolean!, $hasPosts: Post) {
      author(id: $authorID) {
        name
        twitterHandle @skip(if: $notOnTwitter)
        posts @include(if: $hasPosts) {
          title
        }
      }
    }

Mutation

传统的API使用场景中,咱们会有须要修改服务器上数据的场景,mutations就是应这种场景而生。mutations被用以执行写操做,经过mutations咱们会给服务器发送请求来修改和更新数据,而且会接收到包含更新数据的反馈。mutationsqueries具备相似的语法,仅有些许的差异。

mutation UpdateAuthorDetails($authorID: Int!, $twitterHandle: String!) {
      updateAuthor(id: $authorID, twitterHandle: $twitterHandle) {
        twitterHandle
      }
    }

咱们在mutation中以数据为载物发送,好比上面的例子中,咱们发送了下面的数据:

# Update data

    {
     "authorID": 5,
     "twitterHandle": "ammezie"
    }

更新完成后,咱们将从服务器获取如下内容做为反馈。

# Response after update

    {
      "data": {
        "id": 5,
        "twitterHandle": "ammezie"
      }
    }

咱们能够看出,反馈数据中包含咱们更新的数据

queries相似,mutations也可以接受,多重fields,不过queriesmutations的一个重大不一样之处在于,为了保证数据的完整性mutations是串形执行,而queries能够并行执行。

Schemas

Schemas 描述了 数据的组织形态 以及服务器上的那些数据可以被查询,Schemas提供了你数据中可用的数据的对象类型,GraphQL中的对象是强类型的,所以schema中定义的全部的对象必须具有类型。类型容许GraphQL服务器肯定查询是否有效或者是否在运行时。Schemas可用是两种类型QueryMutation

Schemas用GraphQL schemas语言构建,它和咱们前面已经学过的query很是相似,下面是一个示例:

type Author {
      name: String!
      posts: [Post]
    }

上面的schemas定义了一个Author对象,它包含两个fields(nameposts),这意味着当咱们操做(读取)Author时,咱们只能使用namefields,每一个field均可以是必须的或者可选的,好比上面的namefield是必须的,由于其后有符号!,而posts是可选的。

Arguments

Schemas中的Fields 也能够接收参数,这些参数能够是可选的或者必须的,必须的参数经过!识别。

type Post {
      allowComments(comments: Boolean!)
    }

标量类型

顺便提一下,GraphQL支持如下标量类型:

  • Int: 带符号的32位整数
  • Float: 带符号的双精度浮点数
  • String: UTF-8 字符串
  • Boolean: true or false
  • ID: 惟一标识符

由上述类型定义的fields 不能 再拥有本身的 fields,咱们可使用scalar关键字,自定义标量类型,好比咱们能够定义一个Date类型:

scalar Date

枚举类型

又称Enums,这是一种特殊的标量类型,经过此类型,咱们能够限制值为一组特殊的值,好比,咱们能够:

  • 保证此类型的任何参数都是容许值之一;
  • 经过类型系统进行通讯,该字段始终是有限值集之一。

Enums经过关键字enum进行定义:

enum Media {
      Image
      Video
    }

输入类型

input类型对mutations来讲很是重要,在 GraphQL schema 语言中,它看起来和常规的对象类型很是相似,可是咱们使用关键字input而非type,input类型按以下定义:

# Input Type

    input CommentInput {
      body: String!
    }

开始实践

咱们将使用node.js建设一个简单的任务管理GraphQL serve,这个例子很是简单,可是足以用到咱们学过的大部分概念,巩固咱们的学习成果。

构建Node.js服务器

咱们使用Express作为咱们的node.js框架,首先咱们须要初始化一个node.js项目,使用如下命令:

mkdir graphql-tasks-server
cd graphql-tasks-server
npm init -y
npm install express body-parser apollo-server-express graphql graphql-tools lodash --save

建立以下文件及目录

  • /src/
  • /src/data/
  • /src/data/data.js:
  • /src/schema/
  • /src/schema/index.js
  • /src/schema/resolvers.js
  • /src/server.js
// src/server.js

    const express = require('express');
    const bodyParser = require('body-parser');
    const { graphqlExpress, graphiqlExpress } = require('apollo-server-express');
    const schema = require('./schema/index');

    const PORT = 3000;

    const app = express();

    // Graphql 用以构建Graph服务器
    app.use('/graphql', bodyParser.json(), graphqlExpress({ schema }));

    // Graphiql 用以展现查询客户端
    app.use('/graphiql', graphiqlExpress({ endpointURL: 'graphql' }));

    app.listen(PORT, () => console.log(`GraphiQL is running on http://localhost:${PORT}/graphiql`));

构建Schemas

// src/schema/index.js

    const { makeExecutableSchema } = require('graphql-tools');
    const resolvers = require('./resolvers');

    const typeDefs = `
        type Project {
            id: Int!
            name: String!
            tasks: [Task]
        }
        type Task {
            id: Int!
            title: String!
            project: Project
            completed: Boolean!
        }
        type Query {
            projectByName(name: String!): Project
            fetchTasks: [Task]
            getTask(id: Int!): Task
        }
        type Mutation {
            markAsCompleted(taskID: Int!): Task
        }
    `;

    module.exports = makeExecutableSchema({ typeDefs, resolvers });

咱们为咱们的app定义了schema,咱们定义了两种类型,projectstasks,type task中包含咱们要完成的任务,而type Project中包含三项id,nametasksidname是必备fields,tasks则是一系列Task类型的组合,task type包含4项,id, title, projectcompletedidtitle是必须的,project指明了属于那个项目,而completed代表了其完成状况。

一个项目能够包含多个任务,而一个任务必属于一个项目。

接下来,咱们定义了一系列查询,

  • projectByName:用以经过传入的name参数来返回对应的Project;
  • fetchTasks:用以获取全部的任务并返回;
  • getTasks:依据传入的id返回特定的任务;

咱们也定义了一些Mutation:

  • markAsCompleted:接受一个id作为参数,并返回修改完成状态后的Task

Writing Resolvers

resolver是决定schemas中的field该如何执行的函数。

Tip: GraphQL resolvers 能够返回Promises

// src/schema/resolvers.js

    const _ = require('lodash');

    // Sample data
    const { projects, tasks } = require('./../data/data');

    const resolvers = {
        Query: {
            // Get a project by name
            projectByName: (root, { name }) => _.find(projects, { name: name }),

            // Fetch all tasks
            fetchTasks: () => tasks,

            // Get a task by ID
            getTask: (root, { id }) => _.find(tasks, { id: id }),

        },
        Mutation: {
            // Mark a task as completed
            markAsCompleted: (root, { taskID }) => {
                const task = _.find(tasks, { id: taskID });

                // Throw error if the task doesn't exist
                if (!task) {
                    throw new Error(`Couldn't find the task with id ${taskID}`);
                }

                // Throw error if task is already completed
                if (task.completed === true) {
                    throw new Error(`Task with id ${taskID} is already completed`);
                }

                task.completed = true;

                return task;
            }
        },
        Project: {
            tasks: (project) => _.filter(tasks, { projectID: project.id })
        },
        Task: {
            project: (task) => _.find(projects, { id: task.projectID })
        }
    };

    module.exports = resolvers;

咱们对Schemas中定义的各项添加了处理函数。

添加数据

// src/data/data.js

    const projects = [
        { id: 1, name: 'Learn React Native' },
        { id: 2, name: 'Workout' },
    ];

    const tasks = [
        { id: 1, title: 'Install Node', completed: true, projectID: 1 },
        { id: 2, title: 'Install React Native CLI:', completed: false, projectID: 1 },
        { id: 3, title: 'Install Xcode', completed: false, projectID: 1 },
        { id: 4, title: 'Morning Jog', completed: true, projectID: 2 },
        { id: 5, title: 'Visit the gym', completed: false, projectID: 2 }
    ];

    module.exports = { projects, tasks };

启动服务器并测试

node src/server.js

在浏览器中打开,http://localhost:3000/graphiql,输入查询便可看到结果。

IMAGE

一些有用的连接

相关文章
相关标签/搜索