Relay: fragments (KR translation)

Written by Paul
ย 
Fragment๋Š” Relay์—์„œ ์ฃผ๋œ ํ”ผ์ณ๋“ค ์ค‘ ํ•˜๋‚˜์ด๋‹ค.
์‹ฑ๊ธ€ ์ฟผ๋ฆฌ์˜ ํšจ์œจ์„ฑ์„ ์œ ์ง€ํ•˜๋ฉด์„œ ๊ฐ๊ฐ์˜ ์ปดํฌ๋„ŒํŠธ๋“ค์ด ๊ทธ๋“ค์˜ ์ž์‹ ๋งŒ์˜ ๋ฐ์ดํ„ฐ ์š”๊ตฌ์‚ฌํ•ญ์„ ๊ฐ์ž ์„ ์–ธํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•œ๋‹ค.
์ด ์„น์…˜์—์„œ, ์šฐ๋ฆฌ๋Š” ํ•˜๋‚˜์˜ ์ฟผ๋ฆฌ๋ฅผ ์–ด๋–ป๊ฒŒ ์—ฌ๋Ÿฌ fragment๋“ค๋กœ ๋ถ„ํ•ดํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ๋ณด์—ฌ์ค„ ๊ฒƒ ์ž…๋‹ˆ๋‹ค.

์‹œ์ž‘ํ•˜๊ธฐ์— ์•ž์„œ, Story ์ปดํฌ๋„ŒํŠธ๊ฐ€ story๊ฐ€ ํฌ์ŠคํŒ…๋œ ๋‚ ์งœ๋ฅผ ๋ณด์—ฌ์ฃผ๊ณ  ์‹ถ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ด…์‹œ๋‹ค.
๊ทธ๋ ‡๊ฒŒ ํ•˜๋ ค๋ฉด, ์„œ๋ฒ„๋กœ ๋ถ€ํ„ฐ ๋” ๋งŽ์€ ๋ฐ์ดํ„ฐ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์šฐ๋ฆฌ๋Š” ์ฟผ๋ฆฌ์— ํ•„๋“œ๋ฅผ ์ถ”๊ฐ€ ํ•  ๊ฒƒ ์ž…๋‹ˆ๋‹ค.
ย 
Newsfeed.tsx ๋กœ ๊ฐ€์„œ NewsfeedQuery ๋ฅผ ์ฐพ์•„, ์ƒˆ๋กœ์šด ํ•„๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ด๋ด…์‹œ๋‹ค:
const NewsfeedQuery = graphql` query NewsfeedQuery { topStory { title summary createdAt // Add this line poster { name profilePicture { url } } image { url } } } `;
ย 
์ด์ œ Story.tsx ๋กœ ๊ฐ€์„œ ๋‚ ์งœ๋ฅผ ๋ณด์—ฌ์ฃผ๋„๋ก ๋ณ€๊ฒฝํ•ด ๋ด…์‹œ๋‹ค.
import Timestamp from './Timestamp'; type Props = { story: { createdAt: string; // Add this line ... }; }; export default function Story({story}: Props) { return ( <Card> <PosterByline person={story.poster} /> <Heading>{story.title}</Heading> <Timestamp time={story.createdAt} /> // Add this line <Image image={story.image} /> <StorySummary summary={story.summary} /> </Card> ); }
์ด์ œ ๋‚ ์งœ๊ฐ€ ๋ณด์—ฌ์ ธ์•ผ ํ•ฉ๋‹ˆ๋‹ค. GraphQL ๋•๋ถ„์—, ์šฐ๋ฆฌ๋Š” ์ƒˆ๋กœ์šด ์„œ๋ฒ„์ฝ”๋“œ๋ฅผ ๋ฐฐํฌํ•˜์ง€ ์•Š์•„๋„ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
ย 
ํ•˜์ง€๋งŒ ๋งŒ์•ฝ ์ด๊ฑธ ์ƒ๊ฐํ•ด๋ณธ๋‹ค๋ฉด, Newsfeed.tsx ๋ฅผ ๋ณ€๊ฒฝํ•ด์•ผ ํ•œ๋‹ค๋ฉด? ๋ฆฌ์•กํŠธ ์ปดํฌ๋„ŒํŠธ ์ž์ฒด๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์ง€ ์•Š๋‹ค๋ฉด? ์™œ Newsfeed๊ฐ€ Story ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ•„์š”๋กœ ํ•˜๋Š” ํŠน์ • ๋ฐ์ดํ„ฐ๋ฅผ ์ƒ๊ฐํ•ด์•ผ ํ• ๊นŒ? ๋งŒ์•ฝ ๋ฐ์ดํ„ฐ๊ฐ€ Story์˜ ์ž์‹ ์ปดํฌ๋„ŒํŠธ์— ์˜ํ•ด ์š”๊ตฌ๋œ๋‹ค๋ฉด? ๋งŒ์•ฝ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋‹ค๋ฅธ ์—ฌ๋Ÿฌ๊ณณ์—์„œ ์“ฐ์ด๋Š” ๊ฒƒ์ด๋ผ๋ฉด?
๊ทธ๋ ‡๋‹ค๋ฉด ์šฐ๋ฆฌ๋Š” ๋ฐ์ดํ„ฐ ์š”๊ตฌ์‚ฌํ•ญ์ด ๋ณ€๊ฒฝ๋œ๋‹ค๋ฉด ๋งŽ์€ ์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ณ€๊ฒฝํ•ด์ค˜์•ผ ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.
์ด๊ฑธ ํ”ผํ•˜๊ธฐ ์œ„ํ•ด์„œ ๊ทธ๋ฆฌ๊ณ  ๋‹ค๋ฅธ ๋งŽ์€ ๋ฌธ์ œ๋“ค์€ ํ”ผํ•˜๊ธฐ ์œ„ํ•ด์„œ, ์šฐ๋ฆฌ๋Š” Story๋ฅผ ์œ„ํ•œ ๋ฐ์ดํ„ฐ ์š”๊ตฌ์‚ฌํ•ญ๋“ค์„ Story.tsx ๋กœ ์˜ฎ๊ธธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
Story ์˜ ๋ฐ์ดํ„ฐ ์š”๊ตฌ์‚ฌํ•ญ์„ Story.tsx ๋กœ fragment๋กœ ๋ถ„๋ฆฌํ•ฉ๋‹ˆ๋‹ค. Fragment๋Š” GraphQL์˜ ๊ฐ๊ฐ ๋‚˜๋ˆ ์ง„ ์กฐ๊ฐ๋“ค์ž…๋‹ˆ๋‹ค. Relay ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ์™„์„ฑ๋œ ์ฟผ๋ฆฌ๋“ค๋กœ ๋ชจ์•„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. Fragment๋Š” ๊ฐ๊ฐ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๊ทธ๋“ค ์ž์‹ ๋งŒ์˜ ๋ฐ์ดํ„ฐ ์š”๊ตฌ์‚ฌํ•ญ์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
Story ์˜ ๋ฐ์ดํ„ฐ ์š”๊ตฌ์‚ฌํ•ญ๋“ค์„ ์ด์ œ fragment๋กœ ๋ถ„ํ•ด ํ•ด ๋ด…์‹œ๋‹ค.

Step 1 โ€” Define a fragment

Story.tsx ์— StoryFragment๋ฅผ ์ถ”๊ฐ€ํ•ฉ์‹œ๋‹ค.
import { graphql } from 'relay-runtime'; const StoryFragment = graphql` fragment StoryFragment on Story { title summary createdAt poster { name profilePicture { url } } thumbnail { url } } `;
topStory ์•ˆ์— ์šฐ๋ฆฌ๋Š” ๋ชจ๋“  ์…€๋ ‰์…˜๋“ค์„ ๊ฐ€์ ธ์™”์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ทธ๋“ค์„ ์šฐ๋ฆฌ๋Š” ์ƒˆ๋กœ์šด Fragment ์„ ์–ธ์œผ๋กœ ๋ณต์‚ฌํ–ˆ์Šต๋‹ˆ๋‹ค. ์ฟผ๋ฆฌ์™€ ๋น„์Šทํ•˜๊ฒŒ, fragment๋“ค์€ ์ด๋ฆ„์ด ์žˆ์Šต๋‹ˆ๋‹ค (StoryFragment ), ์šฐ๋ฆฌ๋Š” ์ด๊ฑธ ์ž ์‹œ๋™์•ˆ ์‚ฌ์šฉํ•  ๊ฒƒ์ด๊ณ , ํ•˜์ง€๋งŒ ๊ทธ๋“ค์€ GraphQL ํƒ€์ž…์ธ (Story)๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. โ€œonโ€. ์ด ๋œป์€ ์ด fragment๊ฐ€ Story ๋…ธ๋“œ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ๊ณณ์—์„œ ๋ชจ๋‘ ์“ฐ์ผ ์ˆ˜ ์žˆ๋‹ค๋Š” ์˜๋ฏธ์ž…๋‹ˆ๋‹ค.
ย 

Step 2 โ€” Spread the fragment

Go toย Newsfeed.tsxย and modifyย NewsfeedQueryย to look like this:
const NewsfeedQuery = graphql` query NewsfeedQuery { topStory { ...StoryFragment } } `;
ย 

Step 3 โ€” Call useFragment

import { useFragment } from 'react-relay'; export default function Story({story}: Props) { const data = useFragment( StoryFragment, story, ); return ( <Card> <Heading>{data.title}</Heading> <PosterByline person={data.poster} /> <Timestamp time={data.createdAt} /> <Image image={data.image} /> <StorySummary summary={data.summary} /> </Card> ); }
useFragment ๋Š” ๋‘๊ฐ€์ง€ ์ธ์ž๋ฅผ ๊ฐ€์ง‘๋‹ˆ๋‹ค:
  • Graphql tagged string: ์šฐ๋ฆฌ๊ฐ€ ์ฝ๊ณ  ์‹ถ์€ fragment์˜ literal
  • story object: fragment Key
ย 

Step 4 โ€” Typescript types for fragment refs

import type {StoryFragment$key} from './__generated__/StoryFragment.graphql'; type Props = { story: StoryFragment$key; };

Reusing a Fragment in Multiple Places

fragment๋Š” ๋งํ•ฉ๋‹ˆ๋‹ค, ๊ฐ ๋…ธ๋“œ์—์„œ ์–ด๋–ค ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ๊ธฐ ์›ํ•˜๋Š”์ง€์— ๋Œ€ํ•œ ํŠน์ • ํƒ€์ž…์˜ ๊ทธ๋ž˜ํ”„ ๋…ธ๋“œ๋“ค์ด ์ฃผ์–ด์ง‘๋‹ˆ๋‹ค. fragment ํ‚ค๋Š” ๊ทธ๋ž˜ํ”„์˜ ์–ด๋–ค ๋…ธ๋“œ๊ฐ€ ๋ฐ์ดํ„ฐ๊ฐ€ ์…€๋ ‰ํŠธ ๋˜์—ˆ๋Š”์ง€์— ๋Œ€ํ•ด ํŠน์ •ํ•ฉ๋‹ˆ๋‹ค.

Step 1 โ€” Define the fragment

Image.tsx
import { graphql } from 'relay-runtime'; const ImageFragment = graphql` fragment ImageFragment on Image { url } `;
ย 

Step 2 โ€” Spread the fragment

Story.tsx
const StoryFragment = graphql` fragment StoryFragment on Story { title summary postedAt poster { ...PosterBylineFragment } thumbnail { ...ImageFragment } } `;
PosterByline.tsx
const PosterBylineFragment = graphql` fragment PosterBylineFragment on Actor { name profilePicture { ...ImageFragment } } `;
ย 

Step 3 โ€” Call useFragment

Image.tsx
import { useFragment } from 'react-relay'; import type { ImageFragment$key } from "./__generated__/ImageFragment.graphql"; type Props = { image: ImageFragment$key; ... }; function Image({image}: Props) { const data = useFragment(ImageFragment, image); return <img key={data.url} src={data.url} ... /> }
ย 

Step 4 โ€” Modify once, enjoy everywhere

const ImageFragment = graphql` fragment ImageFragment on Image { url altText } `;
ย 
function Image({image}) { // ... <img alt={data.altText} //... }
ย 
ย 
ย 
ย 
ย 
ย 
ย 
โ† Go home