摘要:包括什么把關(guān)于數(shù)據(jù)獲取的事情都接管過來,比如說請(qǐng)求異常,,請(qǐng)求排隊(duì),,獲取分頁數(shù)據(jù)。的聲明式數(shù)據(jù)獲取是按組織的,最好的方式也是把需要的數(shù)據(jù)寫在。另外,通過聲明式數(shù)據(jù)獲取還可以更好的對(duì)組件約束,只能獲取它聲明的數(shù)據(jù),并且也可以做些驗(yàn)證。
Facebook 在去年夏天公布了 GraphQL,就像往前端深潭砸下了一顆巨石,人們都被水聲吸引到了湖邊,觀望是否會(huì)出現(xiàn)什么,有些人期待,有些人猜疑。過了半年多,社區(qū)已經(jīng)慢慢的摸清這個(gè)石頭的材質(zhì),本文希望在你入門 GraphQL 和 Relay 的過程中能幫你清除一些障礙。
GraphQLGraphQL 是在 Facebook 內(nèi)部應(yīng)用多年的一套數(shù)據(jù)查詢語言和 runtime。
初次入門者建議先把官網(wǎng)的資料都讀一遍,難度不大(specification 和 API 可以后面再看)。
類型系統(tǒng) - GraphQL 是強(qiáng)類型語言,強(qiáng)類型雖然寫時(shí)會(huì)稍微累點(diǎn),但就不用寫一堆類型檢測(cè)的代碼了;
驗(yàn)證 - GraphQL 提供機(jī)制對(duì)你的語法和請(qǐng)求做一定層度的校驗(yàn);
introspection - 一個(gè)讓你能通過幾行代碼就能了解整個(gè)資源提供方的細(xì)節(jié)的 API。
GraphQL 優(yōu)勢(shì)官網(wǎng)已經(jīng)列舉了,我用更簡(jiǎn)練的語言描述下。
GraphQL 與 REST同類型協(xié)議目前最出名的是 REST,特點(diǎn)是資源可定位,使用 HTTP verbs。REST 具體應(yīng)該怎么寫有很多爭(zhēng)議,但簡(jiǎn)單的例子是沒有爭(zhēng)議的:
GET /users/1
REST 優(yōu)點(diǎn)是簡(jiǎn)單明了,缺點(diǎn)也是太簡(jiǎn)單明了,導(dǎo)致語法可擴(kuò)充性不強(qiáng)。
我們來看看 GraphQL 官網(wǎng)是怎么和 REST 對(duì)比的:
語法靈活
GraphQL 只需要一次請(qǐng)求就能夠獲得你所有想要的資源。這里舉一個(gè)和 REST 對(duì)比的例子 讓大家有直觀的認(rèn)識(shí)。
現(xiàn)在,我想獲取id為1的用戶的名字,年齡和他所有朋友的名字
GraphQL 實(shí)現(xiàn)的方案:
{ user(id: 1) { name age friends { name } } }
REST 實(shí)現(xiàn)的方案:
GET /users/1 and GET /users/1/friends
或
GET /users/1?include=friends.name
發(fā)現(xiàn)區(qū)別了嗎?用 REST 要不就發(fā)多次請(qǐng)求,要不就得用一個(gè)不方便擴(kuò)展的語法。
沒有冗余
日后擴(kuò)充資源也沒有冗余,你只會(huì)獲得你想要的資源。還是用上面的例子,如果 user 多了個(gè)屬性 gender 會(huì)怎么樣?
在 REST 的方案中,如果客戶端不變,取到的結(jié)果是會(huì)多了 gender 屬性,而在 GraphQL 方案中,客戶端是不會(huì)獲取到 gender 屬性的。
強(qiáng)類型
有 introspection 機(jī)制,代碼即文檔,方便快捷,而不需要去找這個(gè) API 的說明文檔在哪里,看個(gè)例子:
自定義 schema
沒必要像 REST 這樣固定且通用的語法。
其他專有方案(Ad Hoc Endpoints)和專有方案對(duì)比:
專有方案每個(gè)接口都自己定義獲取數(shù)據(jù),后端代碼不能得到重用;
和 REST 對(duì)比的第二點(diǎn)一樣;
每個(gè)接口的數(shù)據(jù)不能復(fù)用;
對(duì)比其他現(xiàn)有的專有方案,要么沒有強(qiáng)類型,要么沒有 GraphQL 這么昂貴,而且前面3點(diǎn)也還是沒有解決。
與圖數(shù)據(jù)庫的關(guān)系首先,介紹下什么是圖數(shù)據(jù)庫,可以參考neo4j的介紹,一圖勝千言:
上邊是關(guān)系數(shù)據(jù)庫,下邊是圖數(shù)據(jù)庫。
GraphQL 為什么有 Graph,是因?yàn)樗?query 是以圖的形式來組織的:
user ┖-OWNS-> playlist ┖-CONTAINS-> track ┖-LIKED_BY-> users
GraphQL 并不要求后臺(tái)一定要是圖數(shù)據(jù)庫,關(guān)系數(shù)據(jù)庫也可以,它只是一套查詢數(shù)據(jù)的語言而已。
DataLoaderDataloader 是一個(gè)小工具,幫你把你的請(qǐng)求轉(zhuǎn)成批量請(qǐng)求的形式,和 GraphQL 搭配的也挺好,看個(gè)例子:
query FetchPlaylist { playlist(id: "e66637db-13f9-4056-abef-f731f8b1a3c7") { id name tracks { id title viewerHasLiked } } }
這個(gè) query 是要獲取某個(gè)用戶的歌單。
注意一個(gè)細(xì)節(jié),這個(gè) query 想獲取每個(gè) track 的一些屬性。我們定義一下 Track 這個(gè)類型:
import { GraphQLString, GraphQLBoolean, GraphQLObjectType } from "graphql"; export default new GraphQLObjectType({ name: "Track", description: "A Track", fields: () => ({ id: { type: GraphQLString, resolve: it => it.uuid } title: { type: GraphQLString }, viewerHasLiked: { type: GraphQLBoolean, resolve: (it, _, { rootValue: { ctx: { auth } } }) => ( (auth.isAuthenticated) ? it.userHasLiked(auth.user) : null ) } }) });
resolve 函數(shù)調(diào)用的是后端 API,注意這里的 it 就是 track 的對(duì)象。
我們獲取 viewerHasLiked 這個(gè)屬性需要調(diào)用 it.userHasLiked (auth.user)。那么,我的歌單里有 50 首歌的話,就要調(diào)用 50 次it.userHasLiked(auth.user),這樣訪問數(shù)據(jù)庫的性能是無法接受的。合理的想法是變成批量的。那要怎么做呢?這就是 DataLoader 發(fā)揮作用的時(shí)候了:
import DataLoader from "dataloader"; import BaseModel from "./BaseModel"; const likeLoader = new DataLoader((requests) => { // requests is now a an array of [track, user] pairs. // Batch-load the results for those requests, reorder them to match // the order of requests and return. }) export default class Track extends BaseModel { userHasLiked(user) { return likeLoader.load([this, user]); } }
在一個(gè) event loop 里每次調(diào)用 dataloader,dataloader 會(huì)記下你的請(qǐng)求參數(shù),在下次 event loop 的時(shí)候把這么多次的請(qǐng)求參數(shù)變成一個(gè)數(shù)組提供你操作,你就可以拿這個(gè)數(shù)組對(duì)數(shù)據(jù)庫執(zhí)行批量的操作了。而且,它還對(duì)結(jié)果按你的請(qǐng)求參數(shù)進(jìn)行了緩存,是居家必備的殺人利器。
安全性或許有人有疑問,感覺 GraphQL 把我所擁有的資源全部都暴露了,別人不只一覽全局,而且還能一次過全部拉下來,那還得了?
事實(shí)上,GraphQL 提供的資源不一定要和你數(shù)據(jù)庫一樣,因?yàn)樗皇前缪葜虚g層的角色,雖然也可能很像。所以,你要想好哪些資源可以被看。
至于獲取,其實(shí)看到上面的例子里有這句 auth.isAuthenticated。
可以看到你可以在里面插入權(quán)限限制的。至于獲取資源太多拖垮服務(wù)器?
Jacob Gillespie 提到一些思路:
對(duì)語句做 AST 分析,太復(fù)雜的就拒絕了;
做超時(shí)限制,對(duì)容量也可以做限制;
客戶端記得要做 cache(如 Relay)。
RelayRelay 是連接 GraphQL 和 React 的一座橋梁。不過,除了讓 React 認(rèn)識(shí) GraphQL 服務(wù)器之外,它還做了什么呢?
建議先把官網(wǎng)的資料都讀一遍,Relay 相對(duì)來說比 GraphQL 復(fù)雜一些,而且文檔并不詳細(xì)(截至截稿時(shí),Relay的版本是 v0.6.1),也缺失了關(guān)于 graphql-relay 庫的詳細(xì)介紹,掃一遍后,結(jié)合本文最后的學(xué)習(xí)資料的代碼加深理解。
Relay 怎么用?使用 Relay 是要侵入前后端的:
在后端你得通過 graphql-relay-js 讓 GraphQL schema 更適合 Relay;
在前端再通過 react-relay 來配合 React。
Relay 包括什么?Relay 把關(guān)于數(shù)據(jù)獲取的事情都接管過來,比如說請(qǐng)求異常,loading,請(qǐng)求排隊(duì),cache,獲取分頁數(shù)據(jù)。我這里重點(diǎn)講一下以下幾個(gè)方面:
client-side cacheRelay 獲取數(shù)據(jù)當(dāng)然離不開 cache,可以看到 GraphQL 不再依賴 URL cache,而是按照 Graph 來 cache,最大的保證 cache 沒有冗余,發(fā)最少的請(qǐng)求,我舉一個(gè)例子:
比如下面這個(gè)請(qǐng)求:
query { stories { id, text } }
如果利用 URL 請(qǐng)求(比如說瀏覽器的 cache),那么這個(gè)請(qǐng)求下次確實(shí)命中 cache 了,那么假如我還有一個(gè)請(qǐng)求是:
query { story(id: "123") { id, text } }
看得出,下面這個(gè)請(qǐng)求獲取的數(shù)據(jù)是上面請(qǐng)求的子集,這里有兩個(gè)問題:
如果第一第二兩個(gè)請(qǐng)求獲取的數(shù)據(jù)不一致怎么辦?
本來就是子集,為什么我還要發(fā)請(qǐng)求?
這兩個(gè)想法催生出來了 GraphQL 的解決方案:按照 Graph 來 cache,也就是說子集不需要再發(fā)請(qǐng)求了,當(dāng)然你也可以強(qiáng)制發(fā)請(qǐng)求來更新局部或者整個(gè) cache。
具體做法是通過拍平數(shù)據(jù)結(jié)構(gòu)(類似數(shù)據(jù)庫的幾個(gè)范式)來 cache 整個(gè) Graph。
view 通過訂閱他需要的每個(gè) cache record 來更新,只要其中一個(gè) record 更新了,也只有訂閱了這個(gè) record 的 view 才會(huì)得到更新。
最后,聊到修改,我們可以看到 mutation 有個(gè)反直覺的地方是請(qǐng)求的 query 里包括了需要獲取的數(shù)據(jù)。為什么不直接返回你的修改影響的那些數(shù)據(jù)? 因?yàn)榉?wù)端實(shí)現(xiàn)這個(gè)太復(fù)雜了,有的時(shí)候一個(gè)簡(jiǎn)單的修改會(huì)影響到非常多的后臺(tái)數(shù)據(jù),而很多數(shù)據(jù) view 是不需要知道它變化了。
所以,Relay 團(tuán)隊(duì)最后選擇的方案是,讓客戶端告訴服務(wù)器端你認(rèn)為哪些數(shù)據(jù)你想重新獲取。具體到實(shí)現(xiàn),Relay 采用的方案是獲取 cache 和 fat query 有交集的部分,這樣既更新了 cache,而且不在 cache 里的也不會(huì)獲取。
Relay 的聲明式數(shù)據(jù)獲取React 是按 Component 組織 view 的,最好的方式也是把 view 需要的數(shù)據(jù)寫在 view。如果用常規(guī)的做法,view 負(fù)責(zé)自己的 Data-fetch,那么,由于 React 是一層一層的往里深入 Component 的,那么也就意味著每一層 Component 都自己發(fā)請(qǐng)求去了,是不可能做到用一個(gè)網(wǎng)絡(luò)請(qǐng)求來獲取所有數(shù)據(jù)的。
所以,Relay 通過抽象出一個(gè) container 的概念,讓每個(gè)模塊提前聲明自己需要的數(shù)據(jù),Relay 會(huì)先遍歷所有 container,組成 query tree,這樣就達(dá)到了只使用一個(gè)網(wǎng)絡(luò)請(qǐng)求的目的。
另外,通過聲明式數(shù)據(jù)獲取還可以更好的對(duì)組件約束,只能獲取它聲明的數(shù)據(jù),并且 Relay 也可以做些驗(yàn)證。
graphql-relay-js在看一些 React 和 Relay 協(xié)作的例子時(shí),經(jīng)常發(fā)現(xiàn)這個(gè)庫的存在,這個(gè)庫到底是干什么的?
通過查看源碼后發(fā)現(xiàn),里面其實(shí)是各種 helper 方法,負(fù)責(zé)生成一些 GraphQL 的類,為什么需要這樣做?其實(shí),這是因?yàn)?Relay 提供的一些功能(比如 ID handling,分頁)需要 GraphQL 服務(wù)器提供特定的代碼結(jié)構(gòu)。如果你要開發(fā)一個(gè) GraphQL 的前端,就算它基于其他框架,基于其他語言,實(shí)現(xiàn)一個(gè)像 graphql-relay-js 所實(shí)現(xiàn)的 Relay-compliant 的 server 是很有幫助的,比如graphql-go/relay。
babel-relay-pluginRelay 的 container 依賴的數(shù)據(jù)資源是通過聲明的,但客戶端是不知道后端的數(shù)據(jù)結(jié)構(gòu)的。為了讓客戶端了解整個(gè)后臺(tái)結(jié)構(gòu),就要引入這個(gè) bable 插件,這個(gè)插件通過讀取服務(wù)端的 schema,就可以讓客戶端正確理解它所需要的資源在服務(wù)端是長(zhǎng)什么樣的。
optimistic UI update我們看下例子:
Loading...
{error.message}
可以看到在 Relay 里可以很簡(jiǎn)單的處理請(qǐng)求整個(gè)請(qǐng)求過程中的 UI 變化。
總結(jié)相信閱讀本文的讀者都是對(duì)這兩者有一定興趣的人,但在我上手之后,我的心情是復(fù)雜的。GraphQL 和 Relay 帶來了一些優(yōu)勢(shì),最重要的是可以一次性獲取資源,看上去是未來之路,但這優(yōu)勢(shì)其實(shí)用些不優(yōu)雅的方法來解決也沒什么問題,但為了這些優(yōu)勢(shì)需要編寫大量與業(yè)務(wù)邏輯無關(guān)的代碼,讓我真心憂慮它的路能走多遠(yuǎn),相信看過一個(gè)官方的 TODOList的例子 的入門者很容易就能感覺到。REST 如此簡(jiǎn)單,普及開來尚且用了幾年,復(fù)雜好多倍的 GraphQL 的未來還任重而道遠(yuǎn)。
學(xué)習(xí)資料GraphQL 和 Relay學(xué)習(xí)資源匯總:這里列舉了比較全的相關(guān)學(xué)習(xí)資源,5顆星。
搭建你的第一個(gè) GraphQL 服務(wù)器:這篇文章從0開始幫你搭建一個(gè) GraphQL,比較淺,3顆星。
relay-starter-kit:這個(gè)例子簡(jiǎn)單的描述了 Relay 和 GraphQL 的關(guān)系,但沒有 mutation,3顆星。
From rest to GraphQL:提到了rootValue,dataloader,講了比較真實(shí)的例子,5顆星。
Relay 官方例子 TODOlist:比較完整的增刪改查的官方例子,5顆星。
Unofficial Relay FAQ:這篇 FAQ 是 Facebook 員工寫的,里面提到 Relay 是要取代 Flux,而且 routing 還在積極修改中。
相關(guān)的庫server:比如 express-graphql。
ORM:比如 graffiti。
facebook/dataloader。
adrenaline:React bindings for Redux with Relay。
react-router-relay:結(jié)合 react-router,介紹。
graphql-relay-js
babel-relay-plugin
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/78869.html
摘要:它的設(shè)計(jì)使得即使是大型團(tuán)隊(duì)也能以高度隔離的方式應(yīng)對(duì)功能變更。獲取數(shù)據(jù)數(shù)據(jù)變更性能,都是讓人頭痛的問題。通過維護(hù)組件與數(shù)據(jù)間的依賴在依賴的數(shù)據(jù)就緒前組件不會(huì)被渲染為開發(fā)者提供更加可預(yù)測(cè)的開發(fā)環(huán)境。這杜絕了隱式的數(shù)據(jù)依賴導(dǎo)致的潛在。 關(guān)于Relay與GraphQL的介紹 原文:Introducing Relay and GraphQL 視頻地址(強(qiáng)烈建議觀看):https://www.y...
摘要:閱讀過程中如果產(chǎn)生任何不適,請(qǐng)及時(shí)撥打自行搶救,謝謝。端選型總體還是比較前后端分離的,不強(qiáng)制你使用某一種方案。是官方出品和推薦的,也是默認(rèn)的配套方案。事后來看,的坑不少。 apollo-client 是一個(gè)比較難用的 GraphQL 客戶端,本系列帶你集成 redux,趟平深坑,鉆入原理,讓你在 21 分鐘內(nèi)學(xué)完 apollo-client。 NOTE: 閱讀過程中如果產(chǎn)生任何不適,請(qǐng)...
摘要:初始化項(xiàng)目使用初始化項(xiàng)目安裝項(xiàng)目結(jié)構(gòu)如下接口所有接口對(duì)封裝接下來對(duì)進(jìn)行封裝,加上中間件實(shí)現(xiàn)類似于攔截器的效果。 Graphql嘗鮮 在只學(xué)習(xí)graphql client端知識(shí)的過程中,我們常常需要一個(gè)graphql ide來提示graphql語法,以及實(shí)現(xiàn)graphql的server端來進(jìn)行練手。graphql社區(qū)提供了graphiql讓我們使用 graphiql (npm):一個(gè)交互...
摘要:作者滬江前端開發(fā)工程師本文原創(chuàng)翻譯,有不當(dāng)?shù)牡胤綒g迎指出。管理數(shù)據(jù),而提供服務(wù)器上的數(shù)據(jù),因此應(yīng)用于處理網(wǎng)絡(luò)請(qǐng)求。結(jié)論使用建立的應(yīng)用都是模塊化的會(huì)成為其中一個(gè)模塊,庫是另一個(gè)模塊。原文原創(chuàng)新書移動(dòng)前端高效開發(fā)實(shí)戰(zhàn)已在亞馬遜京東當(dāng)當(dāng)開售。 作者:Oral (滬江Web前端開發(fā)工程師)本文原創(chuàng)翻譯,有不當(dāng)?shù)牡胤綒g迎指出。轉(zhuǎn)載請(qǐng)指明出處。 當(dāng)你問起有關(guān)AJAX與React時(shí),老司機(jī)們首先就會(huì)...
摘要:在一個(gè)應(yīng)用中,如何通過和端進(jìn)行交互這個(gè)問題曾經(jīng)困擾了我一段時(shí)間,經(jīng)過學(xué)習(xí)實(shí)踐,有了一點(diǎn)心得體會(huì),寫出來和大家分享一下。組件和一樣,和進(jìn)行交互,將獲取的通過向下傳遞給組件。不足被設(shè)計(jì)用來和服務(wù)器一起運(yùn)行,并不能很好的和第三方服務(wù)交互。 在一個(gè)react應(yīng)用中,如何通過ajax和server端進(jìn)行交互這個(gè)問題曾經(jīng)困擾了我一段時(shí)間,經(jīng)過學(xué)習(xí)實(shí)踐,有了一點(diǎn)心得體會(huì),寫出來和大家分享一下。 總的...
閱讀 2890·2021-08-20 09:37
閱讀 1616·2019-08-30 12:47
閱讀 1101·2019-08-29 13:27
閱讀 1692·2019-08-28 18:02
閱讀 757·2019-08-23 18:15
閱讀 3094·2019-08-23 16:51
閱讀 938·2019-08-23 14:13
閱讀 2156·2019-08-23 13:05