๐Ÿš€์•„ํด๋กœ ๋ฐ˜์‘ | ๋‚™๊ด€์  ๋ฐ˜์‘! ๐Ÿ”ฎ

12204 ๋‹จ์–ด apolloreactgraphqlwebdev
Apollo Client์—์„œ ์ œ๊ฐ€ ๊ฐ€์žฅ ์ข‹์•„ํ•˜๋Š” ๊ฒƒ ์ค‘ ํ•˜๋‚˜๋Š” ์บ์‹ฑ์ž…๋‹ˆ๋‹ค. ๋ถˆํ•„์š”ํ•œ ๋„คํŠธ์›Œํฌ ํ˜ธ์ถœ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ์ด๋ฏธ ๊ฐ€์ ธ์˜จ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜์—ฌ ๋ณด๋‹ค ๋ฐ˜์‘์ด ๋น ๋ฅธ React ์•ฑ์„ ๋นŒ๋“œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!

๋‚™๊ด€์  ๋ฐ˜์‘์ด๋ž€?



๋‚™๊ด€์  ์‘๋‹ต์€ ์„œ๋ฒ„์˜ ์‘๋‹ต์„ ๋ฐ›๊ธฐ ์ „์— ํ”„๋ŸฐํŠธ์—”๋“œ์— ์„ ์ œ์ ์œผ๋กœ ๋ชจ๋“  ๋ณ€ํ˜•์— ์‘๋‹ตํ•˜๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. ์–ด๋–ป๊ฒŒ ๋ณด๋ฉด ์šฐ๋ฆฌ๋Š” ๋ฏธ๋ž˜๋ฅผ ์˜ˆ์ธกํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค!!!



Apollo์˜ ์บ์‹ฑ์„ ํ™œ์šฉํ•˜์—ฌ ๋ฐฑ์—”๋“œ๊ฐ€ ๋‹ค์‹œ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ณ  ์žˆ๋Š” ์บ์‹œ ๊ธฐ๋ฐ˜์„ ๋‹ค์‹œ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!

์บ์‹œ ์—…๋ฐ์ดํŠธ


useMutation ํ›„ํฌ๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ ๋‘ ๋ฒˆ์งธ ์ธ์ˆ˜๋Š” ์—ฌ๋Ÿฌ ์ฝœ๋ฐฑ์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ๋Š” ์˜ต์…˜์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. onError , onComplete , variables , update ๋“ฑ ๋„คํŠธ์›Œํฌ ํ˜ธ์ถœ ์ƒํƒœ์— ๋”ฐ๋ผ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ์œ„์น˜

์—ฌ๊ธฐ๊ฐ€ ์บ์‹œ๋ฅผ ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ๋Š” ์™„๋ฒฝํ•œ ์žฅ์†Œ์ž…๋‹ˆ๋‹ค.

๋‚™๊ด€์  ๋ฐ˜์‘



๊ทธ๋Ÿฌ๋‚˜ ๋จผ์ € optimisticResponse ๋ฅผ ์ „๋‹ฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์บ์‹œ์— ๊ธฐ๋กํ•˜๊ธฐ ์œ„ํ•ด ์ œ๊ณตํ•  ์„ ์ œ์  ๋ฐ์ดํ„ฐ์ž…๋‹ˆ๋‹ค.

I usually separate where I declare my variables and optimisticResponse to the mutation function. Like so.



const [post, postRes] = useMutation(CREATE_POST)

const submitHandler = (e) => {
    e.preventDefault()
    if (body.trim() !== '') {
    // Mutation Function  
    post({  
      variables: { body },
  // Here is where I like to declare what my 'Post' should look like  
        optimisticResponse: {
          createPost: {
            body,
            username: session!.username,
            createdAt: new Date().toISOString(),
            comments: [],
            likes: [],
            // You will have to assign a temporary Id. Like so
            id: 'Temp_ID',
            __typename: 'Post',
          },
        },
      })
    }
  }


NOTE: The temporary id you've assigned will be rewritten after the actual response comes back, assuming the network call is successful. So be wary of rerenders!



์ด์ œ ์บ์‹œ์— ์“ฐ๊ธฐ ์œ„ํ•ด ํ•ด๋‹น ํ•ญ๋ชฉoptimisticResponse์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
update ์ฝœ๋ฐฑ์„ ์‚ฌ์šฉํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. mutate ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•˜๋Š” ์ˆœ๊ฐ„ ํŠธ๋ฆฌ๊ฑฐ๋ฉ๋‹ˆ๋‹ค.

const [post, postRes] = useMutation(
    CREATE_POST,
    {
      // data is where we can access the optimisticResponse we passed in earlier 
      update: (cache, { data }) => {
        // Get the current cached data. 
        const existingPosts = client.readQuery({
         // The cached query key is the same as the name of the GQL schema
          query: GET_POSTS,
        })
        // Now we combine the optimisticResponse we passed in earlier and the existing data
        const newPosts = [data.createPost, ...existingPosts.getPosts]
        // Finally we overwrite the cache
        cache.writeQuery({
          query: GET_POSTS,
          data: { getPosts: newPosts },
        })
      }
    }
  )


GraphQL ์กฐ๊ฐ์„ ์‚ฌ์šฉํ•˜์—ฌ ์—…๋ฐ์ดํŠธ



์ด์ œ ๊ฒฝ์šฐ์— ๋”ฐ๋ผ ๋‹จ์ผ ํ•ญ๋ชฉ์„ ์—…๋ฐ์ดํŠธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์œ„์˜ ์˜ˆ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋ฉด ์ƒ๋‹นํžˆ ๋น„์šฉ์ด ๋งŽ์ด ๋“ญ๋‹ˆ๋‹ค! ์ด๋ฅผ ๋‹ฌ์„ฑํ•˜๋ ค๋ฉด GQL Fragments์˜ ๋„์›€์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

GraphQL ์กฐ๊ฐ์ด๋ž€ ๋ฌด์—‡์ž…๋‹ˆ๊นŒ?



๋‹จ์ˆœํžˆ ์—ฌ๋Ÿฌ ์ฟผ๋ฆฌ์™€ ๋ณ€ํ˜• ๊ฐ„์— ๊ณต์œ ํ•  ์ˆ˜ ์žˆ๋Š” ๋…ผ๋ฆฌ ์กฐ๊ฐ์ž…๋‹ˆ๋‹ค. GQL ์Šคํ‚ค๋งˆ์—์„œ ๋ฐ˜๋ณต๋˜๋Š” ํŒจํ„ด์„ ์ถ”์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

In this example most of my queries and mutations has this repeating schema for a Post. It would be better to put it on a fragment like this.



import { gql } from '@apollo/client'

export const PostFragment = gql`
  fragment PostParts on Post {
    id
    body
    createdAt
    username
    likes {
      username
    }
    comments {
      id
      body
      username
      createdAt
    }
  }
`


And apply it to every query and mutation that needs this. Like so.



// Spreading it like an object
export const COMMENT_POST = gql`
  ${PostFragment}
  mutation CommentPost($postId: ID!, $body: String!) {
    createComment(postId: $postId, body: $body) {
      ...PostParts
    }
  }
`


์—…๋ฐ์ดํŠธํ•  ํ•ญ๋ชฉ์„ ๊ฐœ๋ณ„์ ์œผ๋กœ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋„๋ก ์กฐ๊ฐ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

Fragment๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์บ์‹œ ์—…๋ฐ์ดํŠธ




 const [comment, commentRes] = useMutation(COMMENT_POST, {
    update: (cache, { data }) => {
      // We will take the post we wanted to update using readFragment. 
      const post = cache.readFragment({
        // From here we pass in the Id next to whatever you named
        // your cached data. Then the name of the fragment you've created.
        id: `Post:${data?.createComment.id}`,
        fragment: PostFragment,
      })

      // Then, we update it using writeFragment.
      cache.writeFragment({
      // Same as when you read the individual item
        id: `Post:${data?.createComment.id}`,
        fragment: PostFragment,
      // Updating the contents
      // In this example I'm just spreading the existing contents, then rewriting the comments with the data we passed from the optimisticResponse.
        data: {
          ...post,
          comments: data?.createComment.comments,
        },
      })
    }
  })


์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์บ์‹œ๋œ ์ „์ฒด ๋ฐ์ดํ„ฐ๋ฅผ ๋ฎ์–ด์“ฐ๋Š” ๋Œ€์‹  ์›ํ•˜๋Š” ํ•ญ๋ชฉ์„ ํ„ฐ์น˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!

์ด์ œ ์„œ๋ฒ„์— ๋Œ€ํ•œ ํ™•์‹ ์ด ์žˆ๋‹ค๋ฉด ๊ฑฐ์˜ ์ฆ‰๊ฐ์ ์ธ UX๋ฅผ ๊ฐ–๊ฒŒ ๋ฉ๋‹ˆ๋‹ค!



๊ฒฐ๋ก 



๋น„๋™๊ธฐ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐฐ์šฐ๊ธฐ ์‹œ์ž‘ํ–ˆ์„ ๋•Œ ๋กœ๋”ฉ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ๊ตฌํ˜„ํ•  ๋•Œ ๋„ˆ๋ฌด ๋†€๋ž์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๋‹น์‹ ์˜ ์‚ฌ์šฉ์ž๊ฐ€ ์–ด๋””์—์„œ๋‚˜ ๋ณผ ์ˆ˜ ์žˆ๊ณ  UX๊ฐ€ ๋Š๋ฆฌ๊ฒŒ ๋Š๊ปด์ง€๋„๋ก ๊ท€์ฐฎ๊ฒŒ ๋งŒ๋“œ๋Š” ๊ฒฝ์šฐ.

UI์— ์„ ์ œ์ ์œผ๋กœ ๋ฌด์–ธ๊ฐ€๋ฅผ ์ œ๊ณตํ•˜๋ฉด ์•ฑ์˜ ๋ฐ˜์‘์„ฑ์— ๋งŽ์€ ์ฐจ์ด๊ฐ€ ์ƒ๊น๋‹ˆ๋‹ค. GraphQL์šฉ Apollo์™€ RestAPI์šฉ React Query๋Š” ๋ชจ๋‘ ์ œ ์„ ํƒ์ด ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค!

์ข‹์€ ์›นํŽ˜์ด์ง€ ์ฆ๊ฒจ์ฐพ๊ธฐ