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