상태관리 라이브러리 | Recoil

Kyu-hyun Lee
14 min readJun 10, 2023

--

상태관리 라이브러리

다양한 상태관리 라이브러리들은 문법이 다를 뿐, 기본적으로 같은 역할을 수행합니다. 상태관리 라이브러리는 State를 관리해주는 라이브러리를 말합니다. State는 한글로 ‘상태값’이라고 하며 일종의 변수와 같은 것입니다.

React에서는 일반적인 변수들은 아무리 바뀌어도 재렌더링되지 않지만, State는 바뀔 때마다 다시 렌더링되기 때문에 중요한 변수들은 State를 사용하게 됩니다.

React Hooks의 useState를 사용해 State를 선언하는 문법은 아래와 같습니다.

const [state, SetState] = useState("초기값"); // state는 현재 값, SetState는 Setter 함수j상태 관리 라이브러리는 이렇게 선언된 State를 여러 Component에서 쉽게 사용할 수 있도록 하며, State들을 한 파일에서 쉽게 관리할 수 있기 때문에 사용됩니다.

상태관리 라이브러리를 사용하지 않을때

상태관리 라이브러리를 사용하지 않을 경우, State 값을 여러 컴포넌트에서 사용하려면 Props를 이용해 부모 컴포넌트에서 자식 컴포넌트로 값을 전달해야 합니다. Props는 컴포넌트 간에 값을 전달하는 수단으로, 함수의 파라미터와 비슷한 역할을 합니다.

Props의 기본 사용법은 다음과 같습니다.

//App.jsx

import React, { useState } from 'react';
import Hello from './Hello';

function App() {
const [name, SetName] = useState("");

return (
<Hello name={name} /> // name 속성을 자식 컴포넌트에 전달함
);
}

export default App;
//Hello.jsx

import React from 'react';

function Hello(props) {
return <div>안녕하세요 {props.name}</div> // name이라는 state를 prop.name으로 받아옴
}

export default Hello;

아래 그림은 부모 컴포넌트에서 자식 컴포넌트로 Props를 이용하여 State 값을 전달하는 방법을 보여주고 있습니다. 부모 컴포넌트에서 State 값을 Props로 지정하여 자식 컴포넌트에 전달하고, 자식 컴포넌트에서는 Props를 통해 전달받은 State 값을 사용하여 화면을 렌더링합니다.

만약 자식의 자식 컴포넌트에서 State를 사용하려면 부모 컴포넌트에서 자식 컴포넌트로 props를 넘겨주고, 자식 컴포넌트에서 다시 자식의 자식 컴포넌트로 props를 넘겨주어야 합니다. 이렇게 props로 값을 넘겨받은 후 다시 넘겨주어야 하는 과정은 번거롭습니다. 게다가 자식 컴포넌트에서 부모의 state를 변경하고자 할 때도 props는 읽기 전용이므로 복잡한 과정을 거쳐야 합니다.

상태관리 라이브러리를 사용할때

상태관리 라이브러리를 사용하면, 한 파일에 State를 모아두고 어디에서든지 해당 State를 사용할 수 있습니다. 즉, State를 부모 컴포넌트에서 자식 컴포넌트로 props로 넘겨주지 않아도 됩니다. 또한, 상태관리 라이브러리는 props와 달리 읽기와 쓰기 모두가 가능합니다. 따라서 자식 컴포넌트에서 부모 State를 직접 변경할 수 있으며, 해당 State가 변경되면 구독하는 모든 컴포넌트가 자동으로 재렌더링됩니다. 이를 통해 State를 좀 더 효율적으로 관리할 수 있으며, 컴포넌트 간의 연결고리를 간단하게 만들 수 있습니다.

이렇게 상태 관리 라이브러리를 사용하면 State 관리가 매우 쉬워집니다. Recoil은 이러한 상태 관리 라이브러리 중 하나이며, State를 효과적으로 관리할 수 있습니다. 이제 Recoil을 어떻게 사용하는지 자세히 알아보도록 하겠습니다.

Recoil

Recoil은 문법이 React Hooks와 유사하여, 현재 업계에서 표준으로 자리잡은 Redux보다 사용하기 쉬운 상태관리 라이브러리입니다. Recoil의 세팅부터 atom, selector까지 알아보겠습니다.

Recoil 세팅

먼저 Recoil을 사용하기 위해서는 설치가 필요합니다.

npm install recoil

혹은

yarn add recoil

다음으로, Recoil을 사용하기 위해서는 루트 컴포넌트에서 RecoilRoot를 사용해야 합니다.

//app.tsx

import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import { RecoilRoot } from 'recoil';

import MainPage from './pages/MainPage';
import WritingPage from './pages/WritingPage';

function App() {
return (
<RecoilRoot>
<Router>
<Routes>
<Route path="/" element={<MainPage />} />
<Route path="/write" element={<WritingPage />} />
</Routes>
</Router>
</RecoilRoot>
);
}
export default App;

이제 Recoil을 사용할 준비가 완료되었습니다!

Atom

Recoil 공식 문서에서는 Atom을 상태의 일부로 설명합니다. 간단하게 말하면 State를 선언하는 것입니다. Atom은 아래와 같이 선언할 수 있습니다.

const textState = atom({
key: 'textState', // key는 atom의 고유한 식별자로서, 다른 atom이나 selector와 중복되면 안 됩니다.
default: '', // default는 atom의 초기값을 말합니다.
});

선언한 Atom을 사용하려면 useRecoilState 이용하면 됩니다.

const [text, setText] = useRecoilState(textState);

위와 같이 사용하면, Recoil Atom을 React Hook의 useState와 똑같이 사용할 수 있습니다.

참고로 값을 읽어오기만 하려면 useRecoilValue를 쓰면 됩니다.

const text = useRecoilValue(textState);

저는 프로젝트에서 글쓰기 페이지를 만들 때 이렇게 Atom들을 모아두는 파일을 따로 만들어서 사용했습니다.

//Recoil.tsx

import { atom } from 'recoil';

const titleState = atom<string>({
key: 'titleState',
default: '',
});
const tldrState = atom<string>({
key: 'tldrState',
default: '',
});

export {
titleState,
tldrState,
};
//WritingPage.tsx

import React, { useEffect, useState } from 'react';
import { useRecoilState } from 'recoil';
import {
titleState,
tldrState,
} from '../Recoil';

function WritingPage() {
const [title, setTitle] = useRecoilState(titleState);
const [tldr, setTldr] = useRecoilState(tldrState);

//제목 변경 함수
const handleTitleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setTitle(e.target.value);
};
//소개 변경 함수
const handleTldrChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setTldr(e.target.value);
};

return (
<div>
{/*텍스트 구역*/}
<div>
{/*제목 상자*/}
<input
autoFocus
id="title"
type="text"
placeholder="제목을 입력해주세요."
value={title}
onChange={handleTitleChange}
/>
{/*한줄 소개 상자*/}
<input
id="tldr"
type="text"
placeholder="한 줄 소개를 입력해주세요."
value={tldr}
onChange={handleTldrChange}
/>
</div>
</div>
);
}

export default WritingPage;

Selector

Recoil 공식 문서에서는 Selector를 Atom을 기반으로 한 파생된 상태(derived state)의 일부로 설명합니다. 이것을 간단히 설명하면 Atom을 기반으로 하는 여러 컴포넌트에서 사용할 수 있는 함수와 같다고 생각하면 됩니다. API와 같은 역할을 수행하기 때문에 컴포넌트에서 수행하는 연산이 줄어들고, 관리 포인트가 분리되는 장점이 있습니다. 컴포넌트는 Selector에 값만 제공하고, Selector는 그 값을 가지고 일부 연산을 수행한 후 반환하는 값을 화면에 뿌려주는 방식으로 작동합니다.

다음은 Selector는 선언의 예시입니다.

const tempCelsius = selector({
// key는 Selector의 고유한 id값으로, 다른 atom들이나 selector들과 중복되면 안 됩니다.
key: 'tempCelsius',
// get함수는 get 메서드를 파라미터로 받고, 이 메서드를 사용하여 atom 값을 가져올 수 있습니다.
get: ({get}) => ((get(tempFahrenheit) - 32) * 5) / 9,
// set함수는 set 메서드와 입력값을 파라미터로 받아 계산하는 함수를 작성할 수 있습니다.
set: ({set}, newValue) => set(tempFahrenheit, (newValue * 9) / 5 + 32),
});

Atom과 마찬가지로 Selector을 사용하려면 useRecoilState 이용하면 됩니다.

const [tempC, setTempC] = useRecoilState(tempCelsius);

예시 1

import {atom, selector, useRecoilState} from 'recoil';

const tempFahrenheit = atom({
key: 'tempFahrenheit',
default: 32,
});

const tempCelsius = selector({
key: 'tempCelsius',
get: ({get}) => ((get(tempFahrenheit) - 32) * 5) / 9,
set: ({set}, newValue) => set(tempFahrenheit, (newValue * 9) / 5 + 32),
});

function TempCelsius() {
const [tempF, setTempF] = useRecoilState(tempFahrenheit);
const [tempC, setTempC] = useRecoilState(tempCelsius);

const addTenCelsius = () => setTempC(tempC + 10);
const addTenFahrenheit = () => setTempF(tempF + 10);

return (
<div>
Temp (Celsius): {tempC}
<br />
Temp (Fahrenheit): {tempF}
<br />
<button onClick={addTenCelsius}>Add 10 Celsius</button>
<br />
<button onClick={addTenFahrenheit}>Add 10 Fahrenheit</button>
</div>
);
}

결과

예시 2

//Recoil.jsx

import { atom, selector } from 'recoil';

const textState = atom({
key: 'textState',
default: '',
});

const charCountState = selector({
key: 'charCountState', // unique ID (with respect to other atoms/selectors)
get: ({get}) => {
const text = get(textState);

return text.length;
},
});

export {
textState,
charCountState,
};
//CharacterCount.jsx

import { useRecoilState } from 'recoil';
import { textState, charCountState } from './Recoil';

function CharacterCounter() {
const [text, setText] = useRecoilState(textState);
const count = useRecoilValue(charCountState);

const onChange = (event) => {
setText(event.target.value);

return (
<div>
<div>
<input type="text" value={text} onChange={onChange} />
<br />
Echo: {text}
</div>
Character Count: {count}
</div>
);
};
}

결과

마치며

상태 관리 라이브러리는 State를 관리하기 쉬워지기 때문에 컴포넌트가 많은 대규모 프로젝트에서는 필수적으로 사용되는 라이브러리입니다. 현재 업계 표준인 Redux는 문법이 조금 어렵다는 평가가 있지만, React Hooks와 유사한 문법을 가진 Recoil은 이해하기 쉬워 입문자도 쉽게 접근할 수 있습니다. 상태 관리 라이브러리를 사용해보면 State 관리에 대한 이해도가 높아지고, 개발 실력 또한 향상됩니다. 따라서 상태 관리 라이브러리를 사용해보고 싶은 입문자라면 Recoil을 추천합니다.

Reference

https://www.youtube.com/watch?v=QZcYz2NrDIs

https://recoiljs.org/ko/

--

--