1. Zustand
Zustand 는 작고 빠르며 확장 가능한 리액트 프로젝트에서 사용하는 상태관리(Store) 라이브러리이다.
스토어(Store) 는 애플리케이션의 여러 상태(state) 를 중앙에서 관리하는 것을 말한다. 이를 통해 컴포넌트 간 데이터를 쉽게 공유하고 데이터 변경을 감지해 자동으로 렌더링할 수 있다.
리액트는 기본적으로 부모 -> 자식 방향으로 데이터를 전달을 하고 이런 방식을 Props(Emits) 방식이라고 부른다.
하지만 컴포넌트가 중첩되어 있는 경우, 그림과 같이 중간에 있는 컴포넌트들이 불필요한 데이터를 취급하게 된다. (Props Drilling)
이 방식은 컴포넌트 간의 결합도를 높이고 유지/보수를 어렵게 만든다.
그래서 스토어를 사용해서 공유해야 하는 데이터들을 중앙에서 관리하는 방식을 사용한다. 이 방식을 사용하면, 컴포넌트 간의 결합도를 낮추고 유지/보수를 쉽게 할 수 있다.
1.1. Zustand 사용하기
create 함수로 스토어를 생성할 수 있다. create 함수의 콜백은 set, get 매개변수를 가지며 이를 통해서 상태를 변경/조회할 수 있다.
create 함수의 콜백이 반환하는 객체에서 속성은 상태(State), 메소드는 액션(Action)이라고 한다.
import { create } from 'zustand'
export const use이름Store = create((set, get) => {
return {
상태: 초깃값,
액션: 함수
}
})
set 함수로 상태를 변경하는 방법은 2가지가 있다.
// get으로 상태를 얻어오고, set으로 변경
import { create } from 'zustand'
export const use이름Store = create((set, get) => {
return {
상태: 초깃값,
액션: () => {
const state = get() // 현재 상태 얻어옴
const { 상태 } = state
set({ 상태: 상태 + 1 })
}
}
})
set 함수를 호출할 때 콜백을 사용하면 get 함수로 상태를 얻어오지 않아도 바로 상태를 얻을 수 있다. 대신 변경할 상태를 속성으로 포함한 객체를 콜백에서 반환해주어야 하니 아래의 코드처럼 암시적으로 반환해주던지, return 문을 통해서 반환해주는 것이 필요하다.
// set 호출시 콜백을 사용 (get X)
import { create } from 'zustand'
export const use이름Store = create(set => {
return {
상태: 초깃값,
액션: () => {
set(state => ({ 상태: state.상태 + 1 })) // 콜백을 사용한 set
}
}
})
만든 use이름Store 를 필요한 파일에서 아래와 같이 사용하면 된다.
import { useCountStore } from './store/use이름Store'
const count = use이름Store(state => state.객체(상태))
const 액션 = use이름Store(state => state.액션)
const 액션 = use이름Store(state => state.액션)
import { use이름Store } from './store/use이름Store'
const count = use이름Store(state => state.객체(상태))
const { 액션, 액션 } = use이름Store(state => state.actions)
1.2. 로그인/로그아웃에 Zustand 사용하기
import { create } from 'zustand'
interface StoreState {
isLoggedIn: boolean;
storeLogin: (token: string) => void;
storeLogout: () => void;
}
export const getToken = () => { // 로컬 스토리지에 있는 토큰 가져오기
const token = localStorage.getItem('token');
return token;
};
const setToken = (token: string) => { // 로컬 스토리지에 토큰 넣기
localStorage.setItem('token', token);
}
export const removeToken = () => { // 로컬 스토리지에서 토큰 제거
localStorage.removeItem('token');
}
export const useAuthStore = create<StoreState>((set) => ({
// 객체(상태)
isLoggedIn: getToken() ? true : false,
// 액션
storeLogin: (token: string) => {
set({isLoggedIn: true});
setToken(token);
},
storeLogout: () => {
set({isLoggedIn: false});
removeToken();
},
}));
2. useSearchParams , useLocation
2.1. useSeachParams 사용하기
useSearchParams 는 React Router(v6 이상) 에서 제공하는 URL 의 쿼리 문자열을 읽고 수정할 수 있는 훅이다.
쿼리 스트링은 문자열 형태를 띄며 key=value 형태로 표현되는데, useSearchParams 는 쿼리 스트링의 value 값을 읽어올 수 있다.
- 쿼리 파라미터 읽기
useSearchParams 에 쿼리 스트링의 key 값을 넣으면 value 값을 읽어온다. 만약 useSearchParams 에 있는 key 값이 쿼리 스트링에 없다면 null 값을 반환한다. (그렇기 때문에 타입을 null 과 string 으로 쓴 것!)
import { useSearchParams } from "react-router-dom";
const Example: React.FC = () => {
const [searchParams] = useSearchParams();
// 특정 파라미터 읽기
const query: string | null = searchParams.get("query");
const category: string | null = searchParams.get("category");
return (
<div>
<p>Query: {query}</p>
<p>Category: {category}</p>
</div>
);
};
- 파라미터 업데이트
업데이트 방법을 알아보기에 앞서 URLSearchParams 에 대해 먼저 알아보도록 하자. (이걸로 업데이트할 것이기 때문..!)
URLSearchParams 는 자바스크립트에서 제공하는 웹 API 로 URL 의 쿼리 문자열을 쉽게 읽고, 조작하고 생성할 수 있도록 도와주는 객체이다. 브라우저 환경 뿐 아니라 Node.js 환경에서도 사용할 수 있다.
기존 URL 과 같은 값을 가지는 새로운 URL 객체를 생성해서 원하는 대로 처리한 후에 URL 을 업데이트하는 방법이다.
이 방법을 사용하면 원본 URL 객체를 직접 건드리지 않기 때문에 불변성을 유지하고, 더 안정적으로 URL 을 업데이트할 수 있다.
const { category } = useCategory();
const [ searchParams, setSearchParams ] = useSearchParams();
const handleCategory = (id: number | null) => {
const newSearchParams = new URLSearchParams(searchParams); // 새로운 URL객체 생성
if (id === null) {
newSearchParams.delete(QUERYSTRING.CATEGORY_ID);
} else {
newSearchParams.set(QUERYSTRING.CATEGORY_ID, id.toString());
}
setSearchParams(newSearchParams); // 실제적인 업데이트
}
2.2. useLocation
useLocation 은 React Router 에서 제공하는 훅으로, 현재 경로(location)의 정보를 얻기 위한 훅이다. 이 훅을 사용하면 현재 URL 의 경로(pathname), 쿼리 파라미터(search), 해시(hash) 등을 포함한 location 객체를 가져올 수 있다.
- location 객체의 구조
- pathname: 현재 URL 의 경로 (ex. /home, /about)
- search: 쿼리 문자열 (ex. ?name=John)
- hash: URL 의 해시 부분 (ex. #section1)
- state: 페이지로 이동할 때 전달한 상태 객체
- key: React Router 가 URL 변경을 추적하기 위해 사용하는 고유 키
- 현재 URL 쿼리스트링 읽어오기
const location = useLocation();
const params = new URLSearchParams(location.search);
const view = params.get("view");
3. useLocation 사용해서 뷰모드 바꾸기
location.search 로 현재 URL 의 쿼리 문자열을 읽어와서 뷰 모드를 설정한다.
import styled from 'styled-components'
import BookItem from './BookItem';
import { Book } from '../../models/book.models'
import { useLocation } from 'react-router';
import { useEffect, useState } from 'react';
import { QUERYSTRING } from '../../contants/querystring';
import { ViewMode } from './BookViewSwitcher';
interface Props {
books: Book[];
}
const BooksList = ({ books }: Props) => {
const [view, setView] = useState<ViewMode>('grid');
const location = useLocation();
useEffect(() => {
const params = new URLSearchParams(location.search);
if (params.get(QUERYSTRING.VIEW)) {
setView(params.get(QUERYSTRING.VIEW) as ViewMode);
}
}, [location.search])
return (
<BooksListStyle view={view}>
{
books.map((item) => (
<BookItem key={item.id} book={item} view={view} />
))
}
</BooksListStyle>
)
}
interface BooksListStyleProps {
view: ViewMode;
}
const BooksListStyle = styled.div<BooksListStyleProps>`
display: grid;
grid-template-columns: ${({ view }) =>
(view === 'grid' ? 'repeat(4, 1fr)' : 'repeat(1, 1fr);' )};
gap: 24px;
`;
export default BooksList
4.1. 동적으로 CSS 설정
사실 css 설정이 중요한데, 전달받은 view 값에 따라 grid 모드와 list 모드로 레이아웃을 동적으로 설정한다.
grid-template-columns: ${({ view }) =>
(view === 'grid' ? 'repeat(4, 1fr)' : 'repeat(1, 1fr);' )};
4.2. repeat 함수
Grid 와 Flexbox 에서 반복적인 패턴을 쉽게 설정할 수 있게 해주는 유용한 함수이다. 같은 크기의 열이나 행을 간편하게 생성할 수 있다.
- count: 반복 횟수 -> 얼마나 많은 열 또는 행을 만들지
- size: 각 열/행의 크기, 1fr, px, em, % 등의 단위로 크기를 지정할 수 있음
repeat(count, size)
- grid 뷰모드
grid-template-columns: repeat(4, 1fr);
- list 뷰모드
grid-template-columns: repeat(1, 1fr);
나는 이번에 fr 이라는 단위 자체를 처음 써봤는데, 1fr 은 1프랙션이라는 단위로 그리드 컨테이너 전체 너비에서 4개의 열이 동일한 비율로 공간을 차지하는 것을 의미한다.
예를 들어 가로 크기가 1000px 인 그리드에서 repeat(4, 1fr) 을 사용하면 각 열은 250px 씩 차지하게 되는 것!
(참고로 여기서는 columns 에 대해서만 적용했지만, grid-template-rows 도 있다.)
참고 문헌 📖
Zustand 핵심 정리
Zustand(주스탠드)는 작고 빠르며 확장 가능한 React 프로젝트에서 사용하는 상태 관리(Store) 라이브러리입니다.
www.heropy.dev
'TIL with Programmers' 카테고리의 다른 글
[TIL] 11/18 map 에서 key 가 필요한 이유, (0) | 2024.11.18 |
---|---|
[TIL] 11/15 -webkit-box, Ellipsis, $ 접두사, 낙관적 업데이트 (0) | 2024.11.15 |
[TIL] 11/13 회원가입, useForm, http 클라이언트 생성, forwardRef (0) | 2024.11.13 |
[TIL] 11/12 styled-components 모르는 것 정리 (0) | 2024.11.12 |
[TIL] 11/11 React context API, 테마 스위쳐 구현, 모르는 것 정리 (1) | 2024.11.11 |