GraphQL 這門新技術在去年就開始火熱起來,今年也在不少技術週刊、論壇上看到關於這門新技術的研究和討論。所以做爲一名前端開發,緊跟技術潮流是必須的 🤣,週末便花了點時間對 GraphQL 進行了相關學習,學習過程當中寫了一些簡單的 demo,在此過程當中發現這玩意是真的香啊,因此決定要開篇博客來記錄下這個過程。html
當學習一本新技術時,首先就要去了解這門新技術的定位和一些相關概念,而後就要去研究它的誕生可以解決哪些痛點和帶來哪些爽點。我我的習慣是直接去官方文檔找到這些疑問的答案。打開官網,你就看到了下面有很醒目的一句話:前端
一種用於 API 的查詢語言vue
還附加了幾句頗有意思的話:java
說實話我看到這些第一反應就是感受很酷,但也給我帶來了不少疑惑,單從紙面上看仍是比較難理解的,沒有實際操做的話就很難體會上面說的那些特徵。因此我開始了一波學習和 demo 的嘗試 🤔node
GraphQL 一些關鍵概念包含 Type
,Schema
, Query
, Mutation
等,下面會分別作一下簡單的說明,具體仍是要結合實際代碼進行分析。react
順着文檔看,首先就看到到了關於查詢的相關使用,所謂的查詢就是向服務端獲取你要想的數據,好比我要查全部的用戶列表,webpack
// 先定義 User 數據結構
type User {
id: Int!
name: String!
age: Int!
}
// query 查詢
query {
// 返回一個User類型的集合
userList() : [User!]
// 能夠傳參查詢
// 根據id查詢用戶信息
orderUser(id: Int!) : User
}
複製代碼
在 REST
風格接口應該是這樣子的git
GET /api/v1/userList
GET /api/V1/userList/:id/
複製代碼
這時候,你會對GraphQL
的解析---查詢語言會有點小小的感觸了github
還有,query
和type
的使用息息相關,因此在使用query
的時候,必定要先去了解type
類型系統,關於type
類型系統在官網的文檔 上已經寫得很是詳細,這裏我就不具體說了web
GraphQL 中的 Mutation
是用來改變服務器上的數據的。對應着 REST
風格中的 PUT
,DELETE
,POST
。Mutation
的語法風格和Query
很相似。關鍵在解析Mutation
過程當中會有所不一樣。還有值得注意的是,查詢字段時,是並行執行,而變動字段時,是線性執行,一個接着一個。
好比說,我要變動一個用戶user
的名字
mutation {
// 經過參數 id 去查到對應的用戶信息
MutationUserName(id: Int!, name: String!) : User!
}
複製代碼
解析 MutationUserName
Mutation: {
MutationUserName(_, { id, name }) {
const user = userList.find(val => val.id === id);
if (!user) {
throw new Error(`找不到id爲 ${id} 的user`);
}
user.name = name
return user;
}
}
複製代碼
在 GraphQL 中,Schema
主要是用來描述數據的形態,哪些數據能被查詢到,因此在 Schema
中主要定義可用數據的數據類型。這麼說吧,你想要查到的數據都必需要在 Schema
中進行定義,因此這裏是須要寫不少 type
的,這裏還須要統必定義 Query
和 Mutation
,也就是要把上面那些定位所有放到這裏來
🌰:
type User {
id: Int!
name: String!
age: Int!
}
type Query {
userList() : [User]
orderUser(id: Int!) : User
}
mutation {
MutationUserName(id: Int!, name: String!) : User
}
複製代碼
很很基礎的內容大概就是醬紫,下面應該要開始來一波實戰操做了
Apollo-GraphQL 是基於 GraphQL 封裝的一種實現,它能夠在服務上進行分層,包括
REST
api 和 數據庫,它包含客戶端和服務端,還有 GUI 開發工具,讓開發人員能夠快速上手進行開發。
我在 google 關於 GraphQL 的時候,就發現了這個平臺,在官方文檔上看到了一些講解和入門練習。發現其入口門檻其實並不高,安裝幾個依賴即可快速啓動服務端,客戶端也支持了 vue
、react/rn
、ng
等熱門的前端框架。我這裏的 demo 也是根據文檔教學一步步作出來的,下面就來說講個人 demo 實現過程和一些思路。
具體想法
這邊採用 apollo-server-express
快速搭建服務端
首先安裝依賴
yarn add apollo-server-express express graphql
複製代碼
好,這個 demo 只須要上面三個工具
對於apollo-server
,比較基本的就是要搞清楚 schema 和 resolvers 應該如何定義,這些理解起來仍是比較容易,但若是想玩得很溜,確定須要投入大量的時間去研究。
其實就是
const server = new ApolloServer({
typeDefs,
resolvers
});
複製代碼
定義好 typeDefs(schema) 和 resolvers,即可快速啓動
首先,就拿本人博客文章爲例,把 mock 的數據源造好
[
{
"id": 1,
"title": "記一次系統前端底層升級總結",
"date": "2018-11-11",
"introduction": "最近參與了一個比較大的類後臺管理系統的前端開發(vue技術棧),並負責了該系統的底層升級,升級過程期間,遇到了很多問題,在解決問題的過程當中學到了不少,趁着今天雙11沒啥事作,那麼就花點時間總結下升級系統的過程吧",
"category": "vue",
"isRead": false
},
{
"id": 2,
"title": "Vue實踐小結(長期更新)",
"date": "2018-11-04",
"introduction": "近期都在用 Vue 全家桶進行項目開發,過程當中不免會遇到很多問題,這篇博客主要就是記錄開發過程當中遇到的問題,和每一個問題對應的解決方案。此外,Vue 框架和周邊生態會一直更新,以及發佈新功能,在實踐過程當中總會遇到一些所謂的「坑」,我也會把填坑過程記錄於此。坑是填不完的,這篇博客也是寫不完的。",
"category": "vue",
"isRead": false
},
{
"id": 3,
"title": "如何用Koa2返回文本和圖片流以及解決亂碼事件",
"date": "2018-04-05",
"introduction": "前兩天作項目的時候,碰到了一個要在客戶端(瀏覽器)中實現預覽 txt 文檔和圖片的小需求,在開發過程當中遇到了一些有趣的小插曲---客戶端讀取 txt 時出現各類奇怪亂碼,圖片就沒問題。一時半會還沒找到好的解決方法,由於那天又恰好週五,因此在週末的時候,我決定宅在家裏好好研究如何解決這個有趣的現象。",
"category": "koa2",
"isRead": false
}
][
({
"id": 1,
"html": "<h1>記一次系統前端底層升級總結</h1>"
},
{
"id": 2,
"html": "<h1>Vue實踐小結(長期更新)</h1>"
},
{
"id": 3,
"html": "<h1>如何用Koa2返回文本和圖片流以及解決亂碼事件</h1>"
})
]
複製代碼
而後,要開始思考如何定義 Schema 中的 type
我這邊一共定義瞭如下這些 type
type Article {
id: Int!
title: String!
date: String!
introduction: String!
category: String
isRead: Boolean!
}
type ArticleContent {
id: Int!
html: String!
}
type Category {
num: Int!,
name: String!
}
type Query {
fetchArticles: [Article]
getAllCategories: [Category]
getArticleContent(id: Int!): ArticleContent
}
type Mutation {
articleIsRead(id: Int!): Article
}
複製代碼
把 這些 schema 轉換爲 typeDefs, 須要用到
const { gql } = require("apollo-server-express");
module.exports = gql`上面定義的type`;
複製代碼
定義 resolvers
resolvers
實際上是 query
和 mutation
的實現過程。也就是說這裏會進行數據庫的查詢或者是 api 的調用等等,最終放回的結果在這裏出來。
我這邊的實現大概是這樣的
const articles = require("../data/articles.json");
const articleContent = require("../data/articleContent.json");
const resolvers = {
Query: {
fetchArticles() {
return articles;
},
getAllCategories() {
return articles.reduce((pre, cur) => {
const cate = pre.find(_ => _.name === cur.category);
if (cate) {
cate.num++;
} else {
const obj = {
name: cur.category,
num: 1
};
pre.push(obj);
}
return pre;
}, []);
},
getArticleContent(_, { id }) {
return articleContent.find(val => val.id === id);
}
},
Mutation: {
// 標記已讀
articleIsRead(_, { id }) {
const article = articles.find(val => val.id === id);
if (!article) {
throw new Error(`找不到id爲 ${id} 的文章`);
}
if (article.isRead) {
return article;
}
article.isRead = true;
return article;
}
}
};
module.exports = resolvers;
複製代碼
好了,typeDefs 和 resolvers 搞定了,寫點服務器啓動腳本
const express = require("express");
const { ApolloServer } = require("apollo-server-express");
const typeDefs = require("./schema");
const resolvers = require("./resolvers");
const PORT = 4000;
const app = express();
const server = new ApolloServer({
typeDefs,
resolvers,
playground: {
endpoint: `/graphql`,
settings: {
"editor.theme": "light"
}
}
});
server.applyMiddleware({ app });
app.listen(PORT, () =>
console.log(
`🚀 Server ready at http://localhost:${PORT}${server.graphqlPath}`
)
);
複製代碼
而後 node server.js
一下看看
啓動不報錯的話,在瀏覽器http://localhost:4000/graphql
能夠看到圖形化頁面
試試 query 查詢
服務端搭建完成 🎉
最近在用 vue 比較多,因此就直接用 vue create xxx
進行一頓操做來弄了,主要也是基於 vue-apollo 文檔 進行學習和編寫
安裝?
yarn add vue-apollo graphql apollo-boost
import Vue from "vue";
import ApolloClient from "apollo-boost";
import VueApollo from "vue-apollo";
Vue.use(VueApollo);
const apolloClient = new ApolloClient({
// 你須要在這裏使用絕對路徑
uri: "http://localhost:4000/graphql"
});
const apolloProvider = new VueApollo({
defaultClient: apolloClient
});
new Vue({
el: "#app",
apolloProvider,
render: h => h(App)
});
複製代碼
配置支持 .gql || .graphql
文件後綴的 webpack loader
vue.config.js
module.exports = {
// 支持 gql 文件
chainWebpack: config => {
config.module
.rule("graphql")
.test(/\.(graphql|gql)$/)
.use("graphql-tag/loader")
.loader("graphql-tag/loader")
.end();
}
};
複製代碼
這樣就能夠導入xxx.gql
文件了,不必定非要在.vue
文件或者.js
文件裏面寫查詢語句了
就是這麼簡單 😁
so,使用?
接下來就試一下查詢多個數據,好比說一塊兒查詢博客文章列表和分類信息。驗證一下是否是官方說的獲取多個資源,只用一個請求那麼神奇
import gql from "graphql-tag";
const fetchDataGql = gql` { fetchArticles { id title date introduction category isRead } getAllCategories { num name } } `;
export default {
data() {
return {
articles: [],
categories: []
};
},
apollo: {
fetchData() {
const vm = this;
return {
query: fetchDataGql,
update(data) {
vm.articles = data.fetchArticles;
vm.categories = data.getAllCategories;
}
};
}
}
};
複製代碼
打開瀏覽器控制檯看看
確實是只有一次請求就能獲取到一個頁面上全部的資源!最近在作項目的時候,遇到一個頁面要請求 5 個REST
接口,有些接口常常返回了不少頁面上用不到的,簡單來講就是多餘的數據,這樣不只浪費了服務器資源,先後端對接起來也不方便,因此 GraphQL 能夠很好地解決這個痛點,真是香啊!
ui 界面上隨便寫點
好了,查詢搞定了,下面試試 變動mutation
需求是點擊某篇文章,讓這篇文章的 title 有個已讀標識(opacity: 0.4)
服務端那邊已經定義好,客戶端代碼核心實現
const mArticleISRead = gql` mutation articleIsRead($id: Int!) { articleIsRead(id: $id) { id title date introduction category isRead } } `
methods: {
mutationIsRead(id) {
this.$apollo.mutate({
mutation: mArticleISRead,
variables: {
id
},
update: (store, { data: { articleIsRead } }) => {
console.log(articleIsRead);
}
});
}
},
複製代碼
模板層增長一些判斷
<router-link :class="{isRead: item.isRead}" @click.native="mutationIsRead(item.id)" :to="{ name: 'content', params: { id: item.id } }" >
{{ item.title }}
</router-link>
複製代碼
測試
done!
寫到這裏,讓我感受好奇的是,我從新跳轉回 article 頁面,並無從新觸發 ajax 請求,後來看了文檔才發現這是因爲 Apollo-GraphQL 自帶了運行時的 cache,變動數據的某個字段是不須要從新去獲取最新的列表的,Apollo 會智能去識別而後自動觸發視圖的 render。這樣的話,我在想,這樣既然了 Apollo 的緩存機制,是否是就不須要像 vuex、redux、mobX 這些狀態管理工具了呢?畢竟都是運行時的數據,若是 Apollo 的 cache 機制能解決狀態管理層面上的問題,那麼會減小不少項目層級的複雜度吧。憑空想是想不出的,只有在實際項目中才能找到真正的答案。
好了,GraphQL 的服務端和客戶端均已搭建,一些自身的想法也獲得了相關印證,不斷探索、不斷驗證想法正是學習的一種樂趣 😁
上面代碼均已上傳到 github
GraphQL 牛刀小試結束。關於 GraphQL 之後能不能取代 RESTapi 仍是一個很富有爭議的話題。REST 已經發展多年,它解決了之前的很多問題但也留下了很多問題,GraphQL 仍是比較新的,相信還有不少人都不知道有這個技術,即便它已誕生了好多年...仍是那句話,每一個新的技術的誕生,必然是爲了解決某些問題,以及提升生產效率,這樣纔有它們存在的價值和意義。