React+GraphQL入門

雲棲直播~發表於2018-06-25

最近剛完成一個新專案,閒著沒事,想著學點新東西(做前端的人都懂,技術更新實在太快了,不學容易out),聽說GraphQL現在開始火起來,大有取代傳統Restful API的方式的趨勢,所以我決定學學。

什麼是 GraphQL

GraphQL 是由 Facebook 創造的用於 API 的查詢語言(這裡查詢語言所指的並不是常規意義上的類似 sql 語句的查詢語言,而是一種用於前後端資料查詢方式的規範)。

GraphQL 既是一種用於 API 的查詢語言也是一個滿足你資料查詢的執行時。 GraphQL 對你的 API 中的資料提供了一套易於理解的完整描述,使得客戶端能夠準確地獲得它需要的資料,而且沒有任何冗餘,也讓 API 更容易地隨著時間推移而演進,還能用於構建強大的開發者工具。
複製程式碼

更多關於GraphQL的介紹大家可以看這裡

由於GraphQL只是一套規範,不能直接使用,但社群有了很多程式語言的實現,可以直接拿過來用。這裡我選用了Apollo

什麼是 Apollo

Apollo 是基於 GraphQL 的全棧解決方案集合。包括了 apollo-client 和 apollo-server ;從後端到前端提供了對應的 lib 使得開發使用 GraphQL 更加的方便。
複製程式碼

GraphQL需要前後端一起才能真正發揮它的的作用,我們先在服務端實現它,這裡就要用到apollo-server

apollo-server是一個在Node.js上構建GraphQL服務端的web中介軟體。支援expresskoahapi等框架。這裡我用的是koa

首先,我們要安裝依賴包

yarn add koa koa-bodyparser koa-router apollo-server-koa graphql graphql-tools
//or
npm install koa koa-bodyparser koa-router apollo-server-koa graphql graphql-tools
複製程式碼

然後,編寫程式碼

// server.js

const Koa = require('koa');
const Body = require('koa-bodyparser');
const router = require('koa-router')();
const {graphqlKoa, graphiqlKoa} = require('apollo-server-koa');
const {makeExecutableSchema} = require('graphql-tools');
const { GraphQLScalarType } = require('graphql');
const { Kind } = require('graphql/language');

const app = new Koa();
const PORT = 8090;

// 模擬資料
const users = [
  {
    id: 1,
    name: 'J.K. Rowling',
    date: new Date(2018, 5, 20)
  },
  {
    id: 2,
    name: 'Michael Crichton',
    date: new Date(2018, 5, 21)
  },
];

const typeDefs = `
    scalar Date
    type User{
        id:Int!
        name:String!
        date: Date!
    }
    type Query {
        users(id:Int!): [User]
        user(id:Int!, name: String!):User
    }
    type Mutation {
        addUser(name:String!):User
    }
    schema {
        query: Query
        mutation: Mutation  
    }
`;

const resolvers = {
    Query: {    // 對應到typeDefs中的 type Query
        users(root, args, context) {
            return users;
        },
        user(root, args, context, info) {
          return {id: args.id, name: args.name};
      }
    },
    Mutation: { // 對應到typeDefs中的 Mutation
        addUser(root, args, context) {
            return {id: 2, name: args.name};
        }
    },
    Date: new GraphQLScalarType({ // 自定義標量型別
        name: 'Date',
        description: 'Date custom scalar type',
        parseValue(value) {
          return new Date(value); // 從客戶端來的資料
        },
        serialize(value) {
          return value.getTime(); // 傳送給客戶端的資料
        },
        parseLiteral(ast) {
          if (ast.kind === Kind.INT) {
            return parseInt(ast.value, 10); 
          }
          return null;
        },
    }),
};


const myGraphQLSchema = makeExecutableSchema({
    typeDefs,
    resolvers
});

app.use(Body());

router.post('/graphql', graphqlKoa({
    schema: myGraphQLSchema,
}));
router.get('/graphql', graphqlKoa({
    schema: myGraphQLSchema,
}));

router.get( // 在瀏覽器裡使用GraphiQL(可以理解成GraphQL領域的postman)
  '/graphiql',
  graphiqlKoa({
    endpointURL: '/graphql',
  }),
);

app.use(router.routes());
app.use(router.allowedMethods());
app.listen(PORT, ()=>console.log('app run in localhost:' + PORT));
複製程式碼

接下來,執行下面命令來讓我們的服務端跑起來:

node server.js
app run in localhost:8090
複製程式碼

然後在瀏覽器裡輸入http://localhost:8090/graphiql,你會看到如下介面:

GraphiQL
這時說明我們的GraphQL服務已成功啟動。你可以在介面上進行相應的資料查詢。

服務端已經成功啟動,接下來就是我們的客戶端了,前端本人使用的是React框架,為了方便所以專案直接使用create-react-app建立。

建立完成後我們進入專案目錄,接著安裝客戶端GraphQL查詢所需要的依賴包:

yarn add react-apollo graphql-tag graphql apollo-client apollo-cache-inmemory apollo-link-http
// or
npm install react-apollo graphql-tag graphql apollo-client apollo-cache-inmemory apollo-link-http
複製程式碼

接下來我們修改src/index.js裡的程式碼:

// src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import { ApolloProvider } from 'react-apollo';
import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { HttpLink } from 'apollo-link-http';

const httpLink = new HttpLink({ uri: 'http://localhost:8090/graphql' })

const client = new ApolloClient({
  link: httpLink,
  cache: new InMemoryCache()
})

ReactDOM.render(
  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>
, document.getElementById('root'));
registerServiceWorker();
複製程式碼

然後修改src/App.js裡的程式碼:

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import gql from 'graphql-tag';
import { graphql } from 'react-apollo';

class App extends Component {
  render() {
    console.log(this.props);
    const { loading } = this.props.data;
    if (loading) {
      return <div>Loading...</div>
    }
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h1 className="App-title">Welcome to React</h1>
        </header>
        <p className="App-intro">
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>
        <p>{this.props.data.user.name}</p>
      </div>
    );
  }
}

export default graphql(gql`
  query User{
    user(id: 100, name: "zhangsfs") {
      id
      name
    }
    users(id: 100) {
      id
      name
      date
    }
  }
`)(App);
複製程式碼

經常包裝後的App元件會被注入一個名為dataprops,它包含下面這些欄位:

data
其中user和users是我查詢的欄位,所以這兩個欄位不是必須的。除此以外,其他的都是必須的,其中loading欄位為true是表示在正在查詢,為false表示查詢完成。

然後執行前端程式碼:

yarn start
// Compiled successfully!
// You can now view client in the browser.

// Local:            http://localhost:8081/
// On Your Network:  http://172.22.228.1:8081/
複製程式碼

在瀏覽器輸入http://localhost:8081/結果發現頁面報錯,沒能正常執行,我們開啟開發者工具發現了其中一項報錯:

error
從報錯可以看出是跨域問題,因為專案是執行在兩個不同的埠,因為瀏覽器的同源策略,所以不允許跨域訪問。

那麼如何才能實現跨域訪問呢?我們需要在修改服務端程式碼讓他支援跨域訪問。

首先在服務端增加一個依賴包:

yarn add @koa/cors@2
複製程式碼

然後修改server.js程式碼:

// server.js

const Koa = require('koa');
const Body = require('koa-bodyparser');
const router = require('koa-router')();
const cors = require('@koa/cors');

...

app.use(Body());
app.use(cors());

...

app.listen(PORT, ()=>console.log('app run in localhost:' + PORT));
複製程式碼

然後重新啟動服務端程式碼,重新整理http://localhost:8081/,發現專案能正常執行啦。至此一個簡單的GraphQL查詢就算完成了。

寫在最後

上面只是一個非常簡單的GraphQL查詢demo,因為本人也是初學,很多東西也還在學習階段。如果大家有什麼好的開發心得,歡迎交流。