Slide 1

Slide 1 text

! # @zetavg
 fb.me/pokaichang72 串串起前後端資料的橋樑
 GraphQL 
 & Relay

Slide 2

Slide 2 text

Q: 這個 app 需要多少 API Endpoints?

Slide 3

Slide 3 text

Ans: 4 GET /api/todos.json POST /api/todos.json PATCH /api/todos/id.json DELETE /api/todos/id.json

Slide 4

Slide 4 text

Q: 那這個 app 需要多少 API Endpoints 呢?

Slide 5

Slide 5 text

/api/v1/posts.json /api/v2/posts.json /api/v3/posts.json /api/v4/posts.json /api/v65535/posts.json ⋯⋯

Slide 6

Slide 6 text

/api/posts.json /api/posts.json?include=author /api/posts.json?include=author,comments /api/posts.json?cover=true&include=author,comments /api/posts.json?cover=true&include=author,comments_with

Slide 7

Slide 7 text

API 應該是這樣

Slide 8

Slide 8 text

不是這樣

Slide 9

Slide 9 text

Q: 那這個 app 需要多少 API Endpoints 呢?

Slide 10

Slide 10 text

Ans: 1

Slide 11

Slide 11 text

Ans: 1 GraphQL

Slide 12

Slide 12 text

$ { "name": "Neson", "bio":"Yet another geek.", "followers": [◌, ◌, ◌], "repos": [◌, ◌, ◌]
 } $ { "name": "Lucy", "bio":"...", "followers": [◌], "repos": [◌, ◌]
 } $ { "name": "Ja "bio":"..." "followers" "repos": [◌ } $ { "name": "Pusheen", "bio":"Nyan nyan nyan~", "followers": [◌], "repos": [◌]
 } ! { "name": "Thing Compiler", "description": "...", "stargazers": [◌, ◌, ◌]
 } ! { "name": "Hello World", "description": "...", "stargazers": [◌, ◌]
 } ! { "name": "Handy Ut "description": ". "stargazers": [◌] } ! { "name": "Awes "description" "stargazers": } ! { "name": "Todo", "description": "...", "stargazers": [◌]
 } ! { } { "viewer": ◌
 } GraphQL

Slide 13

Slide 13 text

$ { "name": "Neson", "bio":"Yet another geek.", "followers": [◌, ◌, ◌], "repos": [◌, ◌, ◌]
 } $ { "name": "Lucy", "bio":"...", "followers": [◌], "repos": [◌, ◌]
 } $ { "name": "Ja "bio":"..." "followers" "repos": [◌ } $ { "name": "Pusheen", "bio":"Nyan nyan nyan~", "followers": [◌], "repos": [◌]
 } ! { "name": "Thing Compiler", "description": "...", "stargazers": [◌, ◌, ◌]
 } ! { "name": "Hello World", "description": "...", "stargazers": [◌, ◌]
 } ! { "name": "Handy Ut "description": ". "stargazers": [◌] } ! { "name": "Awes "description" "stargazers": } ! { "name": "Todo", "description": "...", "stargazers": [◌]
 } ! { } { "viewer": ◌
 }

Slide 14

Slide 14 text

$ { "name": "Pusheen", "bio":"Nyan nyan nyan~", "followers": [◌], "repos": [◌]
 } $ { "name" "bio": "follo "repos } ! ! { "name": "Todo", "description": "...", "stargazers": [◌]
 } ! { "name": "Thing Compiler", "description": "...", "stargazers": [◌, ◌, ◌]
 } ! { "name": "Thi "description "stargazers" } ! { } { "viewer": ◌
 } $ { "name": "Neson", "bio":"Yet another geek.", "followers": [◌, ◌, ◌], "repos": [◌, ◌, ◌]
 } $ d d d d d d d d

Slide 15

Slide 15 text

! { "name": "Thing Compiler", "description": "...", "stargazers": [◌, ◌, ◌]
 } ! { "name": "Thi "description "stargazers" } ! ! ! { "name": "Todo", "description": "...", "stargazers": [◌]
 } { } { "viewer": ◌
 } $ { "name": "Neson", "bio":"Yet another geek.", "followers": [◌, ◌, ◌], "repos": [◌, ◌, ◌]
 } $ { "name" "bio": "follo "repos } $ { "name": "Pusheen", "bio":"Nyan nyan nyan~", "followers": [◌], "repos": [◌]
 } $ $ $ $ $ $

Slide 16

Slide 16 text

! ! { "name": "Thi "description "stargazers" } $ { "name": "Pusheen", "bio":"Nyan nyan nyan~", "followers": [◌], "repos": [◌]
 } ! ! { "name": "Todo", "description": "...", "stargazers": [◌]
 } ! { "name": "Thing Compiler", "description": "...", "stargazers": [◌, ◌, ◌]
 } { } { "viewer": ◌
 } $ $ $ $ $ $ $ $ $ $ $ / $ { "name" "bio": "follo "repos } $ { "name": "Neson", "bio":"Yet another geek.", "followers": [◌, ◌, ◌], "repos": [◌, ◌, ◌]
 }

Slide 17

Slide 17 text

⬡ GraphQL 簡介 ⬡ Relay ⼿手感評測 ⬡ Relay 基本使⽤用 ⬡ 全端案例例:GraphQL & Relay on Rails
 https:/ /github.com/zetavg/RailsRelayTodoMVC 不會講到跟 Apollo 的比較 Outline

Slide 18

Slide 18 text

⬡ GraphQL 簡介 ⬡ Relay ⼿手感評測 ⬡ Relay 基本使⽤用 ⬡ 全端案例例:GraphQL & Relay on Rails
 https:/ /github.com/zetavg/RailsRelayTodoMVC 不會講到跟 Apollo 的比較 Outline

Slide 19

Slide 19 text

基本查詢 ⬢ 最外層⼀一定是 query ⬢ 問什什麼得什什麼 { "data": { "viewer": { "name": "Pokai Chang" } } } query { viewer { name } }

Slide 20

Slide 20 text

查詢更更多 ⬢ 巢狀狀選取欄欄位 (field) { "data": { "viewer": { "name": "Pokai Chang", "birthday": { "month": 7, "day": 2 } } } } query { viewer { name birthday { month day } } }

Slide 21

Slide 21 text

巢狀狀查更更多 ⬢ 底下的資料 (node) 可能是同⼀一種類型 (type) { "data": { "viewer": { "name": "Pokai Chang", "following": [ { "name": "..." }, { "name": "..." }, { "name": "..." } ] } } } query { viewer { name following { name } } }

Slide 22

Slide 22 text

巢狀狀查更更多多多多 ⬢ 可以幹奇怪的事⋯⋯ { "data": { "viewer": { "name": "Pokai Chang", "following": [ { "name": "…", "followers": [ { "name": "Pokai Chang", "following": [ { "name": "…", "followers": [ { "name": "Pokai C query { viewer { name following { name followers { following { name followers { name } } } } } }

Slide 23

Slide 23 text

型別定義即⽂文件 query { viewer { name birthday { month day } following { name } } } type Query { viewer: User } type User { name: String birthday: Date followers: [User] following: [User] } type Date { year: Integer month: Integer day: Integer }

Slide 24

Slide 24 text

$ { "name": "Neson", "bio":"Yet another geek.", "followers": [◌, ◌, ◌], "repos": [◌, ◌, ◌]
 } $ { "name": "Lucy", "bio":"...", "followers": [◌], "repos": [◌, ◌]
 } $ { "name": "Ja "bio":"..." "followers" "repos": [◌ } $ { "name": "Pusheen", "bio":"Nyan nyan nyan~", "followers": [◌], "repos": [◌]
 } ! { "name": "Thing Compiler", "description": "...", "stargazers": [◌, ◌, ◌]
 } ! { "name": "Hello World", "description": "...", "stargazers": [◌, ◌]
 } ! { "name": "Handy Ut "description": ". "stargazers": [◌] } ! { "name": "Awes "description" "stargazers": } ! { "name": "Todo", "description": "...", "stargazers": [◌]
 } ! { } { "viewer": ◌
 }

Slide 25

Slide 25 text

$ { "name" "bio": "follo "repos } $ { "name": "Pusheen", "bio":"Nyan nyan nyan~", "followers": [◌], "repos": [◌]
 } ! { "name": "Thing Compiler", "description": "...", "stargazers": [◌, ◌, ◌]
 } ! { "name": "Thi "description "stargazers" } ! ! ! { "name": "Todo", "description": "...", "stargazers": [◌]
 } { } { "viewer": ◌
 } $ { "name": "Neson", "bio":"Yet another geek.", "followers": [◌, ◌, ◌], "repos": [◌, ◌, ◌]
 } query { viewer { name bio } }

Slide 26

Slide 26 text

! { "name": "Thing Compiler", "description": "...", "stargazers": [◌, ◌, ◌]
 } ! { "name": "Thi "description "stargazers" } ! ! ! { "name": "Todo", "description": "...", "stargazers": [◌]
 } { } { "viewer": ◌
 } $ { "name": "Neson", "bio":"Yet another geek.", "followers": [◌, ◌, ◌], "repos": [◌, ◌, ◌]
 } $ { "name" "bio": "follo "repos } $ { "name": "Pusheen", "bio":"Nyan nyan nyan~", "followers": [◌], "repos": [◌]
 } query { viewer { name bio followers { name } } }

Slide 27

Slide 27 text

! ! { "name": "Todo", "description": "...", "stargazers": [◌]
 } $ { "name" "bio": "follo "repos } $ { "name": "Pusheen", "bio":"Nyan nyan nyan~", "followers": [◌], "repos": [◌]
 } ! { "name": "Thing Compiler", "description": "...", "stargazers": [◌, ◌, ◌]
 } ! { "name": "Thi "description "stargazers" } ! { } { "viewer": ◌
 } $ { "name": "Neson", "bio":"Yet another geek.", "followers": [◌, ◌, ◌], "repos": [◌, ◌, ◌]
 } query { viewer { name bio followers { name } repos { name stargazers { name } } } }

Slide 28

Slide 28 text

{ } { "viewer": ◌
 } ! ! { "name": "Todo", "description": "...", "stargazers": [◌]
 } $ { "name": "Neson", "bio":"Yet another geek.", "followers": [◌, ◌, ◌], "repos": [◌, ◌, ◌]
 } $ { "name" "bio": "follo "repos } $ { "name": "Pusheen", "bio":"Nyan nyan nyan~", "followers": [◌], "repos": [◌]
 } ! { "name": "Thing Compiler", "description": "...", "stargazers": [◌, ◌, ◌]
 } ! { "name": "Thi "description "stargazers" } ! query { viewer { name bio followers { name } repos { name stargazers { name } } girlfriend { name } } } '

Slide 29

Slide 29 text

query { viewer { name bio followers { name } repos { name stargazers { name } } girlfriend { name } } } { "errors": [ { "message": "Field 'girlfriend' doesn't
 exist on type ‘User’", ... } ] } '

Slide 30

Slide 30 text

Arguments 參參數 ⬢ 每個 field 會定義可⽤用的參參數 query { user(id: 1) { name } }

Slide 31

Slide 31 text

Arguments 參參數 ⬢ 也可以做巢狀狀查詢 query { user(id: 1) { name repo(name: "awesome-graphql") { name description } } }

Slide 32

Slide 32 text

Fragment 片段 fragment profileFields on User { name bio avatarUrl } query { viewer { ...profileFields } user(id: 1) { ...profileFields } } 先把固定會⽤用到的欄欄位 存成有意義的片段

Slide 33

Slide 33 text

改資料 ? Mutation ⬢ query 改成 mutation,data 放在 arguments ⬢ 其實就像 HTTP GET/POST,只是慣例例,沒有硬性限制 mutation { addComment(input: { subjectId: 1, body: "Hi." }) { subject { comments { body } } } }

Slide 34

Slide 34 text

graphql.org

Slide 35

Slide 35 text

⬡ GraphQL 簡介 ⬡ Relay ⼿手感評測 ⬡ Relay 基本使⽤用 ⬡ 全端案例例:GraphQL & Relay on Rails
 https:/ /github.com/zetavg/RailsRelayTodoMVC 不會講到跟 Apollo 的比較 Outline

Slide 36

Slide 36 text

無限捲軸 Caching Prefetch Caching Server Data Updating Optimistic Update Realtime Update

Slide 37

Slide 37 text

facebook.github.io/relay

Slide 38

Slide 38 text

Relay
 
 Relay Modern (Relay 1.0)

Slide 39

Slide 39 text

No content

Slide 40

Slide 40 text

const View = (data) => UI

Slide 41

Slide 41 text

Redux 資料流 View State subscribe Redux Store

Slide 42

Slide 42 text

Redux 資料流 View State Reducer Action subscribe prevState

Slide 43

Slide 43 text

Redux 資料流 View State Reducer Action subscribe prevState Backend ?

Slide 44

Slide 44 text

Redux 資料流 ? View State Reducer Action subscribe prevState Backend Action Action Action

Slide 45

Slide 45 text

Redux 資料流 ? View Action Action Action Action Backend State State State Reducer Reducer Reducer Reducer ??? ???

Slide 46

Slide 46 text

$ { "name": "Neson", "bio":"Yet another geek.", "followers": [◌, ◌, ◌], "repos": [◌, ◌, ◌]
 } $ { "name": "Lucy", "bio":"...", "followers": [◌], "repos": [◌, ◌]
 } $ { "name": "Ja "bio":"..." "followers" "repos": [◌ } $ { "name": "Pusheen", "bio":"Nyan nyan nyan~", "followers": [◌], "repos": [◌]
 } ! { "name": "Thing Compiler", "description": "...", "stargazers": [◌, ◌, ◌]
 } ! { "name": "Hello World", "description": "...", "stargazers": [◌, ◌]
 } ! { "name": "Handy Ut "description": ". "stargazers": [◌] } ! { "name": "Awes "description" "stargazers": } ! { "name": "Todo", "description": "...", "stargazers": [◌]
 } ! { } { "viewer": ◌
 } GraphQL

Slide 47

Slide 47 text

Relay 的狀狀況 View $ $ $ $ $ d d $ $ $ $ d d d d d d Relay Store

Slide 48

Slide 48 text

Relay 的狀狀況 View $ $ $ $ d d $ $ $ $ d d d d d d Relay Store viewer { name bio } $ Backend query { viewer { name bio } } { "data": { "viewer": { "name": "…", "Bio": "…" } } }

Slide 49

Slide 49 text

Relay 的狀狀況 View ㄎ ㄎ ㄎ ㄎ $ $ $ $ d d $ $ $ $ d d d d d d Relay Store viewer { name bio } $

Slide 50

Slide 50 text

Relay 的狀狀況 View ㄎ ㄎ ㄎ ㄎ d d d d d d d d $ $ $ $ $ $ $ viewer { name bio } $ $ viewer { followers { name } } Backend query { viewer { followers { name } } } { "data": { "viewer": { "followers": […] } } } Relay Store Request only the diff!

Slide 51

Slide 51 text

Relay 的狀狀況 View ㄎ ㄎ ㄎ ㄎ d d d d d d d d $ $ $ $ $ $ $ viewer { name bio } $ $ viewer { followers { name } }

Slide 52

Slide 52 text

Relay 的狀狀況 viewer { name bio } View $ ㄎ ㄎ ㄎ ㄎ $ $ $ $ $ $ $ $ viewer { followers { name } } d d d d d d d d viewer { repos { name description } } Backend query { viewer { repos { name description } } } { "data": { "viewer": { "repos": […] } } }

Slide 53

Slide 53 text

Relay 的狀狀況 viewer { name bio } View $ ㄎ ㄎ ㄎ ㄎ $ $ $ $ $ $ $ $ viewer { followers { name } } d d d d d d d d viewer { repos { name description } }

Slide 54

Slide 54 text

Relay 的狀狀況 View $ ㄎ ㄎ ㄎ ㄎ $ $ $ $ $ $ $ $ d d d d d d d d RenameRepoMutation( repoID: "…", name: "" )

Slide 55

Slide 55 text

Relay 的狀狀況 View $ ㄎ ㄎ ㄎ ㄎ $ $ $ $ $ $ $ $ d d d d d d d d RenameRepoMutation( repoID: "…", name: "" ) Optimistic Updater

Slide 56

Slide 56 text

Relay 的狀狀況 View $ ㄎ ㄎ ㄎ ㄎ $ $ $ $ $ $ $ $ d d d d d d d d RenameRepoMutation( repoID: "…", name: "" ) Optimistic Updater Backend Updater

Slide 57

Slide 57 text

Relay 的狀狀況 View $ ㄎ ㄎ ㄎ ㄎ $ $ $ $ $ $ $ $ d d d d d d d d RenameRepoMutation( repoID: "…", name: "" ) Optimistic Updater Backend Updater

Slide 58

Slide 58 text

想分⾴頁 ? Connections ⬢ 游標分⾴頁法:Relay Cursor Connections query { viewer { friends(first: 10, after: "someCursor") { edges { cursor node { id name } } pageInfo { hasNextPage } } } } Edge (UserEdgeType) Node (UserType) { … } Cursor 游標 Connection Edges Edge (UserEdgeType) Node (UserType) { … } Cursor Edge (UserEdgeTy Node (UserType { … } Cursor Page Info 起始游標

Slide 59

Slide 59 text

⬢ Offset based pagination ⬢ Cursor based pagination Why Cursor? page 1 page 2 page 1 page 2 page 3 page 3 ' 壞ㄌ next 5 next 5

Slide 60

Slide 60 text

⬡ Relay 可信的 Optimistic Updater 機制可以做 「連線問題時重試」甚⾄至離線更更動上線後同步 ⬡ Relay 的 backend 不⼀一定要是 server,也許可以 是裝置上的 database ⬡ 再狂⼀一點,也許我們還能透過⾃自⼰己實作 Relay RecordSource 組出這樣的架構: Fun Things to Try Relay Relay View Server Client DB Memory

Slide 61

Slide 61 text

Optimistic Update Management

Slide 62

Slide 62 text

No content

Slide 63

Slide 63 text

No content

Slide 64

Slide 64 text

No content

Slide 65

Slide 65 text

No content

Slide 66

Slide 66 text

{ 歡樂 Demo 時間 }

Slide 67

Slide 67 text

適⽤用情況 ⬢ Data driven,data 越多越雜,投資越划算 ⬢ React app ⬢ 略略懂後端 ⬢ 踩雷的勇氣 Relay Environment Handler Provider Network Layer Store Record Source GraphQL Endpoint Query Renderer Container Component Fragment Query

Slide 68

Slide 68 text

跟後端不熟?

Slide 69

Slide 69 text

⬡ GraphQL 簡介 ⬡ Relay ⼿手感評測 ⬡ Relay 基本使⽤用 ⬡ 全端案例例:GraphQL & Relay on Rails
 https:/ /github.com/zetavg/RailsRelayTodoMVC 不會講到跟 Apollo 的比較 Outline

Slide 70

Slide 70 text

在 React 專案中安裝 Relay $ yarn add react-relay

Slide 71

Slide 71 text

安裝 GraphQL 語法識別外掛 $ yarn add --dev babel-plugin-relay

Slide 72

Slide 72 text

記得⼿手動加 Babel Plugin ⬢ 編輯 .eslintrc 或 package.json "plugins": [ "relay", ]

Slide 73

Slide 73 text

安裝 Relay GraphQL 編譯器 $ yarn add --dev relay-compiler

Slide 74

Slide 74 text

使⽤用 relay-compiler ⬢ 在 JavaScript 裡寫的 GraphQL 需要被事先編譯⋯⋯
 
 ⬢ 改了了 graphql`…` tag 裡⾯面的內容後都要跑⼀一次 ⬢ 或是加上 --watch 參參數監看變化 $ relay-compiler --src ./src --schema ./schema.graphql 源碼⽬目錄 GraphQL Schema 檔位置

Slide 75

Slide 75 text

import { Environment } from ‘relay-runtime’ const environment = new Environment({ network, store, }) export default environment 環境準備 Relay Environment Handler Provider Network Layer Store Record Source GraphQL Endpoint Network Layer Store ⬢ 先看整個環境⋯⋯

Slide 76

Slide 76 text

import { Network } from ‘relay-runtime' const API_ENDPOINT = 'https://api.github.com/graphql' const fetchQuery = (operation, variables) => { return fetch(API_ENDPOINT, { method: 'POST', body: JSON.stringify({ query: operation.text, variables, }), }).then(response => response.json()) } const network = Network.create(fetchQuery) exports default network Relay 網路路層 ⬢ 給⼀一個溝通介⾯面,⼀一般就是包裝 fetch Relay Environment Handler Provider Network Layer Store Record Source GraphQL Endpoint

Slide 77

Slide 77 text

import { RecordSource, Store, } from 'relay-runtime' const source = new RecordSource() const store = new Store(source) export default store Relay 倉儲 Relay Environment Handler Provider Network Layer Store Record Source GraphQL Endpoint ⬢ Relay 內建具備 garbage collection 的 in-memory record source

Slide 78

Slide 78 text

以上,前置準備完成

Slide 79

Slide 79 text

const MyProfile = () => ( { if (error) { return {error.message} } else if (props) { return Hello, {props.viewer.name}! } return Loading... }} /> ) 基本招 QueryRenderer 可以設計載入中假畫⾯面 注意名字有規定⽤用 "[檔案名稱]Query" Relay Environment Handler Provider Network Layer Store Record Source GraphQL Endpoint Query Renderer Component Query

Slide 80

Slide 80 text

const UserProfileComponent = ({ user }) => ( Hello, {user.name}! ) const UserProfile = createFragmentContainer( UserProfileComponent, graphql` fragment UserProfile_user on User { name login avatarUrl } `, ) export default UserProfile 拆元件 FragmentContainer 注意名字也有規定⽤用 "[檔案名稱]_[prop 名稱]" Relay Environment Store Query Renderer Container Component Fragment Query

Slide 81

Slide 81 text

拆元件 FragmentContainer 直接引⽤用定義在 Container 裡的 Fragment Relay Environment Store Query Renderer Container Component Fragment Query import MyProfile from '...' const MyProfile = () => ( { if (error) { return {error.message} } else if (props) { return } return Loading... }} /> )

Slide 82

Slide 82 text

Component Tree 層層疊疊 Query Renderer Container Fragment Query Container Container Component Component Fragment Fragment const UserProfileComponent = ({ user }) => ( ) const UserProfile = createFragmentContainer( UserProfileComponent, graphql` fragment UserProfile_user on User { ...UserName_user ...UserBio_user } `, ) const UserNameComponent = ({ user }) => ( Hello, {user.name}! ) const UserName = createFragmentContainer( UserNameComponent, graphql` fragment UserName_user on User { name avatarUrl } `, ) const UserBioComponent = ({ user }) => ( {user.bio} ) const UserBio = createFragmentContainer( UserBioComponent, graphql` fragment UserBio_user on User { bio } `, )

Slide 83

Slide 83 text

特種 Container ⬢ RefetchContainer ⬡ 提供 instance method 可以改⽤用不同 arguments 索取資料 ⬢ PaginationContainer ⬡ RefetchContainer 進化版 ⬡ ⽤用 Relay Connection 做分⾴頁專⽤用 ⬡ 可以做出 loadMore()、refresh() 等 method 給無限捲軸和 pull to refresh 使⽤用

Slide 84

Slide 84 text

Mutation const mutation = graphql` mutation AddCommentMutation( $input: AddCommentInput! ) { addComment(input: $input) { clientMutationId commentEdge { cursor node { id body } } subject { id commentsCount } } } ` let variables = { input: { subjectId: "aGVsbG8=", body: "Hello Relay", } } commitMutation( environment, { mutation, variables, onCompleted: (response) => { console.log('Success!') }, onError: (err) => { console.error(err) }, }, ) 1 2 3 1 2 3 ⼀一樣有命名規定 宣告 $input 變數以及型別,內容可以在使⽤用時再指定 與 query ⼀一樣列列出欄欄位,這裡需要列列出所有 mutation 執⾏行行後會變更更或新增的資料 2

Slide 85

Slide 85 text

⬢ 還有需要做的事⋯⋯ Mutation Updater const mutation = graphql` mutation AddCommentMutation( $input: AddCommentInput! ) { addComment(input: $input) { clientMutationId commentEdge { cursor node { id body } } subject { id commentsCount } } } ` Mutation 執⾏行行後,我們需要把新的 edge 安插到
 某個 subject 底下的 comments connection 若若在 payload root 發現具有 id 的 node,
 Relay 慣例例會幫我們更更新好 store 中相應的 node

Slide 86

Slide 86 text

Mutation Updater commitMutation( environment, { mutation, variables: { ... }, updater: (store) => { const payload = store.getRootField('addComment') const newEdge = payload.getLinkedRecord('commentEdge') const subject = payload.getLinkedRecord('subject') const conn = ConnectionHandler.getConnection(subject, ‘commentsConnection') ConnectionHandler.insertEdgeAfter(conn, newEdge) }, optimisticUpdater: (store) => { … }, }, ) 可⾃自訂 store 的 update ⽅方式

Slide 87

Slide 87 text

Mutation Updater ConnectionHandler.insertEdgeAfter(conn, newEdge) }, optimisticUpdater: (store) => { const subject = store.get(subjectID) const newCommentID = `client:newComment:${tempID++}` const newNode = store.create(newCommentID, 'Comment') newNode.setValue(newCommentID, 'id') newNode.setValue(variables.input.body, 'body') const newEdge = store.create( `client:newCommentEdge:${tempID++}`, 'TodoItemEdge', ) newEdge.setLinkedRecord(newNode, 'node') const conn = ConnectionHandler.getConnection(subject, 'commentsConnection') ConnectionHandler.insertEdgeAfter(conn, newEdge) subject.setValue( subject.getValue('commentsCount') + 1, 'commentsCount', ) }, }, ) 製作出暫時的 node 製作出暫時的 edge 把暫時的 edge 安插進 connection ⼿手動更更新計數器

Slide 88

Slide 88 text

⬡ GraphQL 簡介 ⬡ Relay ⼿手感評測 ⬡ Relay 基本使⽤用 ⬡ 全端案例例:GraphQL & Relay on Rails
 https:/ /github.com/zetavg/RailsRelayTodoMVC 不會講到跟 Apollo 的比較 Outline

Slide 89

Slide 89 text

(GraphQL + Relay) on Rails

Slide 90

Slide 90 text

(GraphQL + Relay) on Rails 全端⼀一天可以寫完,拿來來做 RN 更更顯神⼒力力

Slide 91

Slide 91 text

(GraphQL + Relay) on Rails Debug 省下的時間可以拿來來調各種 1px

Slide 92

Slide 92 text

Image source: https:/ /blog.codinghorror.com/learn-to-read-the-source-luke/ 請⾒見見 https:/ /github.com/zetavg/RailsRelayTodoMVC

Slide 93

Slide 93 text

Thanks + Q&A