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

(LT)ApolloClientとGraphQL Code Generatorの話

(LT)ApolloClientとGraphQL Code Generatorの話

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

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自体にバージョン埋め込みなどの話も必要になるのかも