GraphQL 漸進學習 09-graphql-apollo-client-vue-客戶端開發

會煮咖啡的貓發表於2018-05-07

GraphQL 漸進學習 09-graphql-apollo-client-vue-客戶端開發

軟體環境

  • vue > 2.5.x

目標

  • 建立 graphql 客戶端
  • 封裝請求處理
  • 基於 token 驗證

程式碼

步驟

1 安裝依賴包

  • npm install
npm install --save vue-apollo graphql apollo-client apollo-link apollo-link-http apollo-cache-inmemory graphql-tag
  • webpack.json 包清單
  "dependencies": {
    "apollo-cache-inmemory": "^1.2.0",
    "apollo-client": "^2.3.0",
    "apollo-link": "^1.2.2",
    "apollo-link-http": "^1.5.4",
    "element-ui": "^2.3.7",
    "graphql": "^0.13.2",
    "graphql-tag": "^2.9.2",
    "vue": "^2.5.2",
    "vue-apollo": "^3.0.0-beta.5",
    "vue-router": "^3.0.1"
  },

如果遇到程式碼編譯問題,請對照我使用的包版本

2 編寫 webpack 配置

  • 檔案 build/webpack.base.conf.js
  module: {
    rules: [
      ...
      {
        test: /.(graphql|gql)$/,
        exclude: /node_modules/,
        loader: `graphql-tag/loader`
      }
      ...
    ]
  }
  • import 時,自動做 schema js 包裝
  • import QUERY_USER from `@/graphql/user.graphql` ,把 QUERY_USER 列印看看

3 編寫 config.json 配置

  • 檔案 src/utils/config.json
{
  "graphqlServer": "http://127.0.0.1:7001/graphql",
  "tokenName": "UU_AUTH_TOKEN"
}
  • graphqlServer 伺服器地址
  • tokenName 本地寫 localStorage key 名稱 ,推薦大家做本地化可以用 cookie 設定過期時間

4 編寫 VueApollo 介面卡

  • 檔案 src/apolloProvider.js
import Vue from `vue`
import {ApolloClient} from `apollo-client`
import {HttpLink} from `apollo-link-http`
import {InMemoryCache} from `apollo-cache-inmemory`
import VueApollo from `vue-apollo`
import { ApolloLink, concat, split } from `apollo-link`;
import { getMainDefinition } from `apollo-utilities`;
import Config from `@/utils/config.json`

Vue.use(VueApollo)

const httpLink = new HttpLink({
  uri: Config.graphqlServer,
})

const authMiddleware = new ApolloLink((operation, forward) => {
  const token = localStorage.getItem(Config.tokenName) || null
  operation.setContext({
    headers: {
      Authorization: `Bearer ${token}`
    }
  });
  return forward(operation);
})

const apolloClient = new ApolloClient({
  link: concat(authMiddleware, httpLink),
  cache: new InMemoryCache(),
  connectToDevTools: true,
})

const apolloProvider = new VueApollo({
  defaultClient: apolloClient,
  defaultOptions: {
    $loadingKey: `loading`
  },
  errorHandler (error) {
    console.log(`Global apollo error handler`)
    console.error(error)
  }
})

export default apolloProvider

authMiddleware 中寫入 Request headers token
apolloClient 中定義 cache: new InMemoryCache()

  • 檔案 src/main.js
import apolloProvider from `./apolloProvider`
...
new Vue({
  el: `#app`,
  provide: apolloProvider.provide(),
  ...
})

5 編寫 schema 定義

  • 專案中的請求定義 我都放在了 目錄 src/graphql 下,有點像 restfulapi 定義
.
├── removeUser.graphql
├── user.graphql
└── users.graphql
  • 我這裡寫了 2 個查詢 1 個更新操作,名稱和服務端定義一致,這樣方便查詢修改

6 編寫 Graphql 請求

  • 檔案 src/components/HelloWorld.vue
  • 官方 readme.md 中只寫了一種 元件 方式呼叫,我還是喜歡 api 方式
  • 程式碼 HelloWorld.vue

6.1 元件 方式

  apollo: {
    login: {
      query: gql`
        query queryFun($username: String!, $password: String!) {
          user(username: $username, password: $password) {
            id
            name
            token
          }
        }
      `,
      variables() {
        return {
          username: `ducafecat`,
          password: `345457yftgyhdsfghre`
        }
      },
      manual: true,
      result({data, loading}) {
        console.log(data, loading)
        if (!loading) {
          this.res = data
        }
      }
    }
  }
}
</script>

這種方式做查詢,適合功能單一的展示元件,靈活性感覺還是差了點,有興趣的可以去掉註釋大家體會下。

6.2 api 方式

  • query 查詢
import QUERY_USER from `@/graphql/user.graphql`
...
  methods: {
    handleLogin() {
      this.clearData()
      this.$apollo
        .query({
          // Query
          query: QUERY_USER,
          variables: {
            username: `ducafecat`,
            password: `12321321321321432`
          },
        })
        .then(response => {
          this.loading = false
          this.res = response.data
          localStorage.setItem(Config.tokenName, this.res.user.token)
          alert(`登入成功,寫入Token完成,重新裝載 apolloProvider`)
          window.location.reload()
        })
        .catch(error => {
          this.loading = false
          this.err = error
        })
    },
  • mutate 更新
    handleRemoveUser() {
      this.clearData()
      this.$apollo
        .mutate({
          // Query
          mutation: MUTATION_REMOVE_USER,
          variables: {
            id: 123
          }
        })
        .then(response => {
          this.loading = false
          this.res = response.data
        })
        .catch(error => {
          this.loading = false
          this.err = error
        })
    },

參考

1 文章

2 元件

相關文章