사용 기술 스택
버전 관리
협업툴
간략한 구현 설명 사진

메인 페이지

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

해당 전시를 누르면 상세정보와 공식 홈페이지로 이동시켜주는 태그도 생성
이번 아웃소싱 프로젝트를 통해 느낀 점을 말해보자면 팀원 분들은 다 잘해주셔서 아쉬운 점은 없지만 개인적으론 카카오 맵 오픈 API를 처음 써봐서 그런지 이해도가 부족하기에 원하는 기능을 구현하지 못한 것이 아쉬운 부분이다. ex) API를 불러올 때 키워드 필터링
이번에 외부 API를 사용해 과제를 진행하면서 카카오 API를 불러와서 쓰는 법을 배웠기에 다음에도 관련된 프로젝트를 진행할 시 이번 경험을 바탕으로 도움이 많이 될 거 같다
팀원 간의 커뮤니케이션이 활발하게 이루어져 원하는 결과물을 만들 수 있었던 거 같다!
이번 과제를 진행하면서 외부 api에 대한 이해도가 조금은 올라간 거 같고 해당 과제를 진행하면서 겪었던 트러블슈팅 이라면
박물관에 관련된 정보만 보여주고 싶은데 사이트 기획 의도에 맞지 않게 키워드 검색칸에 식당, 오마카세, 디저트 등등 전혀 관련없는 키워드를 입력해도 해당 키워드에 대한 검색 결과값이 출력이 됐었는데 해당 문제를 해결하기 위해서 form onSubmit={} 에 들어가는 handleSearch 함수에서 "박물관", "뮤지엄”, “미술관” 키워드가 포함되어 있어야만 검색 기능이 실행되게끔 하니깐 얼추 겉으로는 되는 거 처럼 보이지만 “뮤지엄”이 들어간 “뮤지엄산” 이라던가 해당 키워드가 들어간 다른 장소가 뜨는 부분은 해결을 못한 것이 너무 아쉬운 점이기에 추후 개선을 해보고 싶은 마음이다.
프로젝트를 진행하면서 협업에 관한 트러블슈팅은 없었던 걸 보면 모든 팀원들이 착착 손발이 잘 맞았기에 무탈하게 주어진 과제를 완성해낼 수 있었다고 생각한다.
=========================================
(1) 상태관리의 중요성
상태관리는 모든 리액트 애플리케이션 에서 매우 중요, 애플리케이션이 커질수록 상태관리는 더 복잡해짐
(2) Zustand의 필요성
Zustand는 상태관리 본연의 기능에 집중하여 복잡성을 줄이고, 보다 간단하고 직관적인 상태관리 기능을 제공
Zustand는 단순화된 Flux 패턴을 사용하는 작고 빠르고 확장가능한 상태관리 솔루션이며, Hooks에 기반으로 하는 간편한 API가 존재
(1) Zustand의 주요 특징
간결함
성능
불필요한 리렌더링을 방지하는 등 성능 최적화 GOOD
Zustand는 상태가 변경될 때, 해당 상태를 구독하고 있는 컴포넌트만 리렌더링
(구독이란? => 상태의 변경을 감지하고, 해당 변경에 반응하는 컴포넌트만 업데이트하는 메커니즘을 의미함. 우리가 유튜브 채널을 구독한다면, 채널에서 새로운 영상이나 공지가 올라오면 알림이 오는 것과 같은 개념)
상태의 일부가 변경되었을 때 그 상태를 사용하는 컴포넌트만 업데이트되므로 이를 통해 성능을 최적화하고 리렌더링으로 인한 성능 저하를 방지
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;
(1) 설정 및 사용법 비교
import { create } from "zustand";
const useStore = create(set => ({
bears: 0,
increasePopulation: () => set(state => ({ bears: state.bears + 1 }))
}))
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) 장단점 비교 (정리)
-Redux Toolkit
(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 란?
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;
(1) 장점
(2) 단점
(3) Redux Toolkit과의 비교
설정과 사용법: Zustand는 설정과 사용이 간단하며, Redux Toolkit은 더 구조화된 방법을 제공
성능: 두 라이브러리 모두 성능 최적화가 잘 되어 있지만, Zustand는 불필요한 리렌더링을 방지하는 데 초점을 맞춤
유연성: Zustand는 필요한 부분만 선택적으로 사용할 수 있지만, Redux Toolkit은 보다 강력한 구조화된 방법을 제공
커뮤니티와 자료: 둘 다 인기가 많아서 자료가 많음
역시 휘진님 정리를 엄청 깔끔하게 잘 하셨네요