๐ฅ 03. ์๋ฌ ํธ๋ค๋ง๊ณผ ์ ์ญ ์ฝ๋ฐฑ(Global Callbacks) ์ํคํ ์ฒ
๐ ๊ฐ์
๋ก์ปฌ try-catch์ ํ๊ณ๋ฅผ ๋์ด์, QueryCache ๋จ์์ ์ ์ญ ์๋ฌ ํธ๋ค๋ง๊ณผ React Error Boundary ๊ฒฐํฉ์ ํตํ ์ต์์ ์๋ฌ ๋ฐฉ์ด๋ง(Defensive UX) ๊ตฌ์ถ์ ๋ค๋ฃน๋๋ค.
๐ ๋ชฉ์ฐจ
- โ๏ธ ์์ฒ ์ด์ ๊ณ ๋ฏผ: "์๋ฌ ํ ์คํธ ์ฐฝ์ด ํ๋ฉด์ ๋ค ๋ฎ์ด๋ฒ๋ ค์!"
- ๐ค ์ ์์์ผ ํ๋๊ฐ: ๋์ ์ด๋์ ๊ฑด์คํ ๊ฒ์ธ๊ฐ?
- 1. ์ต์๋จ ์๋ฌธ์ฅ: Global Cache Callbacks ์ธํ
- 2. Error Boundary์ ์๋ฆ๋ค์ด ๊ฒฐํฉ (Declarative UX)
- ๐ ๋ฐฐ์ด ๋ด์ฉ ์ ๊ฒํ๊ธฐ (Quiz)
"์์ฒ ๋, ๋ง์ฝ ์ ์ ํ ํฐ์ด ๊ฐ ๋ง๋ฃ๋์ด์ ํฐ์ง '401 Unauthorized API' ํธ์ถ์ด 1์ด ์ฌ์ด์ 5๊ฐ๊ฐ ๋์๋ค๋ฐ์ ์ผ๋ก ๋์๊ฐ๋ค๋ฉด, ๋ธ๋ผ์ฐ์ ์ '๋ก๊ทธ์ธ ํด์ฃผ์ธ์' ํ ์คํธ ์๋ฆผ์ 5๊ฐ ๋์์ ๋์ฐ๊ณ ์ ์ ํํ ์๋จน์ ๊ฑด๊ฐ์?"
โ๏ธ ์์ฒ ์ด์ ๊ณ ๋ฏผ: "์๋ฌ ํ ์คํธ ์ฐฝ์ด ํ๋ฉด์ ๋ค ๋ฎ์ด๋ฒ๋ ค์!"
(๋ชฉ์์ผ ์คํ, ํ๋ฉด์ ๋นผ๊ณกํ ๋ฎ์ ๋นจ๊ฐ์ ์คํจ ์๋ ์ฐฝ๋ค์ ์์๋์ ํ๋ฆฌ๋ ์์ฒ )
๐ฃ ์์ฒ : ์ ๊ธฐ... ๐ฆ ๋ฆฌ๋ ๋. ์ ๊ฐ ๊ฐ useQuery ์ปดํฌ๋ํธ๋ง๋ค ์๋์ฒ๋ผ ๋ฐฉ์ด ๋ก์ง์ ์ฐธ ์ ์ง๋จ๊ฑฐ๋ ์?
const { data, isError, error } = useQuery(['user'], fetchUser);
useEffect(() => {
// ๐ฅ ๊ฐ ์ปดํฌ๋ํธ์ ๋
๋จ์ ์ธ ์๋ฌ ํธ๋ค๋ง
if (isError) {
if (error.response.status === 401) {
toast.error("ํ ํฐ์ด ๋ง๋ฃ๋์์ต๋๋ค. ๋ค์ ๋ก๊ทธ์ธ ํด ์ฃผ์ธ์.");
router.push('/login');
}
}
}, [isError, error]);๊ทผ๋ฐ ๊ฐ์๊ธฐ ๋ฐฑ์๋(์์) ๋์ด ์ ์ ๋ฐฐํฌํ๋ค๊ณ ์๋ฒ๋ฅผ 10์ด ๋ด๋ ธ๊ฑฐ๋ ์? ๊ทธ ์ฌ์ด์ ์ ๋์๋ณด๋ ํ๋ฉด์ ์๋ 10๊ฐ์ ์์ ฏ(๊ฐ๊ฐ ๋ค๋ฅธ API ํธ์ถ ์ค)์ด ์ผ์ ํ ์๋ต์ ๋ชป ๋ฐ๋๋, ์๋ฌ ํ ์คํธ(์๋ฆผ์ฐฝ)๊ฐ ์ฐ๋ค๋ค๋ค ์์ฒญ๋๊ฒ ํ๋ฐ๋ฐ ๋จ๋ฉด์ ํ๋ฉด์ ๋ฒ๊ฒ๊ฒ ๋ฎ์ด๋ฒ๋ ธ์ด์ ใ
ใ
.
๐ฆ ์ํธ: ์์ฒ ๋... API ํ๋ํ๋์ ์์ฃผ ๋ํธ๋จธ๋ฆฌ์ธ ๋ก์ปฌ(Local) ์ปดํฌ๋ํธ ๋จ์ ์์ ์ ๋ ๊ฒ "์ธ์ฆ ์๋ฌ"๋ "500 ์๋ฒ ๋ถ๊ดด ์๋ฌ" ๊ฐ์ ์น๋ช
์ ์ธ ์ํ๋ฅผ ๋ค๋ฃจ๋ ค๊ณ ํ๋๊น 10์ค ํญ๋ฐ์ด ์น๋ ๊ฒ๋๋ค.
์ด๋ฐ ์ค๋ํ ๊ณตํต ์๋ฌ๋ ์ปดํฌ๋ํธ ๋ด๋ถ(๋ก์ปฌ)์์ ์ก๋ ๊ฒ ์๋๋๋ค. ์ด์ฐจํผ ๋ชจ๋ ๋คํธ์ํฌ ๋น๋๊ธฐ ์๋ฌ๊ฐ ๊ท๊ฒฐ๋์ด ์์์ง๋ ์ต์๋จ ์ค์ ๋, ๋ฐ๋ก QueryClient์ ์บ์(Cache) ์ ์ญ ์ฝ๋ฐฑ ๋จ์์ ํตํฉ ํ๊ฒฉ์ ๊ฐํด์ผ์ฃ .
๐ค ์ ์์์ผ ํ๋๊ฐ: ๋์ ์ด๋์ ๊ฑด์คํ ๊ฒ์ธ๊ฐ?
์ฐ๋ฆฌ๋ ๊ทธ๋์ ์๋ฌ ์ฒ๋ฆฌ๋ฅผ ํ ๋๋ง๋ค useQuery ํน์ useMutation ์ค์ ๊ฐ์ฒด์ onError ์ฝ๋ฐฑ์ด๋ isError ์ํ ๋ณ์๋ง ๋ฐ๋ผ๋ณด์์ต๋๋ค. ์ด๊ฑด "์์ ์ ์ปดํฌ๋ํธ๋ง ์งํค๋ ์์ ์ฐ์ฐ"์
๋๋ค.
- ๋ฌธ์ ์ 1: ์์ฒ ์ด์ ์ฌ๋ก์ฒ๋ผ A ์ปดํฌ๋ํธ, B ์ปดํฌ๋ํธ, C ์ปดํฌ๋ํธ๊ฐ ๋์์ 401 ์๋ฌ๋ฅผ ๋ง๋ฌ์ ๋, ์ฝ๋ ์ค๋ณต์ด ๋์ฐํ๊ฒ ์ผ์ด๋ฉ๋๋ค.
- ๋ฌธ์ ์ 2:
useMutation({ onError: ... })์์ชฝ์ ๋ก๊ทธ์ธ์ ํ๊ตฌ๋ ๋ก์ง์ ๋ฃ์ด๋๋ฉด? ๋ค๋ฅธ ๊ฐ๋ฐ์๊ฐ ๋์ค์ ๋๊ฐ์ ๋ก๊ทธ์ธ ์ฐ๋ Mutation์ ๋ง๋ค ๋๋ง๋ค ๋ฐฉ์ด ์ฝ๋๋ฅผ "๋ณต๋ถ"ํด์ผ ํฉ๋๋ค. ๋๊ตฐ๊ฐ ํ๋๋ผ๋ ๊น๋จน์ผ๋ฉด? ๊ณง์ฅ ๋ณด์ ๋ฒ๊ทธ๊ฐ ๋ฉ๋๋ค. (TkDodo ์ํฐํด #11 ์ฐธ์กฐ)
์ง์ง ์ฑ์ํ 5๋
์ฐจ์ ํ๋ก ํธ์๋ ๋ผ๋ฉด, ๊ฐ๋ณ ์ปดํฌ๋ํธ์ ์ฐ์ฐ์ ๋ค ๋ฒ๋ฆฌ๊ณ QueryCache ์ MutationCache ๋ผ๋ ๊ฐ์ฅ ๋์ ํ๋์ ์ ์ฒํ ๋ฐฉ์ด ๋(์ ์ญ ์ฝ๋ฐฑ) ์ ์น๋ ์ํคํ
์ฒ๋ฅผ ์ธํ
ํด์ผ ํฉ๋๋ค.
1. ์ต์๋จ ์๋ฌธ์ฅ: Global Cache Callbacks ์ธํ
React Query v5(App Router) ํ๊ฒฝ์ QueryClient ์์ฑ ํฉํ ๋ฆฌ ์ฝ๋๋ก ๋์๊ฐ ๋ด
์๋ค(Basic 09๊ฐ ์ฐธ์กฐ). ์ฌ๊ธฐ์ ์ง์ ํ ์๋ฏธ์ ๋ฐฉ์ด ์์คํ
์ ๊ฒฐํฉํฉ๋๋ค.
// ๐ app/providers/get-query-client.ts
import { QueryClient, QueryCache, MutationCache } from '@tanstack/react-query';
import { toast } from 'react-hot-toast'; // ๊ธ๋ก๋ฒ ํ ์คํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์
function makeQueryClient() {
return new QueryClient({
// ๐ [1] ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ Query(GET) ์ ์ญ ์๋ฌ ์ ์ด
queryCache: new QueryCache({
// ์ง๊ตฌ์์ ์ด๋ค useQuery ํ
์์ ์๋ฌ๊ฐ ํฐ์ง๋ ๊ฒฐ๊ตญ ์ด๊ณณ์ผ๋ก ํ ๋ฒ ๋ถ๋ ค์ต๋๋ค.
onError: (error, query) => {
// ๋ฐฑ๊ทธ๋ผ์ด๋ ์ฌ๊ท(Refetch) ์ค์ ๋ฐ์ํ ์๋ฌ๋ ์ง์ฆ ๋๋๊น ์ด์ง ๋ฌด์ํ๊ณ ...
if (query.state.data !== undefined) {
toast.error(`์ต์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ๋ฐ ์คํจํ์ต๋๋ค: ${error.message}`);
return;
}
// ์น๋ช
์ ์ธ 401, 500 ์ ์ญ ๋ผ์ฐํ
ํธ๋ค๋ง ๋ก์ง์ ์ค๋ก์ง ์ด ํ ๊ณณ์ ์์ง์ํด!
if (error.status === 401) {
toast.error('์ธ์
์ด ๋ง๋ฃ๋์์ต๋๋ค.');
window.location.href = '/login'; // ๊ธ๋ก๋ฒ ๋ก๊ทธ์ธ ๊ฐ์ ์ด๋ (๋จ 1ํ ํญ๋ฐ)
}
},
}),
// ๐ [2] ๋ฐ์ดํฐ๋ฅผ ๋ฐ๊พธ๋ Mutation(POST/DELETE) ์ ์ญ ์๋ฌ ์ ์ด
mutationCache: new MutationCache({
onError: (error, _variables, _context, mutation) => {
// ํด๋น Mutation์ ํธ์ถํ ์ปดํฌ๋ํธ์์ "๋ ์ค์ค๋ก onError ์ฒ๋ฆฌ ์ ํด๋จ์ด์!"
// ๋ผ๊ณ ์ ์ธํ ๊ฒฝ์ฐ(meta ๊ฐ์ฒด๋ฅผ ํตํด)์๋ ์ ์ญ ํ ์คํธ๋ฅผ ๋์ฐ์ง ์๊ณ ๋ก์ปฌ์ ๋งก๊น
if (mutation.meta?.bypassGlobalError) return;
toast.error(`์ ์ฅ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค: ${error.message}`);
}
}),
});
}์ด์ ์ ์ธ๊ณ ์ปดํฌ๋ํธ 100๊ตฐ๋ฐ์์ ํผ์ ธ์๋ 401 ์ธ์ฆ ํ๊น ๋ก์ง์ด๋ isError ์ก๋์ฌ๋ ์ฝ๋๋ค์ด ์์ญ ์ฆ๋ฐํฉ๋๋ค. UI ๊ฐ๋ฐ์๋ ๊ทธ์ "ํ๋ฉด์ ๋ฐ์ดํฐ ์์๊ฒ ๊ทธ๋ฆฌ๊ธฐ" ์๋ง ์ง์คํ๊ฒ ๋์ฃ !
2. Error Boundary์ ์๋ฆ๋ค์ด ๊ฒฐํฉ (Declarative UX)
๐ฃ ์์ฒ : ์ ์ ์ญ ์ฒ๋ฆฌ ์ฉ๋ค์! ๊ทผ๋ฐ ๋ก์ปฌ ์ชฝ์์ "๋ฐ์ดํฐ ์์ฒด๊ฐ ๋ฐ์ด๋์ isError === true ์ผ ๋, ์์ ํ๋ฉด ๋์ '๋นจ๊ฐ์ ์๋ฌ ๋ฌ์ด ํ๊ตฌ' ์ปดํฌ๋ํธ(Fallback UI)๋ฅผ ๊ทธ๋ ค์ค์ผ" ํ ๋๋ ์์์์. ๊ทธ๊ฑด ๊ฒฐ๊ตญ ๊ฐ๋ณ ์ปดํฌ๋ํธ์์ if (isError) return <div>์๋ฌ๋จ</div> ์จ์ผ ํ๋ ๊ฑฐ ์๋๊ฐ์?
๐ฆ ์ํธ: ๋ค, ๋ง์ต๋๋ค. ํ์ง๋ง ์ปดํฌ๋ํธ ์์ชฝ์ if (isError) ๋๋ฐฐ๋ฅผ ์์ํ๋ ์ง๋ ๋์ฐํ๊ธด ๋งคํ๊ฐ์ง์ฃ . ์๋ฌ๊ฐ ๋ฌ์ ๋ "UI๋ฅผ ๋ฌด์์ผ๋ก (์ ์ธ์ ์ผ๋ก) ๋ ๋๋ง ํ ๊ฒ์ธ๊ฐ"๋ React ๋ณธ์ฐ์ ๊ฐ-๊ธฐ๋ฅ์ธ Error Boundary ์๊ฒ ์์ํด์ผ ํฉ๋๋ค.
React Query ์ต์
์ค throwOnError: true (v5 ๋ณ๊ฒฝ์ , ์์ ์ useErrorBoundary) ๋ฅผ ์ผ๋ฉด, ํ
๋ด๋ถ์์ ์ก๊ณ ์๋ ๊ผฌ๋ฆฌ๊ฐ React์ ๊ธฐ๋ณธ ์๋ฌ ์์คํ
์ผ๋ก ํ! ๋์ ธ์ง๋๋ค(throw).
export const todoOptions = {
detail: (id: number) => queryOptions({
queryKey: ['todos', id],
queryFn: fetchTodoDetail,
// ๐ฅ ๋ง๋ฒ์ ์ง์์ด: "์๋ฌ ๋๋ฉด ๋ด๊ฐ(isError) ์ฅ๊ณ ์์ง ์๊ณ ์๋ฌ ๋ฐ์ด๋๋ฆฌ๋ก ํญํ ๋์ง๊ฒ!"
throwOnError: true,
}),
};์ด๋ ๊ฒ ํ๋ฉด, ์ด์ ์ด ๋ฐ์ดํฐ๋ฅผ ์๋นํ๋ ์ปดํฌ๋ํธ ์ฝ๋๋ ๋ถ์๋ฌผ์ด 1g๋ ์ ์์ธ "์์ ๋ฐ์ดํฐ ์ ์ ๋ ๋๋ง ์ฝ๋" ๋ก ๊ทนํ์ ์บก์ํ๊ฐ ๋ฉ๋๋ค.
import { ErrorBoundary } from 'react-error-boundary';
import { Suspense } from 'react';
// ํ๋ฉด ์ต์๋จ ๋๋ ํ์ด์ง ๋ ์ด์์ ๋จ
function Page() {
return (
// ํฐ์ง ํญํ์ ์บ์นํด์ ์์ "๋ค์ ์๋" ์๋ฌ ์นด๋ ๊ทธ๋ ค์ฃผ๋ ์ฐ์ฐ โ๏ธ
<ErrorBoundary fallback={<ErrorFallbackCard />}>
<Suspense fallback={<Spinner />}>
{/* ๐ ์ด ๋
์ ๋ด๋ถ์ ์จ๊ฐ ์์ธ ์ ์ด๋ฌผ(if isLoading, if isError)์ด ์์ ์์ต๋๋ค! */}
<PureTodoDetail id={1} />
</Suspense>
</ErrorBoundary>
)
}์ด ํจํด์ด ๋ฐ๋ก ์ ์ธ์ ๋น๋๊ธฐ UX์ ์ ์ , ์ด๋ฅธ๋ฐ "Suspense + Error Boundary + React Query" ๋ํตํฉ ํจํด์ ๋๋ค.
๐ฃ ์์ฒ ์ด์ ํด๊ทผ ์ผ๊ธฐ
์์จ... ์๊น ๋ฐฑ์๋ 401 ํฐ์ก์ ๋ ๋ด ํ๋ฉด์ ๋ป๊ฑด ํ ์คํธ ์๋ฆผ์ฐฝ 12๊ฐ ์ฐ๋ฅด๋ฅด ์์์ง ๊ฑฐ ์๊ฐํ๋ฉด ์ด์ง๋ฌ์ด๋ฐ, ์ด๊ฑธ ์ต์๋จ QueryCache ๋ก ๋๊ธฐ๋๊น 12๊ฐ๊ฐ ์๋๋ผ ๊น๋ํ๊ฒ ํ ๋ฒ ๋ฑ ๊ฑธ๋ฌ์ ๋ก๊ทธ์ธ ์ฐฝ ํ๊ฒจ์ฃผ๋ ๊ฑฐ ๋ฌด๋น ์ง๋ ธ๋ค...
๐ก ์ค๋์ ๊ตํ: "๋ก์ปฌ ํ (
onError,isError)์์ ๋ชจ๋ ๋ฅ์ ๋ค ์น์ฐ๋ ค๊ณ ํ์ง ๋ง๋ผ. ์น๋ช ์ ์ธ ์ธ์ฆ ์๋ฌ๋ 500 ํฐ์ง์ ๊ฐ์ฅ ์์ชฝ ์ ์ญ ์บ์(QueryCache,MutationCache) ๋์ผ๋ก ๋ชฐ์๋ฒ๋ฆฌ๊ณ (๋ก์ง ๊ฒฉ๋ฆฌ), ๋ก์ปฌ UI ์๋ฌ ํ๋ฉด์ ํญ๋ฐ๋ฌผ ๋์ง๊ธฐ(throwOnError: true) ๋ก Error Boundaryํํ ์งฌ์ฒ๋ฆฌํ์!"
๊ฒฐ๊ตญ ์๋์ด ์ํธ ๋์ด ํญ์ ์ง๋ฅด์น๋ "๋์ ์ปดํฌ๋ํธ๊ฐ ๋๋ฌด ๋ง์ ๊ฑธ ์๊ณ ์๊ฒ(๊ฒฐํฉ๋ ๋๊ฒ) ํ์ง ๋ง๋ผ"๋ ์ฒ ํ์ด ์ํ ๊ด๋ฆฌ๋ฟ๋ง ์๋๋ผ ์๋ฌ ๋ฐฉ์ด๋ง(Defensive UX) ์ค๊ณ์๋ ๋๊ฐ์ด ํตํ๋ ๊ฑฐ์๋ค. ์๋ฆ ๋๋๋ค ์ฆ์ฏ... ์ค๋์ ๋งฅ์ฃผ ๋ง๊ณ ์์คํ๋ ์ ๋ง์๊ณ ์ด ์ฒ ํ์ ๋์ ๊ฐ์ธ ์์ผ์ผ์ง. โ๏ธ
๐ ๋ฐฐ์ด ๋ด์ฉ ์ ๊ฒํ๊ธฐ (Quiz)
Q. ์ ์ญ MutationCache ์์ ํ ์คํธ ์คํจ ์๋ฆผ(onError)์ ๋ฌ์๋์์ต๋๋ค. ๊ทธ๋ฐ๋ฐ ๊ฐ๋ฐ ์ค, ์ด๋ ํ ํน์ ๋ฒํผ(๊ฒ์๊ธ ์ข์์ ์ทจ์) ๋งํผ์ ์ ์ญ ์๋ฌ ํ์
์ ๋์ฐ์ง ์๊ณ ์กฐ์ฉํ ๋ท๋จ์์ ์ฒ๋ฆฌํ๊ฑฐ๋ ๋ค๋ฅธ ์คํ์ผ๋ก ์์ธ ์ฒ๋ฆฌ๋ฅผ ํ๊ณ ์ถ์ด์ก์ต๋๋ค. ์ด๋ ํฉํ ๋ฆฌ ์ ์ญ ์ฝ๋๋ฅผ ๋๋ฝํ์ง(์์ ํ์ง) ์๊ณ , ํด๋น ๋ก์ปฌ ์ปดํฌ๋ํธ ์ชฝ์ Mutation์์ ์ ์ญ ์ฝ๋ฐฑ์ "์ฐํ(Bypass)" ํ ์ ์๊ฒ ์๋ ค์ฃผ๋ ๊ฐ์ฅ ๊ถ์ฅ๋๋ React Query ๋ฌธ๋ฒ/์ต์
์ ๋ฌด์์
๋๊น?
- A) ํ
์ต์
์
ignoreGlobalError: true๊ฐ์ ์์ฒด ๋ด์ฅ ์ต์ ์ ๊ฑด๋ค. - B) ๋ถ๊ฐ๋ฅํ๋ค. ์ ์ญ ์บ์์ ๋ฐํ ๋ก์ง์ ๋ฌด์กฐ๊ฑด ์ง๊ตฌ์ ๋ชจ๋ ํ ์ ๋์ผํ๊ฒ ์ ์ฉ๋๋ฏ๋ก ์ฐํํ ์ ์๋ค.
- C)
useMutation ({ meta: { bypassGlobalToast: true } })์ฒ๋ผ ์ปค์คํ ์ธ๊ณ ๋ฉํ๊ฐ์ฒด(meta) ์์ ํค ๊ฐ์ ๋ฌ์, ์ ์ญ ์บ์๊ฐ ๊ทธ ๊ฐ์ ์ฝ๊ณ ๋ถ๊ธฐ ์ฒ๋ฆฌํ๊ฒ ๋ง๋ ๋ค.
โ
์ ๋ต: C
๐ก ์์ธ ํด์ค:
- ์๋ฆฌ ์ค๋ช
: React Query์
useQuery์useMutation์๋ ์์ฃผ์์ฃผ ๊ฐ๋ ฅํ์ง๋ง ์ ์ ๋ค์ด ์ ๋ชจ๋ฅด๋ ๋ง๋ฒ์ ๋ท๊ตฌ๋ฉ์ธmeta๊ฐ์ฒด๊ฐ ์กด์ฌํฉ๋๋ค(TkDodo ์ํฐํด #11 ์ญ์ค). ์ดmeta๊ฐ์ฒด์ ํค์ ๊ฐ๋ค์ ์ค์ง ์ ๋ณด ์ ๋ฌ ์ฉ๋๋ก๋ง ์ฐ์ด๋ฉฐ, ์ต์๋จ ๊ธ๋ก๋ฒ ์๋ฌ ํธ๋ค๋ฌ์ธ (QueryCache/MutationCache ์ ์ฝ๋ฐฑ)์ 4๋ฒ์งธ ์ธ์๋ก ๊ณ ์ค๋ํ ๋ฐฐ๋ฌ๋ฉ๋๋ค. ๊ธ๋ก๋ฒ ํธ๋ค๋ฌ๋ ์ดmeta๊ผฌ๋ฆฌํ๋ฅผ ์ฝ์ด๋ณด๊ณ "์! ์ด Mutation์ ๊ธ๋ก๋ฒ ํ ์คํธ๋ฅผ ๋์ฐ์ง ๋ง๋ผ๊ณ ๋ถํํ๊ตฌ๋! ๋ฌด์ํ์~" ๋ผ๊ณ ์ค๋งํธํ๊ฒ ์ฐํ ๋ถ๊ธฐ๋ฅผ ํ์ธ ์ ์์ต๋๋ค. - ์ค๋ต ํผ๋๋ฐฑ: "์์ฒ ๋, A๋ฒ์ฒ๋ผ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์๋ ๋ง๋๋ก ์ง์ด๋ธ ์ด๋ฆ ๋ฃ์ผ๋ฉด TS ์๋ฌ ๋๊ณ ํฐ์ ธ์! ์ค๋ก์ง ๊ฐ๋ฐ์ ์ ์ฉ ํต์ ๊ท์ฝ์ธ
meta์ต์ ์ ํตํด์๋ง ํ ๊น์ํ ์์ชฝ์์๋ถํฐ ์ต์๋จ ์ ์ญ ์บ์ ๋๊ธฐ๊ถ ๋ฐ๊นฅ์ชฝ์ผ๋ก ๋ฌด์ (signal)์ ๋๋ฆด ์ ์๋ ๊ฒ๋๋ค." - ๐ ํต์ฌ ๊ธฐ์ต๋ฒ: ๋ก์ปฌ ํ
์ด ์ ์ญ ๋ ํต์ ์ค์ ๋ชฐ๋ ๊ท์๋ง์ ๋ณด๋ด๋ ์ ์ผํ ๋ท๊ตฌ๋ฉ,
meta์ต์ !