상태관리 라이브러리
Context-API
Context-api 사용하려면 Context 객체와 Provider가 필요하다.
- Context 객체 - createContext()로 만든다
- Provider - 전역 상태를 공유하게 하는 컴포넌트를 의미
→ 일반적으로, src폴더 아래에 context 폴더를 만들고 그 안에 Context 객체를 작성하고, context 폴더 안에 provider 폴더를 만들고 그 안에 Provider 컴포넌트를 작성한다.
Context객체는 ie. CounterContext.ts파일에 createContext()
로 만든다.
createContext는 react에서 import한다. → import { createContext } from "react";
export const CounterContext = createContext(null);
을 하면 CounterContext라는 객체를 만드는 건데, 타입을 지정해줘야 하니까 제네릭 사용. ie. export const Context객체명 = createContext<Context객체 타입 | null>(null);
ex.
import { createContext } from "react";
interface CounterContext {
count: number;
}
export const CounterContext = createContext<CounterContext | null>(null);
Provider 파일(ie. provider/CounterProvider.tsx)에서 Context객체에 value를 전달한다.
Context객체에는 value라는 속성이 있고, value에 아래와 같이 작성하면 count라는 상태값, increment, decrement, reset이 CounterProvider에 전달된 children으로 공유가 되게 된다.
<CounterContext value=8>
{children}
</CounterContext>
이렇게 Provider까지 작성하고 나면, App에서 Provider에서 공유하는 데이터들을 공유할 범위를 지정하면 된다.
→ 원하는 범위의 제일 상단 컴포넌트를 Provider 컴포넌트로 묶어주면 된다. 그러면 묶인 컴포넌트가 Provider의 children으로 들어가면서 데이터를 공유받을 수 있게 된다.
<CounterProvider>
<Count />
</CounterProvider>
공유되는 데이터를 사용하는 방법은
원하는 컴포넌트(Count가 될 수도 있고, Count 하위 컴포넌트 아무곳에서나)에서 useContext훅을 사용하면 된다.
useContext에는 Context 객체를 import해서 전달해주면 되고, 해당 값이 null 타입도 받기 때문에 !
로 널 아님 보장해주고, 원하는 데이터를 구조분해할당으로 받아서 사용하면 된다.
ie. const {count} = useContext(CounterContext)!;
import { useContext } from "react";
import { CounterContext } from "../../../context/CounterContext";
export default function CountDisplay() {
const { count } = useContext(CounterContext)!;
return (
<>
<h1>Count: {count}</h1>
</>
);
}
provider 컴포넌트는 provider 컴포넌트들끼리 계속 중첩할 수 있다.
provider 컴포넌트 밖에서 컴포넌트를 불러오면 밖에 불린 컴포넌트는 provider가 공급하는 데이터를 사용하지 못한다.
<CounterProvider>
<Count />
</CounterProvider>
<CountOutsider></CountOutsider>
이렇게 작성하면 CountOutsider는 Context-API에서 공급하는 데이터를 받지 못한다.
+나 -나 리셋 버튼을 클릭하면 CountDisplay컴포넌트는 리렌더링되는게 맞지만 CountButton 컴포넌트는 리렌더링 될 필요는 없다.
버튼 컴포넌트 내에서 변경되는 내용은 없으니까.
이상하게 전역 상태를 독립적으로 CountButton과 CountDisplay가 사용하고 있는데 +나 -버튼을 클릭하면 CountButton도 리렌더링되고 있다.
→ 전역적으로 공급받은 데이터를 사용하게 되면 Provider 컴포넌트가 리렌더링되면서 아래처럼 CounterContext의 value에 전달하는 객체
가 계속 새롭게 정의되고 있기 때문에 데이터를 공급받고 있는 컴포넌트들도 함께 리렌더링 되기 때문이다.
<CounterContext value=8>
{children}
</CounterContext>
⇒ 해결법
context 객체를 하나 더 만들면 된다.
CounterContext.ts 파일에 객체를 하나더 만든다
export const CounterContext = createContext<CounterContext | null>(null);
export const CounterActionContext = createContext<CounterActionContext | null>(null);
그래서 count 상태를 공급하는 Context 객체와 increment, decrement, reset 메서드를 공급하는 Context 객체를 나눠서 Provider에서 value를 각각 공급한다.
그리고 CounterContext의 value에 전달하는 객체가 계속 새롭게 정의되고 있는 문제를 해결하기 위해 useMemo(값 메모이제이션)를 해서 겉에 껍데기를 항상 메모이제이션 해서 값이 변경되었다고 생각하지 않게 해준다.
import { useMemo, useState } from "react";
import { CounterActionContext, CounterContext } from "../CounterContext";
export default function CounterProvider({ children }: { children: React.ReactNode }) {
const [count, setCount] = useState(0);
const increment = () => setCount((count) => count + 1);
const decrement = () => setCount((count) => count - 1);
const reset = () => setCount(0);
const memoization = useMemo(() => ({ increment, decrement, reset }), []);
return (
<>
<CounterActionContext value={memoization}>
<CounterContext value=8>{children}</CounterContext>
</CounterActionContext>
</>
);
}
Zustand(주스탄드)
설치가 필요하다.
https://zustand-demo.pmnd.rs/ → Documentation → npm install zustand
상태 관리 코드도 매우 간단하고,
최적화도 해준다. → 버튼 컴포넌트 리렌더링 되지 않는다.
→ 일반적으로, src폴더 안에 stores 폴더를 만들고 stores 폴더 안에 Store 파일을 작성한다.
zustand는 공급의 범위를 지정할 필요도 없고
스토어 작성 후 export해서 해당 스토어를 원하는 컴포넌트에서 import 해서 그냥 사용하면 됨.
store 파일 작성법
- zustand로부터 create를 import 한다. ie.
import { create } from "zustand";
- 스토어 변수를 만들고 그것을 export한다.
변수명은 use**Store로 짓는게 관례고, **은 파일명이다. ie. couterStore.ts 파일의useCounterStore
-
스토어는 create로 생성하는데, create 내부에는 상태값과, set을 사용한 상태값 변경 함수를 적는다.
type CounterStore = { count: number; increment: () => void; decrement: () => void; reset: () => void; }; export const useCounterStore = create<CounterStore>((set) => ({ count: 50, increment: () => set((state) => ({ count: state.count + 1 })), decrement: () => set((state) => ({ count: state.count - 1 })), reset: () => set({ count: 0 }), }));
zustand는 상태 값 변경할 때 set을 꼭 써야 하는데. useState의 setState와 똑같다.→ 똑같이 그냥 값을 전달하는 것과 ie.
set({count: 0})
콜백함수를 전달하는 방식이 있는데 ie.
set({(state) ⇒ ({count: state.count + 1})})
기준도 똑같다. 상태를 사용해서 업데이트하면 콜백, 아니면 그냥 값 전달하면 된다.
공급 범위를 지정할 필요가 없기 때문에 스토어를 원하는 컴포넌트에서 스토어를 import한다.
ie. import { useCounterStore } from "../../../stores/counterStore";
-
상태 변수를 사용하려면
→
const count = useCounterStore((state) ⇒ state.count);
-
상태 변수를 업데이트하는 메서드를 사용하려면
→
const increment = useCounterStore((state) ⇒ state.increment);
이렇게 그냥 꺼내 쓰면 된다.
import { useCounterStore } from "../../../stores/counterStore";
export default function CountDisplay() {
const count = useCounterStore((state) => state.count);
return (
<>
<h1>Count: {count}</h1>
</>
);
}
import { useCounterStore } from "../../../stores/counterStore";
export default function CountButton() {
const increment = useCounterStore((state) => state.increment);
const decrement = useCounterStore((state) => state.decrement);
const reset = useCounterStore((state) => state.reset);
return (
<>
<button onClick={decrement}>-</button>
<button onClick={reset}>0</button>
<button onClick={increment}>+</button>
</>
);
}
Leave a comment