Refreshing and Refetching (part. 2)

ํ”„๋ž˜๊ทธ๋จผํŠธ ๋ฆฌํ”„๋ ˆ์‹ฑ

โ€œrefreshing a fragmentโ€์ด๋ผ๋Š” ๊ฒƒ์€, ๊ธฐ์กด์— ํ”„๋ž˜๊ทธ๋จผํŠธ๋กœ ๋ Œ๋”ํ–ˆ๋˜ ๋ฐ์ดํ„ฐ์™€ ๋˜‘๊ฐ™์€ ๋ฐ์ดํ„ฐ๋ฅผ ํŒจ์นญํ•˜์—ฌ ํ•ด๋‹น ๋ฐ์ดํ„ฐ์˜ ๊ฐ€์žฅ ์ตœ์‹  ๋ฒ„์ „์„ ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ๋ฐ›์•„์˜ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค.

real-time ํŠน์ง• ์ด์šฉํ•˜๊ธฐ

์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ๊ฐ€์žฅ ์ตœ์‹  ๋ฒ„์ „์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ค๊ธฐ ์œ„ํ•ด์„œ ์ฒซ๋ฒˆ์งธ๋กœ ํ•  ์ผ์€, real-time ํ”ผ์ณ๋ฅผ ์ด์šฉํ•˜๋Š” ๊ฒƒ์ด ์ ์ ˆํ•œ์ง€ ํŒ๋‹จํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ์ด๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ๊ธฐ์ ์œผ๋กœ ์ˆ˜๋™์œผ๋กœ ๋ฆฌํ”„๋ ˆ์‹œ ํ•  ํ•„์š” ์—†์ด, ์ž๋™์ ์œผ๋กœ ์ตœ์‹  ๋ฒ„์ „์ด ์œ ์ง€๋˜๋„๋ก ํ•œ๋‹ค.

real-time ๊ธฐ๋Šฅ์˜ ์˜ˆ์‹œ๋กœ๋Š” GraphQL Subscription์ด ์žˆ๋Š”๋ฐ, ์ด๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์„œ๋ฒ„์™€ ๋„คํŠธ์›Œํฌ ๊ณ„์ธต์— ์ถ”๊ฐ€์ ์ธ configuration์ด ํ•„์š”ํ•˜๋‹ค.

useRefetchableFragment

ํ”„๋ž˜๊ทธ๋จผํŠธ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜๋™์ ์œผ๋กœ ๋ฆฌํ”„๋ ˆ์‹œํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š”, ํ”„๋ž˜๊ทธ๋จผํŠธ๋ฅผ ํฌํ•จํ•œ ์ฟผ๋ฆฌ๋ฅผ ๋ฆฌํŒจ์น˜ํ•ด์•ผ ํ•œ๋‹ค. ์ฃผ์˜ํ•  ์ ์€, ํ”„๋ž˜๊ทธ๋จผํŠธ๋Š” ์ฟผ๋ฆฌ ์—†์ด ๊ทธ ์ž์ฒด๋กœ ํŒจ์น˜๋  ์ˆ˜ ์—†๋‹ค. ๋ฐ˜๋“œ์‹œ ์ฟผ๋ฆฌ์˜ ์ผ๋ถ€๋กœ์จ ์กด์žฌํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ์†์‰ฝ๊ฒŒ ํ”„๋ž˜๊ทธ๋จผํŠธ ์ž์ฒด๋ฅผ โ€œํŒจ์น˜"ํ•  ์ˆ˜ ์—†๋‹ค.

ํ”„๋ž˜๊ทธ๋จผํŠธ ๋ฆฌํ”„๋ ˆ์‹œ๋ฅผ ์œ„ํ•ด์„œ๋Š”, useRefetchableFragment ํ›…๊ณผ @refetchable ๋””๋ ‰ํ‹ฐ๋ธŒ๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค. ์ด๋Š” ํ”„๋ž˜๊ทธ๋จผํŠธ๋ฅผ ๋ฆฌํŒจ์น˜ํ•˜๊ธฐ ์œ„ํ•œ ์ฟผ๋ฆฌ๋ฅผ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•˜์—ฌ refetch ๋ฅผ ์‚ฌ์šฉํ•ด ํ”„๋ž˜๊ทธ๋จผํŠธ๋ฅผ ํŒจ์น˜ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค.

import type {UserComponent_user$key} from 'UserComponent_user.graphql';
// @refetchable ์— ๋”ฐ๋ผ ๋ฆด๋ ˆ์ด๊ฐ€ ์ž๋™ ์ƒ์„ฑํ•ด์ค€ ํƒ€์ž…
import type {UserComponentRefreshQuery} from 'UserComponentRefreshQuery.graphql';

type Props = {
  user: UserComponent_user$key,
};

function UserComponent(props: Props) {
  const [data, refetch] = useRefetchableFragment<UserComponentRefreshQuery, _>(
    graphql`
      fragment UserComponent_user on User
			# @refetchable ๊ฐ€ ํ”„๋ž˜๊ทธ๋จผํŠธ๋ฅผ ํŒจ์น˜ํ•˜๊ธฐ ์œ„ํ•ด ๋ฆด๋ ˆ์ด๊ฐ€ ์ฟผ๋ฆฌ๋ฅผ ์ž๋™ ์ƒ์„ฑํ•ด์ค€๋‹ค.
      @refetchable(queryName: "UserComponentRefreshQuery") {
        id
        name
        friends {
          count
        }
      }
    `,
    props.user,
  );

	return (
	  <>
	    <h1>{data.name}</h1>
	    <div>Friends count: {data.friends?.count}</div>
	    <Button
	      onClick={() => refresh()}>
	      Fetch latest count
	    </Button>
	  </>
  );
}
  const refresh = useCallback(() => {
		// ๋นˆ ๋ณ€์ˆ˜ `{}`๋กœ ๋ฆฌํŒจ์น˜ ํ•˜๋Š” ๊ฒฝ์šฐ;
		// ํ”„๋ž˜๊ทธ๋จผํŠธ๋ฅผ ํŒจ์น˜ํ–ˆ๋˜ ๊ธฐ์กด์˜ ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ @refetchable ๋ฅผ ์‚ฌ์šฉํ•œ ์ฟผ๋ฆฌ๋ฅผ ๋ฆฌํŒจ์น˜ ํ•  ์ˆ˜ ์žˆ๊ณ ,
		// ๋”ฐ๋ผ์„œ ๊ฐ€์žฅ ์ตœ์‹ ์— ํŒจ์น˜๋œ ๋ฐ์ดํ„ฐ๋กœ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์—…๋ฐ์ดํŠธ ํ•  ์ˆ˜ ์žˆ๋‹ค.
		// 'network-only' fetchPolicy๋Š” ํ•ญ์ƒ ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ํŒจ์น˜ํ•จ์„ ๋ณด์žฅํ•˜๊ณ  ๋กœ์ปฌ ๋ฐ์ดํ„ฐ ์บ์‹œ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋„๋ก ํ•œ๋‹ค.
    refetch({}, {fetchPolicy: 'network-only'})
  }), [/* ... */];
  • useRefetchableFragment ๋Š” useFragment ์™€ ๋น„์Šทํ•˜๊ฒŒ ์ž‘๋™ํ•˜๋Š”๋ฐ, ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ ์ด ์ถ”๊ฐ€๋œ๋‹ค.

    • @refetchable ๋””๋ ‰ํ‹ฐ๋ธŒ๋กœ ํ‘œ์‹œ๋œ ํ”„๋ž˜๊ทธ๋จผํŠธ๋ฅผ ์˜ˆ์ƒํ•œ๋‹ค. @refetchable ๋””๋ ‰ํ‹ฐ๋ธŒ๋Š” โ€œ๋ฆฌํŒจ์น˜ ๊ฐ€๋Šฅํ•œโ€ ํ”„๋ž˜๊ทธ๋จผํŠธ์—๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ๋ฐ, ์ด๋Š” Viewer / Query / Node ๋ฅผ ์ƒ์†๋ฐ›๋Š” ๋ชจ๋“  ํƒ€์ž… (id ํ•„๋“œ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ํƒ€์ž…) ์— ์กด์žฌํ•˜๋Š” ํ”„๋ž˜๊ทธ๋จผํŠธ๋ฅผ ์˜๋ฏธํ•œ๋‹ค.

  • ์ด๋Š” refetch ํ•จ์ˆ˜๋ฅผ ๋ฆฌํ„ดํ•˜๋Š”๋ฐ,

    • Flow-type ๋˜์–ด ์ƒ์„ฑ๋œ ์ฟผ๋ฆฌ๊ฐ€ ์˜ˆ์ƒํ•˜๋Š” ์ฟผ๋ฆฌ ๋ณ€์ˆ˜๋ฅผ ์˜ˆ์ธกํ•œ๋‹ค.

    • ๋‘ ๊ฐœ์˜ Flow ํƒ€์ž… ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๊ฐ–์Œ: ์ƒ์„ฑ๋œ ์ฟผ๋ฆฌ์˜ ํƒ€์ž… (e.g. UserComponentRefreshQuery) ๊ณผ ์ด๋ฏธ ์ถ”๋ก  ๊ฐ€๋Šฅํ•œ ๋‘๋ฒˆ์งธ ํƒ€์ž…์„ ์ œ๊ณตํ•˜์—ฌ _ ๋ฅผ ์ „๋‹ฌํ•ด๋„ ๋œ๋‹ค.

  • refetch ๋Š” 2๊ฐ€์ง€ ์ฃผ์š” input๊ณผ ํ•จ๊ป˜ ํ˜ธ์ถœํ•œ๋‹ค.

    • ์ฒซ๋ฒˆ์งธ ์ธ์ž๋Š” ํ”„๋ž˜๊ทธ๋จผํŠธ๋ฅผ ํŒจ์น˜ํ•˜๊ธฐ ์œ„ํ•œ ๋ณ€์ˆ˜ ์„ธํŠธ์ด๋‹ค. ์œ„์˜ ๊ฒฝ์šฐ, refetch ๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ๋นˆ ๋ณ€์ˆ˜ ์„ธํŠธ๋ฅผ ์ „๋‹ฌํ•˜๋ฉด ์ฒ˜์Œ ํ”„๋ž˜๊ทธ๋จผํŠธ๋ฅผ ํŒจ์น˜ํ•  ๋•Œ ์‚ฌ์šฉํ•œ ๋˜‘๊ฐ™์€ ๋ณ€์ˆ˜๋ฅผ ๋‹ค์‹œ ์‚ฌ์šฉํ•˜์—ฌ ํ”„๋ž˜๊ทธ๋จผํŠธ๋ฅผ ํŒจ์น˜ํ•˜๊ฒŒ ๋˜๋ฏ€๋กœ, ๋ฆฌํ”„๋ ˆ์‹œ๋กœ์จ ์ž‘๋™ํ•œ๋‹ค.

    • ๋‘๋ฒˆ์งธ ์ธ์ž๋Š” 'network-only' fetchPolicy ๋ฅผ ์ „๋‹ฌํ•˜์—ฌ ๋„คํŠธ์›Œํฌ๋กœ๋ถ€ํ„ฐ ํŒจ์น˜ํ•˜๊ณ  ๋กœ์ปฌ ๋ฐ์ดํ„ฐ ์บ์‹œ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ์„ ๋ณด์žฅํ•œ๋‹ค.

  • refetch ๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ฆฌ๋ Œ๋”๋งํ•˜๊ณ , ์šฐ๋ฆฌ๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” fetchPolicy ์— ๋”ฐ๋ผ ๋„คํŠธ์›Œํฌ ์š”์ฒญ์ด ๋ฐœ์ƒํ•˜๋ฏ€๋กœ useRefetchableFragment ๊ฐ€ suspend ๋œ๋‹ค. ์ด๋Š” ์ฆ‰, suspend ๋  ๋•Œ fallback ๋กœ๋”ฉ ์ƒํƒœ๋ฅผ ๋‚˜ํƒ€๋‚ด๊ธฐ ์œ„ํ•ด ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ๋ฅผ Suspense ๋ฐ”์šด๋”๋ฆฌ๋กœ ๊ฐ์‹ธ์•ผ ํ•จ์„ ๋‚ดํฌํ•œ๋‹ค.

Suspense ์‚ฌ์šฉ์„ ํ”ผํ•ด์•ผ ํ•  ๋•Œ

๋ช‡๋ช‡์˜ ๊ฒฝ์šฐ, ์ด๋ฏธ ๋ Œ๋”๋œ ์ฝ˜ํ…์ธ ๋ฅผ ์ˆจ๊ธฐ๋Š” Suspense fallback์„ ๋ณด์—ฌ์ฃผ์ง€ ์•Š์•„์•ผ ํ•  ๋•Œ๊ฐ€ ์žˆ๋‹ค. ์ด๋Ÿฌํ•œ ๊ฒฝ์šฐ, fetchQuery ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋กœ๋”ฉ ์ƒํƒœ๋ฅผ ์ˆ˜๋™์ ์œผ๋กœ ์ถ”์ ํ•  ์ˆ˜ ์žˆ๋‹ค.

import type {UserComponent_user$key} from 'UserComponent_user.graphql';
// @refetchable์— ๋”ฐ๋ผ ๋ฆด๋ ˆ์ด๊ฐ€ ์ž๋™ ์ƒ์„ฑํ•œ ํƒ€์ž…
import type {UserComponentRefreshQuery} from 'UserComponentRefreshQuery.graphql';

type Props = {
  user: UserComponent_user$key,
};

function UserComponent(props: Props) {
  const [data, refetch] = useRefetchableFragment<UserComponentRefreshQuery, _>(
    graphql`
      fragment UserComponent_user on User
			# @refetchable์€ ์•„๋ž˜์˜ ํ”„๋ž˜๊ทธ๋จผํŠธ๋ฅผ ํŒจ์นญํ•˜๊ธฐ ์œ„ํ•œ ์ฟผ๋ฆฌ๋ฅผ ์ž๋™ ์ƒ์„ฑํ•œ๋‹ค.
      @refetchable(queryName: "UserComponentRefreshQuery") {
        id
        name
        friends {
          count
        }
      }
    `,
    props.user,
  );

  return (
    <>
      <h1>{data.name}</h1>
      <div>Friends count: {data.friends?.count}</div>
      <Button
        disabled={isRefreshing}
        onClick={() => refresh()}>
        Fetch latest count {isRefreshing ? <LoadingSpinner /> : null}
      </Button>
    </>
  );
}
  const [isRefreshing, setIsRefreshing] = useState(false);
  const refresh = useCallback(() => {
    if (isRefreshing) { return; }
    setIsRefreshing(true);

		// fetchQuery๋Š” ์ฟผ๋ฆฌ๋ฅผ ํŒจ์น˜ํ•˜๊ณ  ๋ฆด๋ ˆ์ด ์Šคํ† ์–ด์— ๋ฐ์ดํ„ฐ๋ฅผ ์“ด๋‹ค.
		// ๋”ฐ๋ผ์„œ ๋ฆฌ๋ Œ๋”๋ง ์‹œ, ๋ฐ์ดํ„ฐ๊ฐ€ ์ด๋ฏธ ์บ์‹œ๋˜์–ด suspend ํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.
    fetchQuery(environment, AppQuery, variables)
      .subscribe({
        complete: () => {
          setIsRefreshing(false);

					// ์ฟผ๋ฆฌ๊ฐ€ ํŒจ์น˜๋œ **์ดํ›„**์—,
					// refetch๋ฅผ ๋‹ค์‹œ ํ˜ธ์ถœํ•˜์—ฌ ์—…๋ฐ์ดํŠธ๋œ ๋ฐ์ดํ„ฐ๋กœ ๋ฆฌ๋ Œ๋”๋งํ•œ๋‹ค.
					// ์ฟผ๋ฆฌ๋ฅผ ์œ„ํ•œ ๋ฐ์ดํ„ฐ๋Š” ์บ์‹œ๋˜์–ด์•ผ ํ•˜๋ฏ€๋กœ,
					// 'store-only' fetchPolicy๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ suspending์„ ๋ฐฉ์ง€ํ•œ๋‹ค.
          refetch({}, {fetchPolicy: 'store-only'});
        },
        error: () => {
          setIsRefreshing(false);
        }
      });
  }, [/* ... */]);
  • ์ด์   ๋ฆฌํ”„๋ ˆ์‹ฑ ํ•  ๋•Œ, suspending์„ ํ”ผํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์šฐ๋ฆฌ๋งŒ์˜ isRefreshing ๋กœ๋”ฉ ์ƒํƒœ๋ฅผ ์ถ”์ ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด ์ƒํƒœ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ฝ˜ํ…์ธ  ์ˆจ๊น€ ์—†์ด ์ปดํฌ๋„ŒํŠธ ๋‚ด busy ์Šคํ”ผ๋„ˆ๋‚˜ ๋กœ๋”ฉ UI๋ฅผ ๋ Œ๋”๋ง ํ•  ์ˆ˜ ์žˆ๋‹ค.

  • ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ์—์„œ, ๋จผ์ € fetchQuery ๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์ฟผ๋ฆฌ๋ฅผ ํŒจ์น˜ํ•˜๊ณ  ๋กœ์ปฌ ๋ฆด๋ ˆ์ด ์Šคํ† ์–ด์— ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•œ๋‹ค. fetchQuery ๋„คํŠธ์›Œํฌ ์š”์ฒญ์ด ๋๋‚˜๋ฉด, refetch ๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์ด์ „์˜ ์˜ˆ์‹œ์™€ ๋น„์Šทํ•˜๊ฒŒ ์—…๋ฐ์ดํŠธ ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ Œ๋”๋งํ•˜๋„๋ก ํ•œ๋‹ค.

  • ์ด ์ ์—์„œ refetch ๊ฐ€ ํ˜ธ์ถœ๋˜๋ฉด, ํ”„๋ž˜๊ทธ๋จผํŠธ๋ฅผ ์œ„ํ•œ ๋ฐ์ดํ„ฐ๋Š” ๋กœ์ปฌ ๋ฆด๋ ˆ์ด ์Šคํ† ์–ด์— ์บ์‹œ๋˜์–ด ์žˆ์–ด์•ผ ํ•œ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด store-only fetchPolicy ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ suspending์„ ํ”ผํ•˜๊ณ  ์ด๋ฏธ ์บ์‹œ๋œ ๋ฐ์ดํ„ฐ๋งŒ ์ฝ์„ ์ˆ˜ ์žˆ๋‹ค.

๋‹ค๋ฅธ ๋ฐ์ดํ„ฐ๋กœ ํ”„๋ž˜๊ทธ๋จผํŠธ ๋ฆฌํŒจ์นญํ•˜๊ธฐ

โ€œrefetching a fragmentโ€๋Š” ์ด๋ฏธ ํŠน์ • ํ”„๋ž˜๊ทธ๋จผํŠธ๋กœ ๋ Œ๋”๋ง๋œ ๋ฐ์ดํ„ฐ์™€ ๋‹ค๋ฅธ ๋ฒ„์ „์˜ ๋ฐ์ดํ„ฐ๋ฅผ ํŒจ์นญํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ํ˜„์žฌ ์„ ํƒ๋œ ์•„์ดํ…œ์„ ๋ณ€๊ฒฝํ•  ๋•Œ, ์ด๋ฏธ ๋ณด์—ฌ์ง€๋Š” ์•„์ดํ…œ ๋ฆฌ์ŠคํŠธ์™€ ๋‹ค๋ฅธ ์•„์ดํ…œ๋“ค์„ ๋ Œ๋”๋ง ํ•  ๋•Œ, ํ˜น์€ ๋” ์ผ๋ฐ˜์ ์ธ ๊ฒฝ์šฐ ํ˜„์žฌ ๋ Œ๋”๋ง๋œ ์ฝ˜ํ…์ธ ์—์„œ ์ƒˆ๋กญ๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ์ฝ˜ํ…์ธ ๋ฅผ ๋ Œ๋”๋ง ํ•˜๋Š” ์ƒํƒœ๋กœ ๋ณ€ํ™”ํ•  ๋•Œ ํ”„๋ž˜๊ทธ๋จผํŠธ๋ฅผ ๋ฆฌํŒจ์นญ ํ•œ๋‹ค.

๊ฐœ๋…์ ์œผ๋กœ, ์ด๋Š” ํ˜„์žฌ ๋ Œ๋”๋ง๋œ ํ”„๋ž˜๊ทธ๋จผํŠธ๋ฅผ ๋‹ค์‹œ ํŒจ์นญ๊ณผ ๋ Œ๋”๋งํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•˜์ง€๋งŒ, ๋‹ค๋ฅธ ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•œ ์ƒˆ๋กœ์šด ์ฟผ๋ฆฌ ๋˜๋Š” ์ƒˆ๋กœ์šด ์ฟผ๋ฆฌ ๋ฃจํŠธ์—์„œ ํ”„๋ž˜๊ทธ๋จผํŠธ๋ฅผ ๋ Œ๋”๋ง ํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค.

useRefetchableFragment

import type {CommentBodyRefetchQuery} from 'CommentBodyRefetchQuery.graphql';
import type {CommentBody_comment$key} from 'CommentBody_comment.graphql';

type Props = {
  comment: CommentBody_comment$key,
};

function CommentBody(props: Props) {
  const [data, refetch] = useRefetchableFragment<CommentBodyRefetchQuery, _>(
    graphql`
      fragment CommentBody_comment on Comment
      @refetchable(queryName: "CommentBodyRefetchQuery") {
        body(lang: $lang) {
          text
        }
      }
    `,
    props.comment,
  );

  return (
    <>
      <p>{data.body?.text}</p>
      <Button
        onClick={() => refetchTranslation()}>
        Translate Comment
      </Button>
    </>
  );
}
const refetchTranslation = () => {
		// ์ƒˆ๋กœ์šด ๋ณ€์ˆ˜๋ฅผ ์ด์šฉํ•ด refetch๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด
		// @refetchable ์ฟผ๋ฆฌ๋ฅผ ๊ทธ ์ƒˆ๋กœ์šด ๋ณ€์ˆ˜๋“ค์„ ์ด์šฉํ•ด ๋ฆฌํŒจ์น˜ํ•˜๊ณ 
		// ๊ฐ€์žฅ ์ตœ์‹ ์˜ ํŒจ์น˜๋œ ๋ฐ์ดํ„ฐ๋กœ ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ๋ฅผ ์—…๋ฐ์ดํŠธ ํ•œ๋‹ค.
    refetch({lang: 'SPANISH'});
  };
  • refetch ๋Š” 2๊ฐ€์ง€ ์ฃผ์š” input๊ณผ ํ•จ๊ป˜ ํ˜ธ์ถœํ•œ๋‹ค.

    • ์ฒซ๋ฒˆ์งธ ์ธ์ž๋Š” ํ”„๋ž˜๊ทธ๋จผํŠธ๋ฅผ ํŒจ์น˜ํ•˜๊ธฐ ์œ„ํ•œ ๋ณ€์ˆ˜ ์„ธํŠธ์ด๋‹ค. ์œ„์˜ ๊ฒฝ์šฐ, refetch ๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ์ƒˆ๋กœ์šด ๋ณ€์ˆ˜ ์„ธํŠธ๋ฅผ ์ „๋‹ฌํ•˜๋ฉด ์ƒˆ๋กญ๊ฒŒ ์ „๋‹ฌ๋œ ๋ณ€์ˆ˜์™€ ํ•จ๊ป˜ ํ”„๋ž˜๊ทธ๋จผํŠธ๋ฅผ ํ•ด์น˜ํ•œ๋‹ค. ์ด ๋•Œ ์ œ๊ณตํ•ด์•ผ ํ•˜๋Š” ๋ณ€์ˆ˜๋Š” @refetchable ์ฟผ๋ฆฌ๊ฐ€ ์˜ˆ์ƒํ•˜๋Š” ๋ณ€์ˆ˜ subset ์ด๋‹ค. ํ”„๋ž˜๊ทธ๋จผํŠธ๊ฐ€ id ํ•„๋“œ๋ฅผ ๊ฐ–๊ณ  ์žˆ๋Š” ๊ฒฝ์šฐ ์ฟผ๋ฆฌ๋Š” id ๋ฅผ ์š”๊ตฌํ•˜๊ณ , ๋‹ค๋ฅธ ๋ณ€์ˆ˜ ๋˜ํ•œ ํ”„๋ž˜๊ทธ๋จผํŠธ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ํ•„๋“œ๋ฅผ ์š”๊ตฌํ•œ๋‹ค.

      • ์œ„์˜ ๊ฒฝ์šฐ ํ˜„์žฌ ์ฝ”๋ฉ˜ํŠธ id ์™€ translationType ๋ณ€์ˆ˜๋ฅผ ์œ„ํ•œ ์ƒˆ๋กœ์šด ๊ฐ’์„ ์ „๋‹ฌํ•˜์—ฌ ๋ฒˆ์—ญ๋œ ์ฝ”๋ฉ˜ํŠธ ๋‚ด์šฉ์„ ํŒจ์น˜ํ•œ๋‹ค.

    • ์œ„์˜ ๊ฒฝ์šฐ ๋‘๋ฒˆ์งธ ์˜ต์…˜ ์ธ์ž๋ฅผ ์ „๋‹ฌํ•˜์ง€ ์•Š๋Š”๋ฐ, ์ด๋Š” ๋””ํดํŠธ fetchPolicy ์ธ store-or-network ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•ด๋‹น ํ”„๋ž˜๊ทธ๋จผํŠธ๋ฅผ ์œ„ํ•œ ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๊ฐ€ ์ด๋ฏธ ์บ์‹œ๋œ ๊ฒฝ์šฐ ๋„คํŠธ์›Œํฌ ์š”์ฒญ์„ ์Šคํ‚ตํ•จ์„ ์˜๋ฏธํ•œ๋‹ค.

Suspense ์‚ฌ์šฉ์„ ํ”ผํ•ด์•ผ ํ•  ๋•Œ

import type {CommentBodyRefetchQuery} from 'CommentBodyRefetchQuery.graphql';
import type {CommentBody_comment$key} from 'CommentBody_comment.graphql';

type Props = {
  comment: CommentBody_comment$key,
};

function CommentBody(props: Props) {
  const [data, refetch] = useRefetchableFragment<CommentBodyRefetchQuery, _>(
    graphql`
      fragment CommentBody_comment on Comment
      @refetchable(queryName: "CommentBodyRefetchQuery") {
        body(lang: $lang) {
          text
        }
      }
    `,
    props.comment,
  );

  return (
    <>
      <p>{data.body?.text}</p>
      <Button
        disabled={isRefetching}
        onClick={() => refetchTranslation()}>
        Translate Comment {isRefetching ? <LoadingSpinner /> : null}
      </Button>
    </>
  );
}
  const [isRefetching, setIsRefreshing] = useState(false)
  const refetchTranslation = () => {
    if (isRefetching) { return; }
    setIsRefreshing(true);

    fetchQuery(environment, AppQuery, variables)
      .subscribe({
        complete: () => {
          setIsRefreshing(false);

          refetch({lang: 'SPANISH'}, {fetchPolicy: 'store-only'});
        }
        error: () => {
          setIsRefreshing(false);
        }
      });
  };

Last updated