一篇文章幫你理清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

一些有用的連接

相關文章
相關標籤/搜索