본문 바로가기
TIL with Programmers

[TIL] 11/14 - Zustand, useSearchParams, URLSearchParams, useLocation, 동적 CSS

by 보먀 2024. 11. 14.
728x90
반응형

1. Zustand

Zustand 는 작고 빠르며 확장 가능한 리액트 프로젝트에서 사용하는 상태관리(Store) 라이브러리이다. 

 

스토어(Store) 는 애플리케이션의 여러 상태(state) 를 중앙에서 관리하는 것을 말한다. 이를 통해 컴포넌트 간 데이터를 쉽게 공유하고 데이터 변경을 감지해 자동으로 렌더링할 수 있다. 

 

출처: https://www.heropy.dev/p/n74Tgc

리액트는 기본적으로 부모 -> 자식 방향으로 데이터를 전달을 하고 이런 방식을 Props(Emits) 방식이라고 부른다.

 

 

출처: https://www.heropy.dev/p/n74Tgc

 

하지만 컴포넌트가 중첩되어 있는 경우, 그림과 같이 중간에 있는 컴포넌트들이 불필요한 데이터를 취급하게 된다. (Props Drilling)

이 방식은 컴포넌트 간의 결합도를 높이고 유지/보수를 어렵게 만든다. 

 

 

그래서 스토어를 사용해서 공유해야 하는 데이터들을 중앙에서 관리하는 방식을 사용한다. 이 방식을 사용하면, 컴포넌트 간의 결합도를 낮추고 유지/보수를 쉽게 할 수 있다. 

 

출처: https://www.heropy.dev/p/n74Tgc

 

 

 

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

 

728x90
반응형