해본이유
next.js의 app dir을 이용하는 프로젝트에서
json파일을 통해 mock data로 fetching을 시도해보던 중
페칭 자체는 잘 되었지만 에러가 발생하는 것을 확인했습니다.
그러다가 msw를 통해서 한번 해볼까? 라는 생각이 들어서 시도해봤는데
https://github.com/mswjs/msw/discussions/1498
최소한 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/
설치
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 |