728x90
전역 상태 관리에 대한 공부를 하던 중
React에서 지원하는 Context와 Recoil의 사용법과 차이에 대한 정리를 했다.
이에 대한 기록이다.
Context API
context를 사용할 때 고려해야 할 점
context의 주된 용도는 다양한 레벨에 네스팅된 많은 컴포넌트에게 데이터를 전달하는 것입니다. context를 사용하면 컴포넌트를 재사용하기가 어려워진다.
- 여러 레벨에 걸쳐 props 넘기는 걸 대체하는 데에 context보다 컴포넌트 합성이 더 간단한 해결책일 수도 있다.
- 데이터 값이 변할 때마다 모든 하위 컴포넌트에게 널리 Broadcast 하는 것이 context다.
createContext
createContext는 context 객체를 생성한다.
import {createContext} from 'react'
const MyContext = createContext(defaultValue);
- 위의 코드처럼 react의 createContext를 이용해 context 객체를 생성하면 된다.
- defaultValue는 트리 안에서 적절한 Provider를 찾지 못했을 때만 쓰이는 값이다.
Provider
Provider는 context를 구독하는 컴포넌트들에게 context의 변화를 알리는 역할을 한다.
<MyContext.Provider value={/* 어떤 값 */}>
- Provider은 value prop를 통해 하위 컴포넌트에게 전달한다.
- 하위 컴포넌트에서 value를 변경해야 한다면 value 객체에 value를 변경하는 메서드를 같이 보내주면 된다.
<CounterContext.Provider value={{counter,setCounter}}>
- 위 코드처럼 counter state를 변경시키는 수정 함수를 같이 보내줄 수 있다.
- Provider 하위에서 context를 구독하는 모든 컴포넌트는 Provider의 value가 바뀔 때마다 리렌더링된다.
- 그럼 하위 컴포넌트에서 어떻게 context를 구독할까?
- counter 예제를 통해 context 구독 방법을 보자.
useContext
useContext는 context 객체를 받아 그 context의 현재 값을 반환합니다.
useContext의 인자로 context 객체를 전달하는데 꼭 해당 context의 객체여야 한다.
위에서 언급한 대로 useContext를 통해 value를 전달받으면 value가 바뀔 때마다 리렌더링된다.
counter 예제는 옳은 예시는 아니지만
App 컴포넌트에서 버튼을 통해 count state를 증가시키고, Count 컴포넌트에서 증가한 count 값을 보여주는 예제이다.
//App.js
import { useState, createContext } from "react";
import Count from "./Count";
export const CounterContext = createContext(0);
export default function App() {
const [counter, setCounter] = useState(0);
const onClick = () => {
setCounter((prev) => prev + 1);
};
return (
<CounterContext.Provider value={counter}>
<Count />
<button onClick={onClick}>+1</button>
</CounterContext.Provider>
);
}
- counter state 값을 value로 전달했다.
//Count.js
import { useContext } from "react";
import { CounterContext } from "./App";
const Count = () => {
const counter = useContext(CounterContext);
return (
<div>
<h1>{counter}</h1>
</div>
);
};
export default Count;
- useContext를 통해 counter state값을 value로 전달받고, 그 값을 띄워줬다.
context에서 value객체를 전달받고,
그중 일부만을 사용한다고 했을 때 사용하지 않는 값의 변화에도 리렌더링이 일어날까?
이를 실험하기 위해 App 컴포넌트의 버튼을 Count 컴포넌트로 옮기고, value에 textColor를 추가했다.
억지스러운 예제지만 counter value를 증가시켰을 때 textColor만 사용하는 컴포넌트도 리렌더링되는지 실험해봤다.
//App.js
import { useState, createContext } from "react";
import Count from "./Count";
import Some from "./Some";
export const CounterContext = createContext({});
export default function App() {
const [counter, setCounter] = useState(0);
return (
<CounterContext.Provider value={{ counter, setCounter, textC: "red" }}>
<Count />
<Some/>
</CounterContext.Provider>
);
}
- App 컴포넌트에 Some 컴포넌트를 추가하고, value에 text color을 추가했다.
//Counter.js
import { useContext } from "react";
import { CounterContext } from "./App";
const Count = () => {
const { counter, textC, setCounter } = useContext(CounterContext);
const onClick = () => {
setCounter((prev) => prev + 1);
};
console.log(counter);
return (
<div>
<h1 style={{ color: textC }}>{counter}</h1>
<button onClick={onClick}>+1</button>
</div>
);
};
export default Count;
- Counter 컴포넌트로 버튼을 옮기고, onClick 이벤트를 추가해줬다.
//Some.js
import { useContext } from "react";
import { CounterContext } from "./App";
const Some = () => {
const { textC } = useContext(CounterContext);
console.log("render!");
return <div style={{ color: textC }}>Some Components</div>;
};
export default Some;
- Some 컴포넌트에서는 counter을 사용하지 않고 text color만을 사용했다.
- 그럼에도 불구하고 counter의 값이 증가할 때마다 Some 컴포넌트가 리렌더링되는 것을 볼 수 있다.
- Context를 사용해서 위의 문제를 해결하려면 별도의 context 객체를 만들어야 한다.
- recoil이 위의 문제를 해결할 수 있을까?
recoil
recoil은 상태 관리 라이브러리다.
recoil의 motivation을 보면, 전역 상태 관리의 필요성과 context의 단점을 보완하기 위해 만들어졌음을 알 수 있다.
Motivation
-컴포넌트의 상태는 공통된 상위 요소까지 끌어올려야만 공유될 수 있으며, 이 과정에서 거대한 트리가 다시 렌더링 되는 효과를 야기하기도 한다.
-Context는 단일 값만 저장할 수 있으며, 자체 소비자(consumer)를 가지는 여러 값들의 집합을 담을 수는 없다.
recoil의 핵심 개념과 자세한 사용법은 따로 다루기로 하고, 간단한 사용법과 문제점이 해결되었는지 살펴보자.
설치
$npm install recoil
$yarn add recoil
RecoilRoot
recoil을 사용하려면 Context의 Provider처럼 값을 전달한 하위 컴포넌트를 감싸는 컴포넌트가 필요하다.
RecoilRoot 컴포넌트는 위의 역할을 하며, 루트 컴포넌트가 RecoilRoot를 넣기에 가장 좋은 장소이다.
import {RecoilRoot} from 'recoil'
const App=()=>{
return(
<RecoilRoot>
<Counter/>
</RecoilRoot>
)}
- 위처럼 Counter 컴포넌트를 감싸주면 Counter에서 recoil이 제공하는 기능을 사용할 수 있다.
Atom
Atom은 전역 상태의 일부를 나타낸다. 따라서 어떤 컴포넌트에서나 읽고 쓸 수 있다.
- Atom을 사용하면 개별적인 전역 상태를 쉽게 만들어낼 수 있다.
const textState = atom({
key: 'textState', // unique ID (with respect to other atoms/selectors)
default: '', // default value (aka initial value)
});
- key는 atom을 구별하는 unique한 값이다.
- default는 말 그대로 default value다.
Handle Atom
Atom을 다루는 방법은 3가지가 있다.
1. useRecoilState : useState처럼 atom의 값과 수정 함수를 반환하는 메서드이다.
2. useRecoilValue : atom의 값만을 반환하는 메서드이다.
3. useSetRecoilState : atom의 수정 함수만을 반환하는 메서드이다.
const [text,setText]=useRecoilState(textState);
- 위에서 만든 atom을 인자로 전달한다.
위에서 직면한 문제를 해결해보자.
atom을 사용해서 text color와 counter를 만들고, counter 값이 바뀌었을 때
text color만을 사용하는 컴포넌트는 리렌더링되지 않으면 성공이다.
//atom.js
import { atom } from "recoil";
export const CounterAtom = atom({
key: "counter",
default: 0
});
export const textColor = atom({
key: "textColor",
default: "red"
});
- textColor와 Counter 값을 담은 atom을 만들어줬다.
//Count.js
import { useEffect } from 'react';
import { useRecoilValue, useRecoilState } from "recoil";
import { CounterAtom, textColor } from "./atom";
const Count = (props) => {
const [counterState, setCounter] = useRecoilState(CounterAtom);
const textC = useRecoilValue(textColor);
useEffect(() => {
console.log(counterState);
}, [counterState]);
const onClick = () => {
setCounter((prev) => prev + 1);
};
return (
<div>
<h1 style={{ color: textC }}>{counterState}</h1>
<button onClick={onClick}>+1</button>
</div>
);
};
export default Count;
- Count 컴포넌트에서는 counter atom value와 text Color atom value를 모두 사용했다.
- 추가로 counter atom 값을 변경시킬 수 있게 해주고 useEffect 의존성 배열을 이용해 counter 값이 바뀔 때마다 counter 값을 볼 수 있도록 했다.
- useEffect에 관해서는 위의 글에 정리했다.
//Some.js
import { useRecoilValue } from "recoil";
import { textColor } from "./atom";
const Some = () => {
const textC = useRecoilValue(textColor);
console.log("render!");
return <div style={{ color: textC }}>Some Components</div>;
};
export default Some;
- Some 컴포넌트에서는 text Color atom value만을 사용했다.
- counter 값이 변해도 Some 컴포넌트가 리렌더링되지 않는 것을 볼 수 있다.
recoil 기능을 제공하는 Provider를 공유하면서
쉽게 만든 별도의 atom을 사용할 수 있다는 점에서 편리한 것 같다.
recoil에 관해 자세히 다루면서 그 구조에 대해서도 알아보면 좋을 것 같다.
728x90
'Frontend > React' 카테고리의 다른 글
[React] 폰트 깜빡임 현상 해결(Global Style, CSS) (0) | 2022.12.29 |
---|---|
[React] Recoil(Atom, Selectors, Atom Effects) (0) | 2022.12.18 |
[React] CRA 프로젝트 초기 설정(ESLint,Prettier) (0) | 2022.11.16 |
[React] Server State&Client State,React-Query (0) | 2022.09.30 |
[React] 리액트 토스트 메세지 구현하기(react-toastify) (0) | 2022.09.27 |