日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

GraphQL在react中的應用示例詳解_React

作者:Flipped_Z ? 更新時間: 2022-11-22 編程語言

什么是 GraphQL

GraphQL由Facebook發起,其手機客戶端自2012年起,就全面采用了GraphQL查詢語言, 2015年, Facebook全面開源了第一份GraphQL規范。 GraphQL 對你的 API 中的數據提供了一套易于理解的完整描述,是一種規范,使得客戶端能夠準確地獲得它需要的數據,而且沒有任何冗余.

GraphQL出現的意義

傳統API存在的主要問題:

  • 接口數量眾多維護成本高:接口的數量通常由業務場景的數量決定,為了盡量減少接口數量,服務端工程師通常會對業務做抽象,首先構建粒度較小的數據接口,再根據業務場景對數據接口進行組合,對外暴露業務接口,即便這樣,服務端對前端暴露的接口數量還是非常多,因為業務總是多變的。
  • 接口擴展成本高:出于帶寬的考慮移動端我們要求接口返回盡量少的字段,PC 端通常要展現更多字段;考慮首屏性能,我們又要求對接口做合并;傳統 API 應對這些需求,前后端都面臨改造,成本較高。
  • 接口響應的數據格式無法預知:由于接口文檔幾乎總是不能及時更新,前端工程師無法預知接口響應的數據格式,影響前端開發進度。

GraphQL 如何解決問題

請求參數在發送到服務端之前會先經過 GraphQL Client 轉換成客戶端 Schema,這段 Schema 其實是一段 query 開頭的字符串,描述了客戶端的對數據的述求:調用哪個方法,傳遞什么樣的參數,返回哪些字段。服務端拿到這段 Schema 之后,通過事先定義好的服務端 Schema 接收請求參數并執行對應的 resolve 函數提供數據服務。

GraphQL基本語法

參考 [GraphQL][1] 官網文檔

標量類型

GraphQL 自帶一組默認標量類型: Int:有符號 32 位整數。 Float:有符號雙精度浮點值。 String:UTF‐8 字符序列。 Boolean:true 或者 false。 ID:ID 標量類型表示一個唯一標識符,通常用以重新獲取對象或者作為緩存中的鍵。ID 類型使用和 String 一樣的方式序列化。

對象類型

一個 GraphQL schema 中的最基本的組件是對象類型,它就表示你可以從服務上獲取到什么類型的對象,以及這個對象有什么字段

type Character {
    name: String!
    list: [Episode!]!
}
myField: [String!]
myField: null 
myField: [] 
myField: ['a', 'b'] 
myField: ['a', null, 'b'] 
myField: [String]!
myField: null
myField: []
myField: ['a', 'b'] 
myField: ['a', null, 'b']

GraphQL 對象類型上的每一個字段都可能有零個或者多個參數,

type Starship {
    id: ID!
    name: String!
    length(unit: LengthUnit = METER): Float
}

枚舉類型

enum Episode {
    NEWHOPE
    EMPIRE
    JEDI
}

這表示無論我們在 schema 的哪處使用了 Episode,都可以肯定它返回的是 NEWHOPE、EMPIRE 和 JEDI 之一。

對象類型、標量以及枚舉是 GraphQL 中你唯一可以定義的類型種類。但是當你在 schema 的其他部分使用這些類型時,或者在你的查詢變量聲明處使用時,你可以給它們應用額外的類型修飾符來影響這些值的驗證。

type Character {
    name: String!
    list: [Episode]!
}

GraphQL 內置指令

GraphQL 中內置了兩款邏輯指令,指令跟在字段名后使用。

@include 當條件成立時,查詢此字段

query {
    search {
        actors @include(if: $queryActor) {
            name
        }
    }
}

@skip 當條件成立時,不查詢此字段

query {
    search {
        comments @skip(if: $noComments) {
            from
        }
    }
}

  • 操作類型:指定本請求體要對數據做什么操作,類似與 REST 中的 GET POST。GraphQL 中基本操作類型有 query 表示查詢,mutation 表示對數據進行操作,例如增刪改操作,subscription 訂閱操作。
  • 操作名稱:操作名稱是個可選的參數,操作名稱對整個請求并不產生影響,只是賦予請求體一個名字,可以作為調試的依據。
  • 變量定義:在 GraphQL 中,聲明一個變量使用符號開頭,冒號后面緊跟著變量的傳入類型。如果要使用變量,直接引用即可,例如上面的movie就可以改寫成movie(name:符號開頭,冒號后面緊跟著變量的傳入類型。如果要使用變量,直接引用即可,例如上面的 movie 就可以改寫成 movie(name: 符號開頭,冒號后面緊跟著變量的傳入類型。如果要使用變量,直接引用即可,例如上面的movie就可以改寫成movie(name:name)。
query Hero($episode: Int!, $withFriends: Boolean!) {
    hero(episode: $episode) {
        name
        friends @include(if: $withFriends) {
        name
        }
    }
}

什么是 Apollo

Meteor 團隊有著很豐富的數據流控制經驗,他們發現了 Relay 的不便之處,引領業界通過使用他們開發的 Apollo 享受到更簡潔的接口,Apollo 是基于GraphQL的全棧解決方案集合。包括了 apollo-client 和 apollo-server ;從后端到前端提供了對應的 lib ,使開發使用 GraphQL 更加的方便。

apollo-server

apollo-server是一個在nodejs上構建grqphql服務端的web中間件。支持express,koa 等框架。 參考 [apollo-server][2] 官網文檔

處理流程

主要是通過官方graphql-js庫進行處理

1.解析階段

為了識別客戶端 Schema, graphql-js 定義了一系列的特征標識符:

export const TokenKind = Object.freeze({
    BANG: '!',
    DOLLAR: '$',
    PAREN_L: '(',
    PAREN_R: ')',
    SPREAD: '...',
    COLON: ':',
    EQUALS: '=',
    BRACKET_L: '[',
    BRACKET_R: ']',
    ...
});

并定義了 AST 語法樹規范,規定語法樹支持以下節點:

export const Kind = Object.freeze({
    // Name
    NAME: 'Name',
    // Document
    DOCUMENT: 'Document',
    OPERATION_DEFINITION: 'OperationDefinition',
    VARIABLE_DEFINITION: 'VariableDefinition',
    VARIABLE: 'Variable',
    // Values
    INT: 'IntValue',
    FLOAT: 'FloatValue',
    STRING: 'StringValue',
    BOOLEAN: 'BooleanValue',
    ...
});

有了特征字符串與 AST 語法樹規范,GraphQL Server 對客戶端 Schema 進行逐字符掃描,如果客戶端 Schema 不符合服務端定義的 AST 規范,解析過程會直接拋出語法異常。

2.校驗階段

校驗階段用于驗證客戶端 Schema 是否按照服務端 Schema 定義好的方式獲取數據,比如:獲取數據的方法名是否有誤,必填項是否有值等等,校驗范圍一共有幾十種,不一一舉例。

{
    "errors":[
        {
            "message":"Cannot query field "getU" on type "Query". Did you mean "getUser"?",
            "locations":[
                {
                    "line":3,
                    "column":9
                }
            ]
        }
    ]
}

不僅返回結構化的報錯信息,還非常人性化的告訴你正確的調用方式是什么。校驗階段通過之后會進入執行階段.

3.執行階段

執行階段依賴的輸入為:解析階段的產出物 document ,服務端 Schema;其中 document 準確描述了客戶端對數據的述求:請求哪個方法,參數是什么,需要哪些字段;服務端 Schema 描述了提供數據的方式。執行服務端 Schema 中的 resolve 函數,得到執行階段的輸出。每個類型的每個字段都由一個 resolver 函數支持,該函數由 GraphQL 服務器開發人員提供。

Schema

Schema可以說是GraphQL最具核心的部分,其描述了整個接口向外暴露的形式;像Restful API,我們會定義一個查詢所有人的接口url定義為:/api/v1/user/getUsers,而查詢人具體信息的接口url為:/api/v1/user/getUserById,前端人員調用起來很直觀。但是graphql是完全不一樣的使用方式,其向前端暴露的url就一個像/api/graphql之類的,那這么多接口怎么區分呢?

一個graphql接口都有一個Schema定義,其定義三種操作方式:query(查詢),mutation(變更)和subscription(監聽)。再往下延伸,一個查詢中包含多個field,也就是多種不同的查詢,比如query user查詢人,query message查詢消息,query weather查詢天氣,通過這些就實現了Restful API使用多個url來達到不同操作的效果。

給server端帶來的便利性

由于 GraphQL 通過客戶端 Schema 而不是通過 URL 描述數據述求,所以理論上服務端只需要對客戶端暴露一個地址即可, 解決了接口數量眾多維護成本高的問題; 同時,服務端提供的是全量字段,客戶端可按需獲取,面對接口擴展的需求,服務端沒有開發成本;

import express from 'express';
import { graphiqlExpress, graphqlExpress } from 'apollo-server-express';
const app = express();
app.use('/graphql', graphqlExpress({
    schema,
}));
app.use('/graphiql', graphiqlExpress({
    endpointURL: '/graphql'
}));

apollo-client

參考 [apollo-client][3] 官網文檔

創建client

import ApolloClient from "apollo-boost";
const client = new ApolloClient({
  uri: "https://48p1r2roz4.sse.codesandbox.io"
});

在我們將Apollo Client連接到React之前,讓我們先嘗試發送查詢。記住首先導入gql用于將查詢字符串解析為查詢文檔。

import gql from "graphql-tag";
...
client.query({
    query: gql`
      {
        rates(currency: "USD") {
          currency
        }
      }
    `
  })
  .then(result => console.log(result));

將client注入到react

react-apollo提供ApolloProvider組件,ApolloProvider類似于redux的provider。它會把apollo客戶端放入到React app的上下文里,以便在組件樹的任何地方都是可以獲取到apollo客戶端。

import React from "react";
import { render } from "react-dom";
import { ApolloProvider } from "react-apollo";
const App = () => (
  <ApolloProvider client={client}>
    <div>
      <h3>My first Apollo app ??</h3>
    </div>
  </ApolloProvider>
);
render(<App />, document.getElementById("root"));

數據請求

1.通過react-apollo提供的graphql函數獲取數據,并connect到組建中,就可以在組件的this.props中看到多了個data對象,

import React, { Component } from 'react';
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';
export const USERS_QUERY = gql`
  query UserQuery($pageNum: Int,$pageSize:Int){
    users(pageNum:$pageNum,pageSize:$pageSize ) {
      pageNum
      pageSize
      total
      data {
        userName
      }
    }
  }
`;
const withQuery = graphql(USERS_QUERY, {
  options: () => ({
    variables: {
      pageNum: 3,
      pageSize: 8,
    },
  }),
});
class List extends Component {
  constructor(props) {
    super(props);
    this.state = {};
  }
  render() {
    const { data: { loading, error, users } } = this.props;
    if (loading) {
      return <div className="loading">Loading...</div>;
    }
    if (error) return `Error! ${error.message}`;
    const { total } = users;
    };
    return (
        <div>
            <p className="total">總共<span>{total}</span>人</p>
        </div>
    );
  }
}
export default withQuery(List);

data中包含loading, error, users等字段

當React安裝Query組件時,Apollo Client會自動觸發查詢。如果想延遲觸發查詢,直到用戶執行操作(例如單擊按鈕),該怎么辦?對于這種情況,可以使用ApolloConsumer組件并直接調用client.query()。

import React, { Component } from 'react';
import { ApolloConsumer } from 'react-apollo';
class DelayedQuery extends Component {
  state = { dog: null };
  onDogFetched = dog => this.setState(() => ({ dog }));
  render() {
    return (
      <ApolloConsumer>
        {client => (
          <div>
            {this.state.dog && <img src={this.state.dog.displayImage} />}
            <button
              onClick={async () => {
                const { data } = await client.query({
                  query: GET_DOG_PHOTO,
                  variables: { breed: "bulldog" }
                });
                this.onDogFetched(data.dog);
              }}
            >
              Click me!
            </button>
          </div>
        )}
      </ApolloConsumer>
    );
  }
}

2.通過apollo提供的組件獲取

import gql from "graphql-tag";
import { Query } from "react-apollo";
const GET_DOG_PHOTO = gql`
  query Dog($breed: String!) {
    dog(breed: $breed) {
      id
      displayImage
    }
  }
`;
const DogPhoto = ({ breed }) => (
  <Query query={GET_DOG_PHOTO} variables={{ breed }} pollInterval={500}>
    {({ loading, error, data, startPolling, stopPolling }) => {
      if (loading) return null;
      if (error) return `Error!: ${error}`;
      return (
        <img src={data.dog.displayImage} style={{ height: 100, width: 100 }} />
      );
    }}
  </Query>
);

通過設置pollInterval為500,每隔0.5秒看到一個新的小狗圖像。 當我們從Query組件中獲取數據時,看看Apollo Client幕后發生的事情。

1.當Query組件安裝時,Apollo Client會為我們的查詢創建一個observable。我們的組件通過Apollo Client緩存訂閱查詢結果。

2.首先,我們嘗試從Apollo緩存加載查詢結果。如果它不在那里,我們將請求發送到服務器。

3.數據恢復后,我們將其標準化并將其存儲在Apollo緩存中。由于Query組件訂閱了結果,因此它會自動更新數據。

數據緩存

創建本地緩存

import { ApolloClient } from 'apollo-client'
import { withClientState } from 'apollo-link-state'
import { HttpLink } from 'apollo-link-http'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { resolvers, typeDefs, defaults } from '../client/index'
const cache = new InMemoryCache()
const client = new ApolloClient({
  cache, // 本地數據存儲
  link: withClientState({ resolvers, defaults, cache, typeDefs }).concat(
    new HttpLink({
      uri: 'http://localhost:4001/graphql',
      opts: {
        credentials: 'cross-origin',
      },
    })
  ),
})

要直接與緩存交互,可以使用Apollo Client方法readQuery,readFragment,writeQuery和writeFragment。

總結

如果使用 GraphQL,那么后端將不再產出 API,而是將 Controller 層維護為 Resolver,和前端約定一套 Schema,這個 Schema 將用來生成接口文檔,前端直接通過 Schema 或生成的接口文檔來進行自己期望的請求。

GraphQL 的優缺點

優點

所見即所得:所寫請求體即為最終數據結構 減少網絡請求:復雜數據的獲取也可以一次請求完成 Schema 即文檔:定義的 Schema 也規定了請求的規則 類型檢查:嚴格的類型檢查能夠消除一定的認為失誤

缺點

增加了服務端實現的復雜度:一些業務可能無法遷移使用 GraphQL,雖然可以使用中間件的方式將原業務的請求進行代理,這無疑也將增加復雜度和資源的消耗

原文鏈接:https://juejin.cn/post/7152810866772017159

欄目分類
最近更新