react+graphql起手和特性介紹(一)

這篇文章介紹一下如何搭建一個簡單的graphql服務,graphql的特性以及如何在react中使用graphql,graphql給項目開發所帶來的益處和它所適應的場景很少說了,能夠看下infoq上的這篇文章瞭解下,這裏主要介紹下如何搭建和使用。
首先初始化項目react-graphqlnode

mkdir react-graphql
cd react-graphql
npm init

安裝基於koa的後端依賴react

npm install koa koa-bodyparser koa-router apollo-server-koa graphql graphql-tools

說一下各個依賴包的用處npm

  • koa 基於nodejs使用中間件模型的網絡服務框架
  • koa-bodyparser 解析請求體數據,form,json等格式數據
  • koa-router 用於配置路由規則,將不一樣路由請求映射到對應的處理邏輯
  • apollo-server-koa apollo是graphql技術發展及各類解決方案的項目,apollo-server-koa 提供了graphql開發調試的圖形化工具,並結合koa對請求路由地址和請求數據進行解析
  • graphql graphql的核心,如何處理類型定義和resolve的執行方式就是由它處理的
  • graphql-tools 用於解析schema定義

graphql-tools這裏再多說一下,便於讀者理解,若是簡單地使用graphql去定義類型和resolve函數也是可行的,它的方式以下json

import {
  graphql,
  GraphQLSchema,
  GraphQLObjectType,
  GraphQLString
} from 'graphql';

new GraphQLSchema({
  query: new GraphQLObjectType({
    name: 'RootQuery',       // RootQuery 根節點查詢類型
    fields: {                    // 查詢包含的字段
      hello: {                   // RootQueryType 查詢會返回一個 hello 字段
        type: GraphQLString,     // hello 字段是一個String類型
        resolve() {              // RootQueryType 查詢的解析器函數
          return 'world';        // 函數返回值爲 world 字符串
        }
      }
    }
  })
});

這裏就能看出其類型定義方式比較繁瑣,而且和代碼耦合度較高,graphql-tools 的意義就在於它會解析一種結構定義語言schema,將其解析爲上述方式,這樣能夠將定義文件分離開來,對組織一個項目大有用處,看看用graphql-tools咱們來怎麼寫後端

// scheme定義能夠寫在獨立文件中
const schema = `
  schema {
    query: RootQuery
  }

  type RootQuery {
    hello: String
  }
`;

makeExecutableSchema({
  typeDefs: [
    schema,
  ],
  resolvers: {
    hello() {
        return 'world'
    }
  },
});

如今開始咱們的graphql服務搭建,在項目目錄中,建立server目錄,並建立index.js文件,引入依賴bash

// server/index.js

const Koa = require('koa');
const { ApolloServer, gql } = require('apollo-server-koa');

const typeDefs = gql`
    schema {
        query: Query
    }
    
    type Query {
        hello: String
    }
`; // 定義類型

const resolvers = {
    Query: {
        hello() {
            return 'world'
        }
    }
}; // resolve 定義

const app = new Koa();

const server = new ApolloServer({
    typeDefs,
    resolvers,
    playground: true, // 開啓開發UI調試工具
});

server.applyMiddleware({ app });

const port = 8000;
app.listen({ port }, () => console.log(`graphql server start at http://localhost:${port}${server.graphqlPath}`));
// 開啓服務,server.graphqlPath 默認爲 /graphql

package.json中添加啓動命令網絡

"scripts": {
   "start": "node server/index.js"
 }

啓動服務app

npm start

訪問服務http://localhost:8000/graphql 能夠看到以下UI界面,這是graphql的UI調試工具
Playground---http-localhost-8000-graphql
咱們簡單的graphql服務就搭建好了,在實際開發中schema和resolve會愈來愈多而且要進行模塊分割,如今咱們將schema和resolve提取出來
在server目錄下建立schema和reslover文件,並添加index.js文件框架

// schema/index.js

const fs = require('fs');
const path = require('path');
const { gql } = require('apollo-server-koa');

// 包含全部的類型定義
const typeDefs = [];

// 同步讀取當前目錄下全部 .graphql 文件
const dirs = fs.readdirSync(__dirname);

dirs.forEach((dir) => {
    const filePath = path.join(__dirname, dir);
    if (
        fs.statSync(filePath).isFile && 
        filePath.endsWith('.graphql') // 讀取.graphql文件
    ) {

        const content = fs.readFileSync(
            filePath, 
            { 
                encoding: 'utf-8' 
            }
        );

        typeDefs.push(gql`${content}`); // gql字符串模板標籤函數會解析schame定義語法
    }

});

// 導出類型定義
module.exports = typeDefs;
// resolver/index.js

const fs = require('fs');
const path = require('path');
const _ = require('lodash'); // npm install lodash 添加依賴

// 包含全部的resolve
const resolvers = {};

// 同步讀取當前目錄下全部 .js 文件
const dirs = fs.readdirSync(__dirname);

dirs.forEach((dir) => {
    const filePath = path.join(__dirname, dir);
    if (
        fs.statSync(filePath).isFile &&
        filePath.endsWith('.js') &&
        !filePath.endsWith('index.js') // 不包含此文件
    ) {

        const resolver = require(filePath);

        _.merge(resolvers, resolver); // 合併全部的resolver到reslovers中
    }

});

// 導出resolvers
module.exports = resolvers;

而後修改server/index.js引入schema和reloverkoa

// server/index.js

const Koa = require('koa');
const { ApolloServer, gql } = require('apollo-server-koa');

// 加載全部的 schema
const typeDefs = require('./schema');

// 加載全部的 resolver
const resolvers = require('./resolver');

const app = new Koa();

const server = new ApolloServer({
    typeDefs,
    resolvers,
    playground: true, // 開啓開發UI調試工具
});

server.applyMiddleware({ app });

const port = 8000;
app.listen({ port }, () => console.log(`graphql server start at http://localhost:${port}${server.graphqlPath}`));
// 開啓服務,server.graphqlPath 默認爲 /graphql

而後咱們以用戶發帖爲例子,定義schema和reslove,並介紹graphql中的一些特性,如結構定義,經常使用類型,枚舉,自定義類型,定義指令,mock數據,自定義請求上下文等是如何使用的
首先咱們先實現建立用戶,發帖,查看全部帖子這幾個功能
在server/schema中建立文件schame.graphql文件,vscode有對應.graphql文件的語法提示插件

# server/schema/schema.graphql

# #號用於寫註釋

schema { # schema關鍵字用於定義schema入口
    query: Query # query是規定的入口字段,這裏表示全部的查詢都是查詢 Query 類型下的字段
}

type Query { # type 關鍵字用於定義結構
    node: Node # 由於咱們要將Post和User分離出去,因爲結構定義不容許空結構,Node在這裏只是做爲佔位
}

type Node {
    id: ID # ID 類型,ID類型表明惟一性,值能夠是數字或字符串
}

再建立user.graphql和post.graphql

# server/schema/user.graphql

type User {             # 定義User結構
    id: ID              # ID 類型,原始類型
    name: String        # String 字符串類型,原始類型
    age: Int            # Int 整數類型,原始類型
    available: Boolean  # Boolean 類型,值爲 true/false,原始類型
    money: Float        # Float 浮點類型,原始類型
    gender: Gender      # Gender 性別枚舉類型
    birthday: Date      # Date 自定義日期類型
}                       # 標量類型可參見 http://spec.graphql.cn/#sec-Scalars-

enum Gender {           # enum 用於定義枚舉類型
    FEMALE              # 枚舉值,若沒有對應的resolve值,將會是字符串
    MALE
}

extend type Query {     # extend 擴展 Query 結構,至關於 type Query { user(id: ID!): User }
    user(id: ID!): User # user表示查詢字段,id: ID! 表示查詢時的輸入值,!表示必須由此輸入值,返回 User結構的數據,能夠將這種寫法看成函數來理解,user函數名,id 輸入值,User 返回類型
}
# server/schema/post.graphql

type Post {
    id: ID!
    title: String
    content: String
    userId: ID
    user: User
}

extend type Query {
    post(id: ID!): Post
}

注意要想查詢出自定義結構,就必須將自定義結構與根節點Query結構關聯起來,graphql是一層套一層的方式,最終組織成一個大的結構,例如咱們如今應用的結構是這樣子的

# 僞代碼
schema {
    query: {
        user: User {
            ...
        }
        post: Post {
            ...
            User {
                ...
            }
        }
    }
}

到這裏,若是直接運行服務,會報Date類型未找到的錯誤,所以咱們要手動實現自定義類型Date
首先在schema.graphql最後一行聲明自定義類型

# server/schema/schema.graphql

# #號用於寫註釋

schema { # schema關鍵字用於定義schema入口
    query: Query # query是規定的入口字段,這裏表示全部的查詢都是查詢 Query 類型下的字段
}

type Query { # type 關鍵字用於定義結構
    node: Node # 由於咱們要將Post和User分離出去,因爲結構定義不容許空結構,Node在這裏只是做爲佔位
}

type Node {
    id: ID # ID 類型,ID類型表明惟一性,值能夠是數字或字符串
}

scalar Date # 自定義Date類型

建立server/scalar.js文件,並添加以下內容

// server/scalar.js

const { GraphQLScalarType } = require('graphql');

const resolvers = {
    Date: new GraphQLScalarType({
        name: 'Date',
        description: '日期類型',
        parseValue(value) {
            // 這個函數用於轉換客戶端傳過來的json值
            // json值是相似這樣傳過來的 { query: "user(date: $date): User", variables: { $date: "2017-07-30" }}
            return new Date(value);
        },
        parseLiteral(ast) { 
            // 這個函數用於轉換客戶端傳過來的字面量值
            // 字面量值是相似這樣傳過來的 { query: "user(date: '2017-07-30'): User" }
            return new Date(ast.value);
        },
        serialize(value) {
            // 發送給客戶端時將Date類型的值轉換成可序列化的字符串值
            return value.toISOString();
        },
    }),
};

module.exports = resolvers;

最後一步,加載咱們的自定義類型

// server/index.js

...
// 加載全部的 resolver
const resolvers = require('./resolver');

// 加載自定義類型
// 自定義類型,其本質也是個resolve
const scalar = require('./scalar.js');
// 將自定義類型合併到resolver中
_.merge(resolvers, scalar);

const app = new Koa();
...

如今咱們的自定義Date類型有了,能夠運行服務,但此時尚未定義resolve,所以查詢將不會獲取到數據,在resolve未實現以前,咱們能夠先用mock數據來驗證咱們的查詢,apollo項目提供了強大又易用的mock集成,原始類型都已經實現了mock,所以咱們只須要實現自定義類型Date的mock,建立server/mock.js文件並添加以下內容

// server/mock.js

module.exports = {
    Date() {
        return new Date()
    }
}

將mock引入到index.js文件中

// server/index.js

...
_.merge(resolvers, scalar);

// mock 數據
const mocks = require('./mock');

const app = new Koa();

const server = new ApolloServer({
    typeDefs,
    resolvers,
    mocks,            // 配置mock數據
    playground: true, // 開啓開發UI調試工具
});

server.applyMiddleware({ app });
...

如今咱們能夠啓動服務,並能查詢出mock數據了
Screenshot_2018-09-09-Playground---http-localhost-8000-graphql自此,咱們實現了一個簡單的graphql服務,將schema,resolve定義進行分離,實現了自定義類型和mock數據,能夠寫查詢語句來驗證咱們定義的schema結構了,後續咱們會繼續完成resolver,自定義指令來實現鑑權,擴展請求上下文加入保存數據的功能,等咱們的graphql服務都搭建完成,再來實現react與graphql的集成

相關文章
相關標籤/搜索