6/21 TIL (아웃소싱 프로젝트 제출, Zustand)

Hwi·2024년 6월 21일

TIL

목록 보기
54/96

📖 진행한 공부 📖

  • 조별 과제 제출 결과물
  • Zustand

조별 과제 제출 결과물

사용 기술 스택

  • HTML5, TAILWINDCSS, JAVASCRIPT, REACTQUERY
    REACT, ZUSTAND

버전 관리

  • GITHUB, GIT

협업툴

  • FIGMA, SLACK

간략한 구현 설명 사진

메인 페이지


키워드에 xx박물관 혹은 미술관, 뮤지엄 검색 시 옆에 전시회를 진행했던 히스토리가 뜸

해당 전시를 누르면 상세정보와 공식 홈페이지로 이동시켜주는 태그도 생성

이번 아웃소싱 프로젝트를 통해 느낀 점을 말해보자면 팀원 분들은 다 잘해주셔서 아쉬운 점은 없지만 개인적으론 카카오 맵 오픈 API를 처음 써봐서 그런지 이해도가 부족하기에 원하는 기능을 구현하지 못한 것이 아쉬운 부분이다. ex) API를 불러올 때 키워드 필터링

이번에 외부 API를 사용해 과제를 진행하면서 카카오 API를 불러와서 쓰는 법을 배웠기에 다음에도 관련된 프로젝트를 진행할 시 이번 경험을 바탕으로 도움이 많이 될 거 같다

팀원 간의 커뮤니케이션이 활발하게 이루어져 원하는 결과물을 만들 수 있었던 거 같다!

이번 과제를 진행하면서 외부 api에 대한 이해도가 조금은 올라간 거 같고 해당 과제를 진행하면서 겪었던 트러블슈팅 이라면

박물관에 관련된 정보만 보여주고 싶은데 사이트 기획 의도에 맞지 않게 키워드 검색칸에 식당, 오마카세, 디저트 등등 전혀 관련없는 키워드를 입력해도 해당 키워드에 대한 검색 결과값이 출력이 됐었는데 해당 문제를 해결하기 위해서 form onSubmit={} 에 들어가는 handleSearch 함수에서 "박물관", "뮤지엄”, “미술관” 키워드가 포함되어 있어야만 검색 기능이 실행되게끔 하니깐 얼추 겉으로는 되는 거 처럼 보이지만 “뮤지엄”이 들어간 “뮤지엄산” 이라던가 해당 키워드가 들어간 다른 장소가 뜨는 부분은 해결을 못한 것이 너무 아쉬운 점이기에 추후 개선을 해보고 싶은 마음이다.

프로젝트를 진행하면서 협업에 관한 트러블슈팅은 없었던 걸 보면 모든 팀원들이 착착 손발이 잘 맞았기에 무탈하게 주어진 과제를 완성해낼 수 있었다고 생각한다.

=========================================

01. Zustand의 등장배경

(1) 상태관리의 중요성
상태관리는 모든 리액트 애플리케이션 에서 매우 중요, 애플리케이션이 커질수록 상태관리는 더 복잡해짐

(2) Zustand의 필요성
Zustand는 상태관리 본연의 기능에 집중하여 복잡성을 줄이고, 보다 간단하고 직관적인 상태관리 기능을 제공

Zustand는 단순화된 Flux 패턴을 사용하는 작고 빠르고 확장가능한 상태관리 솔루션이며, Hooks에 기반으로 하는 간편한 API가 존재

02. Zustand의 주요 개념

(1) Zustand의 주요 특징

  • 간결함

    • 매우 간단한 API를 제공함
    • 적은 설정 코드 및 적용 코드를 이용해 상태관리 구현 가능
  • 성능

    • 불필요한 리렌더링을 방지하는 등 성능 최적화 GOOD

    • Zustand는 상태가 변경될 때, 해당 상태를 구독하고 있는 컴포넌트만 리렌더링
      (구독이란? => 상태의 변경을 감지하고, 해당 변경에 반응하는 컴포넌트만 업데이트하는 메커니즘을 의미함. 우리가 유튜브 채널을 구독한다면, 채널에서 새로운 영상이나 공지가 올라오면 알림이 오는 것과 같은 개념)

    • 상태의 일부가 변경되었을 때 그 상태를 사용하는 컴포넌트만 업데이트되므로 이를 통해 성능을 최적화하고 리렌더링으로 인한 성능 저하를 방지

  • React와의 통합

    • React의 훅과 완벽하게 통합
    • Zustand는 React의 useState, useEffect 같은 기본 훅을 사용하는 것처럼 간편하게 사용 가능
    • 상태를 정의하고 이를 React 컴포넌트에서 쉽게 사용할 수 있어 개발 경험을 그대로 활용 가능
  • 높은 점유율 확보

(2) 설치 및 기본 사용법

  • Zustand 설치
    yarn add zustand

  • 기본 사용법

// src > zustand > bearsStore.js
import { create } from "zustand";

const useBearsStore = create((set) => ({
  bears: 0,
  increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
  removeAllBears: () => set({ bears: 0 }),
}));

export default useBearsStore;
// src > App.jsx
import "./App.css";
import useBearsStore from "./zustand/bearsStore";

function App() {
  const bears = useBearsStore((state) => state.bears);
  const increasePopulation = useBearsStore((state) => state.increasePopulation);
  return (
    <div>
      <h1>{bears} around here ...</h1>
      <button onClick={increasePopulation}>one up</button>
    </div>
  );
}
export default App;

03.Zustand와 Redux Toolkit 비교

(1) 설정 및 사용법 비교

  • Zustand: 매우 간단한 설정과 사용법을 제공, 우리가 이미 경험해 본 대로 상태를 정의하고 사용하는 과정이 직관적
import { create } from "zustand";

const useStore = create(set => ({
  bears: 0,
  increasePopulation: () => set(state => ({ bears: state.bears + 1 }))
}))
  • Redux Toolkit: 보다 구조화된 방법을 제공하지만 설정이 다소 복잡할 수 있고, 보일러 플레이트가 너무 많다 보니 상태 하나를 관리하고자 해도 추가/설정해야 하는 것이 번거로움
import { configureStore, createSlice } from '@reduxjs/toolkit'

const slice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment: state => { state.value += 1 }
  }
})

const store = configureStore({ reducer: slice.reducer })

(2) 장단점 비교 (정리)

  • Zustand
    • 장점: 간단ㅇ하고 빠르며 설정이 매우 쉬움
    • 단점: 상태가 커지면 관리가 어려울 수 있음

-Redux Toolkit

  • 장점: 구조화된 방법을 통해 대규모 앱에서도 관리가 용이함
  • 단점: 설정이 복잡하고, 학습 곡선이 가파름

04. 예시코드

(1) 상태 정의

// src > zustand > todosStore.js
import { create } from "zustand";

const useTodosStore = create(set => ({
  todos: [],
  addTodo: (todo) => set(state => ({ todos: [...state.todos, todo] })),
  removeTodo: (index) => set(state => ({
    todos: state.todos.filter((_, i) => i !== index)
  }))
}))

export default useTodosStore;

(1)-1. 상태 사용

// src > App.jsx
import React, { useState } from "react";
import useTodosStore from "./zustand/todosStore";

function App() {
  const todos = useTodosStore((state) => state.todos);
  const addTodo = useTodosStore((state) => state.addTodo);
  const removeTodo = useTodosStore((state) => state.removeTodo);
  const [input, setInput] = useState("");

  return (
    <div>
      <h1>Todo List</h1>
      <input value={input} onChange={(e) => setInput(e.target.value)} />
      <button
        onClick={() => {
          addTodo(input);
          setInput("");
        }}
      >
        Add Todo
      </button>
      <ul>
        {todos.map((todo, index) => (
          <li key={index}>
            {todo} <button onClick={() => removeTodo(index)}>Remove</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default App;

(2) Zustand와 Immer의 결합
(2)-1. 직접 중첩된 상태를 업데이트 했을 때의 문제점
Zustand의 간편한 상태 업데이트 방식은 깊은 중첩 구조를 가지는 상태를 업데이트할 때 문제가 발생할 수 있음

배열이나 객체의 중첩된 상태를 업데이트할 때 불변성을 유지하지 않으면 상태 반영이 제대로 이루어지지 않아서 예상치 못한 오류가 발생할 수 있음

mutable

import create from "zustand";

// Zustand 스토어 생성
const useTodosStore = create((set) => ({
  todos: [],
  addTodo: (text) =>
    set((state) => {
      // 불변성을 어기는 예시: 직접 배열을 수정
      state.todos.push({ id: Date.now(), text, completed: false });
      return state;
    }),
  toggleTodo: (id) =>
    set((state) => {
      // 불변성을 어기는 예시: 직접 객체를 수정
      const todo = state.todos.find((todo) => todo.id === id);
      if (todo) {
        todo.completed = !todo.completed;
      }
      return state;
    }),
}));

export default useTodosStore;

immutable

// src > App.jsx
import useTodosStore from "./zustand/todosStore";

function App() {
  const { todos, addTodo, toggleTodo } = useTodosStore();

  return (
    <div>
      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>
            <span
              style={{
                textDecoration: todo.completed ? "line-through" : "none",
              }}
              onClick={() => toggleTodo(todo.id)}
            >
              {todo.text}
            </span>
          </li>
        ))}
      </ul>
      <button
        onClick={() => addTodo(prompt("새로운 todolist를 입력해주세요."))}
      >
        Add Todo
      </button>
    </div>
  );
}

export default App;

리스트가 추가 / 수정 되더라도 다른 요소에 의해 리렌더링이 일어나지 않는 한, UI에 반영이 안 됨

(2)-2. immer를 이용해 해결하는 경우
immer 란?

  • JavaScript에서 상태를 쉽게 변경할 수 있게 해주는 라이브러리, 원본 데이터를 변경하지 않고도 직접 수정하는 것처럼 코드를 작성할 수 있으며, Immer가 자동으로 불변성을 유지한 새 상태를 만들어 줌

immer 라이브러리 설치
yarn add immer

todosStore.js 를 다음과 같이 수정

import { create } from "zustand";
import { immer } from "zustand/middleware/immer";

const useTodosStore = create(
  immer((set) => ({
    todos: [
      {
        id: 1,
        title: "Learn Zustand",
        tasks: [{ id: 1, task: "Read documentation", done: false }],
      },
    ],
    addTask: (todoId, newTask) =>
      set((state) => {
        const todo = state.todos.find((todo) => todo.id === todoId);
        if (todo) {
          todo.tasks.push(newTask); // 불변성 유지: immer가 자동으로 처리
        }
        // return { todos: state.todos }; // 변경된 참조가 기존 상태와 같아 리렌더링되지 않음
      }),
    toggleTask: (todoId, taskId) =>
      set((state) => {
        const todo = state.todos.find((todo) => todo.id === todoId);
        if (todo) {
          const task = todo.tasks.find((task) => task.id === taskId);
          if (task) {
            task.done = !task.done; // 불변성 유지: immer가 자동으로 처리
          }
        }
        // return { todos: state.todos }; // 변경된 참조가 기존 상태와 같아 리렌더링되지 않음
      }),
  }))
);

export default useTodosStore;

(3) 기타 유용한 패턴
(3)-1. 선택적 상태 구독
상태의 특정 부분만 구독하여 성능을 최적화 할 수 있음

// src > App.jsx
const todos = useTodosStore((state) => state.todos);
const addTask = useTodosStore((state) => state.addTask);
const toggleTask = useTodosStore((state) => state.toggleTask);

(3)-2. 미들웨어 사용
로딩, 퍼시스턴스 등을 사용하면 다양한 기능을 추가할 수 있음
ex) persist를 이용해 새로고침을 해도 데이터가 남아있도록 함

persist, devtools는 zustand 내장이므로 별도 설치할 필요 X

import { produce } from "immer";
import { create } from "zustand";
import { persist } from "zustand/middleware";

const useTodosStore = create(
  persist(
    (set) => ({
      todos: [
        {
          id: 1,
          title: "Learn Zustand",
          tasks: [{ id: 1, task: "Read documentation", done: false }],
        },
      ],
      addTask: (todoId, newTask) =>
        set(
          produce((state) => {
            const todo = state.todos.find((todo) => todo.id === todoId);
            if (todo) {
              todo.tasks.push(newTask); // 불변성 깨짐: 직접 수정
            }
            // return { todos: state.todos }; // 변경된 참조가 기존 상태와 같아 리렌더링되지 않음
          })
        ),
      toggleTask: (todoId, taskId) =>
        set(
          produce((state) => {
            const todo = state.todos.find((todo) => todo.id === todoId);
            if (todo) {
              const task = todo.tasks.find((task) => task.id === taskId);
              if (task) {
                task.done = !task.done; // 불변성 깨짐: 직접 수정
              }
            }
            // return { todos: state.todos }; // 변경된 참조가 기존 상태와 같아 리렌더링되지 않음
          })
        ),
    }),
    {
      name: "todos-storage", // 저장소 이름을 설정해요!
      // getStorage: () => sessionStorage, // localStorage가 아닌 곳에 저정하고 싶다면!
    }
  )
);

export default useTodosStore;

05. Zustand의 장점과 단점

(1) 장점

  • 간편한 사용: 간단한 API와 직관적인 사용법 제공
  • 성능 최적화: 불필요한 리렌더링 방지
  • React와의 완벽한 통합: 훅과 잘 통합돼있음
  • 미들웨어 지원: 로깅, 퍼시스턴스 등 다양한 미들웨어 사용 가능
  • 유연성: 필요한 부분만 선택적으로 사용 가능
  • 커뮤니티와 자료: 많은 자료가 확보되고 있음

(2) 단점

  • 상태가 커지면 관리하기 어려움

(3) Redux Toolkit과의 비교

  • 설정과 사용법: Zustand는 설정과 사용이 간단하며, Redux Toolkit은 더 구조화된 방법을 제공

  • 성능: 두 라이브러리 모두 성능 최적화가 잘 되어 있지만, Zustand는 불필요한 리렌더링을 방지하는 데 초점을 맞춤

  • 유연성: Zustand는 필요한 부분만 선택적으로 사용할 수 있지만, Redux Toolkit은 보다 강력한 구조화된 방법을 제공

  • 커뮤니티와 자료: 둘 다 인기가 많아서 자료가 많음

profile
개발자가 되고 싶어~~~

2개의 댓글

comment-user-thumbnail
2024년 6월 23일

역시 휘진님 정리를 엄청 깔끔하게 잘 하셨네요

1개의 답글