해본이유
next.js의 app dir을 이용하는 프로젝트에서
json파일을 통해 mock data로 fetching을 시도해보던 중
페칭 자체는 잘 되었지만 에러가 발생하는 것을 확인했습니다.
그러다가 msw를 통해서 한번 해볼까? 라는 생각이 들어서 시도해봤는데
https://github.com/mswjs/msw/discussions/1498
MSW intercept for fetch of next.js 13 app dir server component · mswjs/msw · Discussion #1498
Currently I'm trying to mock the api response for data fetching with react server component (next.js 13 app dir), so far msw intercept only occurs with explicit import of node-fetch in server compo...
github.com
최소한 2023년 5월 2일에는 지원하지않았네요..
하하;;
이제 stable이니 차차 지원해주겠죠?
css-in-js도 그렇고 msw도 그렇고 app dir과 호환되지 않는 녀석들이 많네요..
하지만 msw 자체는 굉장히 시작하기 쉬워보이고 장점이 있어보였기에
app router에서는 사용해볼 수 없었지만
vite으로 프로젝트를 구성해서 한번 사용해보고자 했습니다.
마침 부트캠프에서 진행하는 pre-project에서
next.js 대신 vite으로 구성한 CSR 프로젝트를 진행할 예정이었기에
프로젝트에도 적용할 수 있도록 코드를 작성했습니다.
차근차근 시작해보겠습니다.
msw 코드에 관해서는 거의 100%에 가깝게 해당 블로그를 참고했다는 점을 밝힙니다.
https://www.daleseo.com/mock-service-worker/
MSW로 백앤드 API 모킹하기
Engineering Blog by Dale Seo
www.daleseo.com
설치
npm i @tanstack/react-query
npm i -D msw
reactquery도 함께 사용할 것이니 설치해주겠습니다.
다만 msw는 개발환경에서만 사용할 것이기에 -D 옵션을 줘서 개발종속성으로 설치해주겠습니다.
npx msw init public/ --save
npx 명령어를 통해 msw init을 진행해줍니다.
뒤에 public/은 리소스를 두는 폴더를 지정하는 부분입니다.
만약 public 폴더가 없다면 생성되고 있다면 있는것을 그대로 사용하는 것으로 보입니다.
이 과정을 통해서 서비스 워커 코드를 생성할 수 있습니다.
생성된 결과물을 보면 직접 작성할 생각이 싹 사라지더라구요..
엄청나게 많은 코드가 담긴
mockServiceWorker.js 파일이 생성됩니다.
msw를 위한 폴더만들기
요렇게 세개의 파일을 만들어줄 것입니다.
ts로 생성해줬지만 실제로 타입을 작성하는 부분은 없기때문에
.js로 작성해도 무방합니다.
각 파일의 흐름을 표현하면 다음과 같습니다.
data.ts -> 우리가 요청을 보냈을때 응답받을 데이터를 정의합니다.
handler.ts -> express 서버 문법과 비슷한 문법을 통해 요청에 따른 응답을 정의합니다.
worker.ts -> handler를 가져와서 사용할 수 있게 펼쳐줍니다.
나눠주기가 귀찮다면 한파일내부에서 모든것을 정의해도 상관은 없을 듯 합니다.
하지만 나눠주면 기분이 좋고 코드 읽기도 좋고 역할분담도 좋으니까 나눠줄게요
data.ts
export const todos = ['다함', '님이', '다함'];
data를 정의해주었습니다.
handler.ts
import { todos } from './data';
import { rest } from 'msw';
export const handlers = [
rest.get('/todos', (_, res, ctx) => {
return res(ctx.status(200), ctx.json(todos));
}),
rest.post('/todos', async (req, res, ctx) => {
todos.push(await req.json());
return res(ctx.status(201));
}),
];
마치 express 프레임워크에서 app.get('/todos')하던게 떠오르지 않나요?
우리가 미리 정의해둔 data를 보내주기 위해 data.ts에서 data를 import해왔습니다.
그리고 get요청이 오면 상태코드 200과 함께 우리의 data를 전송하는 코드입니다.
반면 post의 경우에는 async로 작성되어 데이터가 다 파싱되면
todos에 값을 push하고 상태코드 201을 전송하는 함수입니다.
그리고 이것들이 배열에 요소형태로 담겨있다는 것을 기억하고
worker.ts로 넘어가겠습니다.
worker.ts
import { setupWorker } from 'msw';
import { handlers } from './handlers';
export const worker = setupWorker(...handlers);
msw에서 setupWorker 함수를 import하고
우리가 정의했던 배열을 구조분해하여 setupWorker의 인자로 넘겨줍니다.
이제 엔트리포인트에서
main.tsx
import ReactDOM from 'react-dom/client';
import App from './App';
import './index.pcss';
import Providers from './components/Providers';
import { worker } from './mocks/worker';
if (process.env.NODE_ENV === 'development') {
worker.start();
}
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<Providers>
<App />
</Providers>
);
우리가 만든 worker를 import해주고
환경변수 NODE_ENV가 development일때에만 worker를 시작시켜줍니다.
개발서버를 실행시키고
F12를 통해 개발자도구를 열어보았을 때 위와 같은 메시지가 출력되는 것을 확인할 수 있습니다.
vite으로 구성한 프로젝트의 경우 엔트리포인트는 주로 main.tsx입니다.
cra로 구성한 프로젝트의 경우에는 엔트리포인트가 주로 index.tsx이니 유의하세요!
이제 react-query를 suspense와 함께
사용해보도록 하겠습니다.
리액트가 제공해주는 Suspense 컴포넌트를 사용하면
로딩상태를 선언적으로 관리할 수 있다는 장점이 생기며
특히 여러개의 api 콜을 관리해야하는 경우에 코드가 훨씬 간결해지는 효과가 있습니다.
우선 취향차이가 있지만 저는 next.js에서 주로 하는 것처럼
Providers 컴포넌트를 만드는 것을 선호하기에 그렇게 작업하겠습니다.
src/components/Providers.tsx
import { QueryClientProvider, QueryClient, QueryCache } from '@tanstack/react-query';
import React from 'react';
interface ProvidersProps {
children: React.ReactNode;
}
const Providers = ({ children }: ProvidersProps) => {
const queryClient = new QueryClient({
defaultOptions: {
queries: {
suspense: true,
},
},
});
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
};
export default Providers;
쿼리클라이언트의 매개변수로 suspense: true를 주겠습니다.
이제 만들어준 Providers를 query를 사용할 영역에 감싸지도록 해주겠습니다.
main.tsx
import ReactDOM from 'react-dom/client';
import App from './App';
import './index.pcss';
import Providers from './components/Providers';
import { worker } from './mocks/worker';
if (process.env.NODE_ENV === 'development') {
worker.start();
}
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<Providers>
<App />
</Providers>
);
Providers를 import하고 감싸주겠습니다.
src/queries/useDahamQuery.tsx
import { useQuery } from '@tanstack/react-query';
export type DahamType = string[];
export const getDaham = async () => {
const response = await fetch('/todos');
return await response.json();
};
const useDahamQuery = () => {
const daham = useQuery<DahamType>({
queryKey: ["daham"],
queryFn: () => getDaham(),
});
return daham;
};
export default useDahamQuery;
쿼리는 그냥 사용해도 좋지만 간단한 커스텀훅으로 구성해서 사용해보겠습니다.
react-query v4부터는 querykey를 반드시 배열로 지정해주어야하니
이 점 유의하세요!
데이터는 위에서 모킹데이터로 정의해주었던 string[] 타입의 데이터입니다.
fetch요청을 보낼때에는 handler에서 작성한것과 매칭되게 작성해주면 됩니다.
queryFn의 경우에는 따로 작성해주어도 되지만
queryFn 내부에서 작성해주어도 됩니다.
좀 더 사용성 좋게 만들 수도 있지만 우선 간단하게 작성했어요
src/components/UseDahamComponent.tsx
import useDahamQuery, { DahamType } from '@/queries/useDahamQuery';
import React from 'react';
interface UseDahamComponentProps {}
const UseDahamComponent = ({}: UseDahamComponentProps) => {
const daham = useDahamQuery();
return (
<div>
{daham.data?.map((all) => (
<div>{all}</div>
))}
</div>
);
};
export default UseDahamComponent;
바로 사용해주었습니다.
loading 상태일때를 처리하는 로직을 작성하지 않았는데
왜냐하면 Suspense를 사용할 것이기 때문입니다.
src/App.tsx
import React, { useState } from 'react';
import UseDahamComponent from './components/UseDahamComponent';
interface AppProps {}
const App = ({}: AppProps) => {
return (
<div className="App">
<div className=" flex items-center justify-center bg-red-300">it goes Work!!</div>
<React.Suspense fallback={<div>loading</div>}>
<UseDahamComponent />
</React.Suspense>
</div>
);
};
export default App;
Suspense로 감싸주었습니다.
jest 등 테스트 라이브러리와 함께 사용하고자 하는경우에는
const queryCache = new QueryCache();
자세한 방법은 구글링해보세요
마치며
너무 마음에드는 msw 앱 디렉토리에서도 사용하고 싶어잉
'react' 카테고리의 다른 글
zod를 이용해 회원가입 폼을 만들어보기 (0) | 2023.06.18 |
---|---|
zod 라이브러리 사용법 간단히 익히기 (0) | 2023.06.15 |
[react-query] react-query의 query key는... (0) | 2023.06.06 |
[react-query]@tanstack/react-query를 사용해보자 (1) | 2023.06.05 |
useRef와 forwardRef 사용법을 동시에 배울 수 있는 글이 있다? (2) | 2023.05.22 |