Refreshing and Refetching (part. 2)
ํ๋๊ทธ๋จผํธ ๋ฆฌํ๋ ์ฑ
โrefreshing a fragmentโ์ด๋ผ๋ ๊ฒ์, ๊ธฐ์กด์ ํ๋๊ทธ๋จผํธ๋ก ๋ ๋ํ๋ ๋ฐ์ดํฐ์ ๋๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ํจ์นญํ์ฌ ํด๋น ๋ฐ์ดํฐ์ ๊ฐ์ฅ ์ต์ ๋ฒ์ ์ ์๋ฒ๋ก๋ถํฐ ๋ฐ์์ค๋ ๊ฒ์ ์๋ฏธํ๋ค.
real-time ํน์ง ์ด์ฉํ๊ธฐ
์๋ฒ๋ก๋ถํฐ ๊ฐ์ฅ ์ต์ ๋ฒ์ ์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ค๊ธฐ ์ํด์ ์ฒซ๋ฒ์งธ๋ก ํ ์ผ์, real-time ํผ์ณ๋ฅผ ์ด์ฉํ๋ ๊ฒ์ด ์ ์ ํ์ง ํ๋จํ๋ ๊ฒ์ด๋ค. ์ด๋ ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ธฐ์ ์ผ๋ก ์๋์ผ๋ก ๋ฆฌํ๋ ์ ํ ํ์ ์์ด, ์๋์ ์ผ๋ก ์ต์ ๋ฒ์ ์ด ์ ์ง๋๋๋ก ํ๋ค.
real-time ๊ธฐ๋ฅ์ ์์๋ก๋ GraphQL Subscription์ด ์๋๋ฐ, ์ด๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด์๋ ์๋ฒ์ ๋คํธ์ํฌ ๊ณ์ธต์ ์ถ๊ฐ์ ์ธ configuration์ด ํ์ํ๋ค.
useRefetchableFragment
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
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