티스토리 뷰
🐻 Zustand
Zustand는 React를 위한 상태 관리 라이브러리이다.
Zustand는 상태 관리를 위해 React의 Context API와 함께 사용되며, Redux보다 훨씬 간단하고 직관적이다.
Zustand는 상태를 관리하기 위해 React 컴포넌트 트리 내에서 사용되는 상태 컨테이너를 제공하며,
useState와 useReducer를 사용하거나 더 간단한 API를 제공해 상태를 업데이트하고 구독하는 방법을 단순화한다.
Zustand의 특징
1. 간결함과 유연성
상태 관리를 위한 간단한 API를 제공하여 복잡한 설정이나 보일러플레이트 코드 없이 쉽게 통합할 수 있다.
2. React Hooks와의 통합
React Hooks를 기반으로 한 상태 관리를 제공하여, 상태를 정의하고 사용하는 것이 매우 직관적이다.
3. 모듈화 패턴 지원
큰 스토어를 작은 개별 스토어로 나누어 관리할 수 있는 Slices 패턴을 지원한다.
이를 통해 코드의 모듈화와 유지보수가 쉬워진다.
4. 미들웨어 지원
'immer'와 같은 미들웨어를 통해 불변 상태 관리를 간편하게 할 수 있다.
'persist' 미들웨어를 통해 스토어 데이터를 지속적으로 저장할 수도 있다.
5. 타입스크립트 지원 강화
Zustand는 타입스크립트를 완벽하게 지원해 타입 안전성을 높이고 개발 경험을 개선한다.
Zustand 사용하기
- 설치하기
>npm i zustand
- store 만들기
상태와 상태를 업데이트하는 액션을 정의한다.
src 디렉토리 내부에 store 폴더를 만들고 안에 파일을 작성한다.
// src/store/modal.ts
import { Post } from "@/model/Post";
import { create } from "zustand";
interface ModalState {
mode: "new" | "comment"; // 특수한 경우(post가 존재하는 경우)에는 답글
data: Post | null;
setMode(mode: "new" | "comment"): void;
setData(data: Post | null): void;
reset(): void;
}
export const useModalStore = create<ModalState>((set) => ({
// 초기값
mode: "new", // 기본적으로 게시글 작성은 새 글
data: null,
// mode를 바꾸는 set함수
setMode(mode) {
set({ mode });
},
// data를 바꾸는 set함수
setData(data) {
set({ data });
},
// reset하는 함수
reset() {
set({
mode: "new",
data: null,
});
},
}));
create 함수에 mode와 data 초기 설정을 해주고 이 상태를 변경할 set함수와 상태를 초기화 할 reset 함수를 설정한다.
- create
- set 매개변수를 가짐
- 객체를 리턴
- store 사용하기
포스트에 대한 답글을 작성할 때 현재 mode와 원글에 대한 상태를 전달한다.
export default function ActionButtons({ white, post }: Props) {
...
// modalStore 가져오기
const modalStore = useModalStore();
const onClickComment: MouseEventHandler<HTMLButtonElement> = (e) => {
e.stopPropagation();
// 어떤 포스트에 대한 답글인지 정보 보내주기
// -> 컴포넌트 간 상태 공유
modalStore.setMode("comment"); // mode는 comment
modalStore.setData(post); // data로 post를 전달
// /compose/tweet으로 이동
router.push(`/compose/tweet`);
};
return (
<div className={style.actionButtons}>
<div className={cx(style.commentButton, white && style.white)}>
<button onClick={onClickComment}>
<svg width={24} viewBox="0 0 24 24" aria-hidden="true">
<g>
<path d="M1.751 10c0-4.42 3.584-8 8.005-8h4.366c4.49 0 8.129 3.64 8.129 8.13 0 2.96-1.607 5.68-4.196 7.11l-8.054 4.46v-3.69h-.067c-4.49.1-8.183-3.51-8.183-8.01zm8.005-6c-3.317 0-6.005 2.69-6.005 6 0 3.37 2.77 6.08 6.138 6.01l.351-.01h1.761v2.3l5.087-2.81c1.951-1.08 3.163-3.13 3.163-5.36 0-3.39-2.744-6.13-6.129-6.13H9.756z"></path>
</g>
</svg>
</button>
<div className={style.count}>{post._count?.Comments || ""}</div>
</div>
...
</div>
);
}
글 작성 모달에서는 상태에 따라 답글인 경우에는 원글에 대한 정보를 표시해야 한다.
zustand store에서 모드를 읽어온다.
export default function TweetModal() {
...
const modalStore = useModalStore();
// 원글
const parent = modalStore.data;
const mutation = useMutation({
mutationFn: async (e: FormEvent) => {
e.preventDefault();
const formData = new FormData();
formData.append("content", content);
preview.forEach((p) => {
p && formData.append("images", p.file);
});
return fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/posts`, {
method: "post",
credentials: "include",
body: formData,
});
},
async onSuccess(response, variable) {
...
},
onError(error) {
console.error(error);
alert("업로드 중 에러가 발생했습니다.");
},
onSettled() {
// 완료 후 모달창 닫기
router.back();
},
});
const comment = useMutation({
mutationFn: (e: FormEvent) => {
e.preventDefault();
const formData = new FormData();
formData.append("content", content);
preview.forEach((p) => {
p && formData.append("images", p.file);
});
return fetch(
`${process.env.NEXT_PUBLIC_BASE_URL}/api/posts/${parent?.postId}/comments`,
{
method: "post",
credentials: "include",
body: formData,
}
);
},
async onSuccess(response, variable) {
...
},
onError(error) {
console.error(error);
alert("업로드 중 에러가 발생했습니다.");
},
onSettled() {
// store 초기화
modalStore.reset();
router.back();
},
});
const onClickButton = () => {
imageRef.current?.click();
};
const onRemoveImage = (index: number) => () => {
setPreview((prevPreview) => {
const prev = [...prevPreview];
prev[index] = null;
return prev;
});
};
const onSubmit: FormEventHandler<HTMLFormElement> = (e) => {
if (modalStore.mode === "new") {
mutation.mutate(e);
} else {
comment.mutate(e);
}
};
return (
<div className={style.modalBackground}>
<div className={style.modal}>
...
<form className={style.modalForm} onSubmit={onSubmit}>
{modalStore.mode === "comment" && parent && (
<div className={style.modalOriginal}>
<div className={style.postUserSection}>
<div className={style.postUserImage}>
<img src={parent.User.image} alt={parent.User.id} />
</div>
</div>
<div>
{parent.content}
<div>
<Link
href={`/${parent.User.id}`}
style={{ color: "rgb(29, 155, 240)" }}
>
@{parent.User.id}
</Link>
님에게 보내는 답글
</div>
</div>
</div>
)}
...
</div>
...
</form>
</div>
</div>
);
}
mode의 상태에 따라 새 글이면 mutation을 호출하고 답글이면 comment를 호출한다.
🌟comment 요청의 경우 요청이 완료가 되면 스토어를 초기화해야 새 글 작성 시 데이터가 남아있지 않는다!
'코딩 > 코딩노트' 카테고리의 다른 글
[Zustand] Next.js에서 전역 상태 관리하기 (0) | 2024.11.27 |
---|---|
[ESLint] 자바스크립트에서 타입스크립트로 마이그레이션 시 발생한 오류에 대해.. (0) | 2024.11.11 |
[ESLint, Prettier] ESLint와 Prettier에 대해 알아보자! (0) | 2024.07.16 |
[React Query] react-query로 무한 스크롤링 구현하기 (0) | 2024.06.20 |
백엔드에서 API를 아직 만들지 않았다면 MSW를 사용하자! - 회원가입 (0) | 2024.05.30 |