Upgrade to Pro — share decks privately, control downloads, hide ads and more …

(LT)ApolloClientとGraphQL Code Generatorの話

(LT)ApolloClientとGraphQL Code Generatorの話

社内でのLT会で使用した資料です。

Avatar for asazu taiga

asazu taiga

March 09, 2022
Tweet

More Decks by asazu taiga

Other Decks in Technology

Transcript

  1. API

  2. ApolloClient , ApolloProvider , Link ApolloClient インスタンスを <ApolloProvider> の client

    引数にセット Link を挟むことでデータフローをカスタマイズ InMemoryCache が自動的に取得データをメモリ上にキャッシュしてくれる 手動更新も可能
  3. const authLink = setContext((_, { headers }) => { const

    token = localStorage.getItem('token') debugger return { headers: { ...headers, authorization: `Bearer ${token}` ?? '', }, } }) const httpLink = new HttpLink({ uri: config.API_URL + '/graphql', }) const client = new ApolloClient({ link: authLink.concat(httpLink), uri: 'http://loclahost:8080/graphql', cache: new InMemoryCache(), }) export const AppApolloProvider: React.VFC<{ children: ReactNode }> = ({ children, }) => { return <ApolloProvider client={client}>{children}</ApolloProvider> }
  4. useQuery gql で作成したquery objectをもとにデータを取得する サーバーのスキーマ定義に則ってください loading , error , data

    を返す 非同期処理を完全にラップするのでView層に Promise が登場しない コンポーネントがマウントされたタイミングでqueryが実行される キャッシュがある場合はキャッシュのデータを利用する
  5. import React from 'react' import { useQuery, gql } from

    '@apollo/client' interface RocketInventory { id: number model: string year: number stock: number } interface RocketInventoryData { rocketInventory: RocketInventory[] } interface RocketInventoryVars { year: number } const GET_ROCKET_INVENTORY = gql` query GetRocketInventory($year: Int!) { rocketInventory(year: $year) { id model year stock } } `
  6. export const RocketInventoryList: React.VFC = () => { const {

    loading, error, data } = useQuery<RocketInventoryData, RocketInventoryVars>( GET_ROCKET_INVENTORY, { variables: { year: 2019 } }, ) if (error) return <>{error.message}</> return ( <div> <h3>Available Inventory</h3> {loading ? ( <p>Loading ...</p> ) : ( <table> <thead> <tr> <th>Model</th> <th>Stock</th> </tr> </thead> <tbody> {data && data.rocketInventory.map((inventory) => ( <tr key={inventory.id}> <td>{inventory.model}</td> <td>{inventory.stock}</td> </tr> ))} </tbody> </table> )} </div> ) }
  7. import React, { useState } from 'react' import { useMutation,

    gql } from '@apollo/client' const SAVE_ROCKET = gql` mutation saveRocket($rocket: RocketInput!) { saveRocket(rocket: $rocket) { model } } ` interface RocketInventory { id: number model: string year: number stock: number } interface NewRocketDetails { model: string year: number stock: number }
  8. export const NewRocketForm: React.VFC = () => { const [model,

    setModel] = useState('') const [year, setYear] = useState(0) const [stock, setStock] = useState(0) const [saveRocket, { loading, error, data }] = useMutation<{ saveRocket: RocketInventory }, { rocket: NewRocketDetails }>( SAVE_ROCKET, { variables: { rocket: { model, year: +year, stock: +stock } }, } ) return ( <div> <h3>Add a Rocket</h3> {error ? <p>Oh no! {error.message}</p> : null} {data && data.saveRocket ? <p>Saved!</p> : null} <form> <p> <label>Model</label> <input name="model" onChange={(e) => setModel(e.target.value)} /> </p> <p> <label>Year</label> <input type="number" name="year" onChange={(e) => setYear(+e.target.value)} /> </p> <p> <label>Stock</label> <input type="number" name="stock" onChange={(e) => setStock(e.target.value)} /> </p> <button onClick={() => model && year && stock && saveRocket()}>Add</button> </form> </div> ) }
  9. local only stateの定義 Cacheのフィールドポリシーで定義することで使えるようになる 例は localStorage に保持しているが他の場所にもおけそうですね const cache =

    new InMemoryCache({ typePolicies: { // Type policy map Product: { fields: { // Field policy map for the Product type isInCart: { // Field policy for the isInCart field read(_, { variables }) { // The read function for the isInCart field return localStorage.getItem('CART').includes( variables.productId ) } } } } } })
  10. codegen.yml schema: ./schema.graphql # or http://localhost:8080/graphql documents: ./graphql/**/*.graphql generates: ./src/generated/graphql.tsx:

    plugins: - typescript - typescript-operations - typescript-react-apollo config: withHOC: false withComponent: false withHooks: true namingConvention: typeNames: change-case#pascalCase enumValues: change-case#pascalCase transformUnderscore: true hooks: afterOneFileWrite: - prettier --write
  11. export enum Linked { AccountNotFound = 'ACCOUNT_NOT_FOUND', Approval = 'APPROVAL',

    ApprovalRequest = 'APPROVAL_REQUEST', Denial = 'DENIAL', Expulsion = 'EXPULSION', PrivateAccount = 'PRIVATE_ACCOUNT', SelfDenial = 'SELF_DENIAL', }
  12. query SnsConnects($first: Int = 10, $linked: Linked = APPROVAL_REQUEST) {

    snsConnects(first: $first, linked: $linked) { pageInfo { hasNextPage hasPreviousPage startCursor endCursor } totalCount edges { cursor node { id name followerCount createdAt userInfluencers { id name image snsConnects { id linked provider } } } } } }
  13. export type SnsConnectsQuery = { __typename?: 'Query' snsConnects?: | {

    __typename?: 'SnsConnectConnection' totalCount?: number | null | undefined pageInfo: { __typename?: 'PageInfo' hasNextPage: boolean hasPreviousPage: boolean startCursor?: string | null | undefined endCursor?: string | null | undefined } edges?: | Array< | { __typename?: 'SnsConnectEdge' cursor: string node?: | { __typename?: 'SnsConnect' id: string name: string followerCount: number createdAt: any userInfluencers: { __typename?: 'UserInfluencer' id: string name?: string | null | undefined image?: string | null | undefined snsConnects: Array<{ __typename?: 'SnsConnect' id: string linked: Linked provider: Provider }> } } | null | undefined } | null | undefined > | null | undefined } | null | undefined }
  14. pluginで useQuery などのラッパーも生成してくれる export function useSnsConnectsQuery( baseOptions?: Apollo.QueryHookOptions< SnsConnectsQuery, SnsConnectsQueryVariables

    >, ) { const options = { ...defaultOptions, ...baseOptions } return Apollo.useQuery<SnsConnectsQuery, SnsConnectsQueryVariables>( SnsConnectsDocument, options, ) }
  15. GraphQL Code Generatorまとめ schemaという共通言語を通して「正しい型」がGraphQL server ⇆ Frontendで共有 自分で gql や

    useQuery を書くことはなくなるかも schemaを取得する必要があるので、monorepoなどの選択肢が出てくる? or schema自体にバージョン埋め込みなどの話も必要になるのかも