#0 개요
리액트 상태 관리 툴을 무엇으로 할까 고민하다가, 익히기 쉬운 recoil로 진행하면서 테스팅을 위해 로그인 로그아웃 기능을 먼저 개발하기로 했다.
#1 라우팅
react-route 7.1.1 버전을 사용한다. 이전에 잠깐 언급 했던대로 createBrowserRouter로 로그인 전 상태 컴포넌트와, 로그인 이후 상태 component를 분리한다.
로그인 전 상태 컴포넌트에서 isLoggedIn 값을 변경해주는 로직을 작성하는 방향으로 진행한다.
recoil로 local Storage에 상태를 저장하는 과정을 먼저 진행한다.
이후 isLoggedIn state가 변환했을 때, 다시 랜더링 해줄 컴포넌트를 설계해주는 과정으로 진행한다.
#2 form 페이지 만들기
react-hook-form을 사용한다.
공식 문서를 참고하여 만들었다.
https://react-hook-form.com/docs/useform/register
useForm - register
Performant, flexible and extensible forms with easy-to-use validation.
react-hook-form.com
가장 핵심이 되는 register
useState처럼 useForm으로 가져와서 사용한다.
function Login(){
const {register, handleSubmit} = useForm();
const onSubmitCustom = (data: any) => {
console.log(data);
};
return (
<div className="flex flex-col md:flex-row h-screen">
{/* 왼쪽 이미지 섹션 */}
<div className="hidden md:flex md:w-1/2 bg-cover bg-center" style={{ backgroundImage: 'url(/path-to-your-image.jpg)' }}>
{/* 이미지 내용 */}
</div>
{/* 오른쪽 로그인 폼 섹션 */}
<div className="flex w-full md:w-1/2 justify-center items-center p-6">
<div className="w-full max-w-md">
<h2 className="text-2xl font-bold mb-6 text-center">로그인</h2>
<form onSubmit={handleSubmit(onSubmitCustom)}>
<div className="mb-4">
<label className="block text-gray-700">이메일</label>
<input
{...register('email',
{
required: '필수 입력 사항입니다.',
}
)}
type="email"
className="w-full px-4 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="이메일을 입력하세요"
/>
대충 위와 같다.
required와 같이 속성을 넣는 곳에, validate : 검사함수, pattern: regex 등으로 설정을 추가할 수 있다.
+닉네임 중복 검사 api를 호출하는 기능도 있으니 추후 구현
또한 error 메세지 같은 경우 다음과 같이 가져올 수 있다.
const {register, watch, handleSubmit, formState :{errors}} = useForm<IForm>();
...
<input
{...register('email',
{
required: '필수 입력 사항입니다.',
}
)}
type="email"
className="w-full px-4 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="이메일을 입력하세요"
/>
<div>{errors.email?.message}</div>
+++ form 요소 컴포넌트화 하기
import React from "react";
interface IFormErrorProps{
errorMessage?: string
}
export const FormError:React.FC<IFormErrorProps> = ({errorMessage}) => {
return(errorMessage === undefined ? <></> : <span>{errorMessage}</span>)
}
다음과 같이 에러 메세지를 출력하는 부분을 컴포넌트화 하는 등, 여러가지 기법을 적용할 수 있다.
#3 tailwind css
1. tailwind css 알아둘 태그
1) px (padding x)
...
2. 반응형
1) md: 클래스는 화면 크기가 md(768px) 이상일 때 적용되는 css이다.
다음과 같이 반응형으로 쓸 수 있다.
<div className="flex flex-col md:flex-row h-screen">
{/* 왼쪽 이미지 섹션 */}
<div className="hidden md:flex md:w-1/2 bg-cover bg-center" style={{ backgroundImage: 'url(/path-to-your-image.jpg)' }}>
{/* 이미지 내용 */}
</div>
{/* 오른쪽 로그인 폼 섹션 */}
<div className="flex w-full md:w-1/2 justify-center items-center p-6">
</div>
</div>
2) 기본적으로 rem으로 사용하는 사이즈가 반응형을 보장한다.
3. custom css
@apply를 사용하여 tailwind에 있는 css class를 넣거나,
평범하게 css를 써서 디자인을 추가할 수 있다.
#4 서버 상태 관리, 클라이언트 상태 관리
클라이언트 상태 관리를 위한 recoil, 서버 상태 관리를 위한 react query를 사용한다.
1. Recoil
1) store/userState.ts
import { atom } from 'recoil';
import { IUser } from '../types/User';
export const userState = atom<IUser>({
key: 'userState',
default: {
id: null,
username: '',
token: 'default_token',
},
})
2) types/User.ts
export interface IUser {
id: string | null;
username: string;
token: string;
}
위와 같은 방식으로 전역으로 관리할 state를 사용해준다.
2. react-query
이전 react query가 이름이 tanstack query로 바뀌었다.
react-query의 핵심 개념은 다음 세개이다.
Query, Mutation, Query Invalidation
1) Query == useQuery
https://tanstack.com/query/latest/docs/framework/react/guides/queries
Queries | TanStack Query React Docs
Query Basics A query is a declarative dependency on an asynchronous source of data that is tied to a unique key. A query can be used with any Promise based method (including GET and POST methods) to f...
tanstack.com
A query can be used with any Promise based method (including GET and POST methods) to fetch data from a server.
쿼리는 서버에서 데이터를 가져오기 위해 모든 프로미스 기반 메서드(GET 및 POST 메서드 포함)와 함께 사용하는게 핵심
function App() {
const info = useQuery({ queryKey: ['todos'], queryFn: fetchTodoList })
}
다음과 같이 사용하며, key값은 고유하게 해주기 위해서 리스트로 만들어서 다양한 정보를 넣어 고유하게 만들며,
queryFn에 비동기 처리가 필요한 함수가(api 같은거) 오게 된다.
반환되는 객체 info에는 로딩 되었는지 여부 등의 상태가 담긴다
2) Mutation == useMutation
3) Query Invalidation
tanstack query 사용 법은 다음과 같다.
import {
QueryClient,
QueryClientProvider,
} from '@tanstack/react-query'
const queryClient = new QueryClient()
createRoot(document.getElementById('root')!).render(
<StrictMode>
<RecoilRoot>
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
</QueryClientProvider>
</RecoilRoot>
</StrictMode>,
)
먼저 클라이언트를 선언한다. 다만 여기서 client의 개념은 본래 알고 있던 개념과는 조금 다르다. GPT 참고하여 정리하면 다음과 같다.
위 코드에서 // Create a client라는 주석과 함께 생성된 queryClient(즉 new QueryClient())는 React Query에서 사용하는 “클라이언트”를 말한다. 여기서 말하는 클라이언트는 단순히 서버와 통신을 담당하는 어떤 네트워크 클라이언트(예: axios의 인스턴스)만을 의미하는 것이 아니라, React Query가 앱 전체에서 사용하는 ‘요청 상태 관리’와 ‘데이터 캐싱 로직’을 담은 핵심 객체이다.
React Query를 사용할 때 데이터를 가져오고(useQuery), 수정하는(useMutation) 과정에서 생기는 캐싱, 요청 상태(in-flight, success, error), 재시도, 스테일(오래된) 데이터 처리 등이 전부 QueryClient를 통해 제어됩니다. 즉, 이 queryClient 한 곳에서 전역적으로 React Query의 모든 로직을 다루고 있기 때문에, 이 객체를 앱 전체에서 사용할 수 있도록 QueryClientProvider로 감싸 주는 것이다.
client라는 이름은 흔히 “서버와 통신하기 위한 객체”라고 느껴지지만, 여기서 queryClient는 React Query 내부에서 비동기 요청 및 상태 관리를 통합해서 제공해주는 ‘중앙 관리자’ 같은 개념으로 이해하면 된다.
4) devtools에 대하여
npm i -D @tanstack/react-query-devtools
을 통해 캐시 상태를 확인할 수 있는 도구를 다운받는다.
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
function App() {
const isLoggedIn = true
return (
<>
<Header />
<Outlet context={ {isLoggedIn} } />
<ReactQueryDevtools initialIsOpen={false} />
</>
)
}
다음과 같이 툴을 띄울 수 있다.
#5 재미있는 디자인
이런 저런 디자인을 찾아보는 중 다음과 같은 사이트의 디자인을 찾았다.
Linear – Plan and build products
Linear streamlines issues, projects, and roadmaps. Purpose-built for modern product development.
linear.app
인트로 날아오는 디자인이 너무 마음에 들고,
또 애니메이션을 n분당 한번 실행하도록하는 기능도 넣어보고 싶어서 구현해봐야겠다고 생각했다.
'프로젝트 기록 > LLM 게임 프로젝트' 카테고리의 다른 글
개발 리뷰 -10- 메인 페이지 개발 (0) | 2025.01.23 |
---|---|
개발 리뷰 -9- 실시간 채팅 구현을 위한 Websocket, webRTC 선행 (0) | 2025.01.16 |
개발 리뷰 -4- FE 개발 환경 세팅 (0) | 2025.01.06 |
개발 리뷰 -3- 화면 설계 (figma) (0) | 2025.01.03 |
개발 리뷰 -1- 기능 설정 (1) | 2024.12.10 |