Pink Bobblehead Bunny ๋ฆฌ์•กํŠธ ์ƒํƒœ ๊ด€๋ฆฌ, Recoil ์ดํ•ดํ•˜๊ธฐ
 

๋ฆฌ์•กํŠธ ์ƒํƒœ ๊ด€๋ฆฌ, Recoil ์ดํ•ดํ•˜๊ธฐ

์•ˆ๋…•ํ•˜์„ธ์š”! ๋ฆฌ์•กํŠธ ๊ฐœ๋ฐœ์ž๋ผ๋ฉด ํ•œ ๋ฒˆ์ฏค ์ƒํƒœ ๊ด€๋ฆฌ์˜ ๋Šช์— ๋น ์ ธ๋ณธ ๊ฒฝํ—˜์ด ์žˆ์œผ์‹ค ๊ฒ๋‹ˆ๋‹ค. Prop-drilling์˜ ๊ณ ํ†ต, Redux์˜ ๋ณต์žกํ•œ ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ... ์ƒ๊ฐ๋งŒ ํ•ด๋„ ๋จธ๋ฆฌ๊ฐ€ ์•„ํŒŒ์˜ค์ฃ . ๐Ÿคฏ

์ด๋Ÿฐ ๊ณ ๋ฏผ์„ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ํŽ˜์ด์Šค๋ถ(ํ˜„ Meta)์—์„œ ์ง์ ‘ ์„ ๋ณด์ธ ์ƒํƒœ ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ, Recoil์— ๋Œ€ํ•ด ๊นŠ์ด ์žˆ๊ฒŒ ์•Œ์•„๋ณด๋ ค ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฒˆ ํฌ์ŠคํŒ…์—์„œ๋Š” Recoil์ด ๋ฌด์—‡์ธ์ง€, ์™œ ์šฐ๋ฆฌ๊ฐ€ Recoil์„ ์ฃผ๋ชฉํ•ด์•ผ ํ•˜๋Š”์ง€, ๊ทธ๋ฆฌ๊ณ  ๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•๋ถ€ํ„ฐ ์‹ค์šฉ์ ์ธ ๊ฟ€ํŒ๊นŒ์ง€ ๋ชจ๋‘ ๋‹ด์•˜์Šต๋‹ˆ๋‹ค.


Recoil, ์™œ ๋“ฑ์žฅํ–ˆ์„๊นŒ์š”? ๐Ÿค”

๊ธฐ์กด์˜ ๊ฐ€์žฅ ์œ ๋ช…ํ•œ ์ƒํƒœ ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ธ Redux๋Š” ๊ฐ•๋ ฅํ•˜์ง€๋งŒ, ๋ฐฐ์šฐ๊ธฐ ์–ด๋ ต๊ณ  ์„ค์ •์ด ๋ณต์žกํ•˜๋‹ค๋Š” ๋‹จ์ ์ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ๊ฐ„๋‹จํ•œ ์ƒํƒœ ํ•˜๋‚˜๋ฅผ ์ถ”๊ฐ€ํ•˜๋ ค ํ•ด๋„ ์•ก์…˜, ๋ฆฌ๋“€์„œ, ๋””์ŠคํŒจ์น˜ ๋“ฑ ์ˆ˜๋งŽ์€ ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ–ˆ์ฃ .

Recoil์€ ์ด๋Ÿฌํ•œ ๋ฌธ์ œ์˜์‹์—์„œ ์ถœ๋ฐœํ–ˆ์Šต๋‹ˆ๋‹ค. "๋งˆ์น˜ ๋ฆฌ์•กํŠธ์˜ useState๋ฅผ ์‚ฌ์šฉํ•˜๋“ฏ, ๊ฐ„๋‹จํ•˜๊ณ  ์ง๊ด€์ ์œผ๋กœ ์ „์—ญ ์ƒํƒœ๋ฅผ ๋‹ค๋ฃฐ ์ˆ˜๋Š” ์—†์„๊นŒ?"๋ผ๋Š” ์ƒ๊ฐ์—์„œ ํƒ„์ƒํ•œ ๊ฒƒ์ด์ฃ . Recoil์€ ๋ฆฌ์•กํŠธ์˜ ๋‚ด๋ถ€ ๊ตฌ์กฐ์™€ ๋งค์šฐ ํก์‚ฌํ•˜๊ฒŒ ๋™์ž‘ํ•˜์—ฌ, ๋ฆฌ์•กํŠธ ๊ฐœ๋ฐœ์ž๋ผ๋ฉด ๋ณ„๋„์˜ ํ•™์Šต ๊ณก์„  ์—†์ด ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Recoil์˜ ํ•ต์‹ฌ ์ฒ ํ•™:

  • React-ish: ๋ฆฌ์•กํŠธ์˜ ์ฒ ํ•™์„ ๊ทธ๋Œ€๋กœ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค. Hooks์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉฐ, ์„ ์–ธ์ ์œผ๋กœ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
  • ๊ฐ„๊ฒฐํ•จ (Minimal & Boilerplate-free): ๋ถˆํ•„์š”ํ•œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ํ•„์š” ์—†์ด, atom ํ•˜๋‚˜๋กœ ์ „์—ญ ์ƒํƒœ๋ฅผ ๋š๋”ฑ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ํŒŒ์ƒ ๋ฐ์ดํ„ฐ (Derived data): ๊ธฐ์กด ์ƒํƒœ ๊ฐ’์„ ์กฐํ•ฉํ•˜๊ฑฐ๋‚˜ ๊ฐ€๊ณตํ•˜์—ฌ ์ƒˆ๋กœ์šด ์ƒํƒœ๋ฅผ ๋งŒ๋“œ๋Š” selector ๊ธฐ๋Šฅ์ด ๋งค์šฐ ๊ฐ•๋ ฅํ•˜๊ณ  ํšจ์œจ์ ์ž…๋‹ˆ๋‹ค.
  • ์•ฑ ์ „์ฒด์˜ ์ƒํƒœ ๊ด€์ฐฐ: ์ƒํƒœ ๋ณ€ํ™”์— ๋”ฐ๋ฅธ ๋ฆฌ๋ Œ๋”๋ง์„ ์ตœ์ ํ™”ํ•˜๊ณ , ๋””๋ฒ„๊น…์„ ์šฉ์ดํ•˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

Recoil์˜ ๋‘ ๊ฐ€์ง€ ํ•ต์‹ฌ ๊ฐœ๋…: Atom & Selector

Recoil์„ ์ดํ•ดํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋”ฑ ๋‘ ๊ฐ€์ง€๋งŒ ๊ธฐ์–ตํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค: atom๊ณผ selector.

1. Atom: ์ƒํƒœ์˜ ์ตœ์†Œ ๋‹จ์œ„ โš›๏ธ

Atom์€ ์ƒํƒœ(state)์˜ ๊ฐ€์žฅ ์ž‘์€ ๋‹จ์œ„์ž…๋‹ˆ๋‹ค. ํ•˜๋‚˜์˜ atom์€ ํ•˜๋‚˜์˜ ์ƒํƒœ ๊ฐ’์„ ์˜๋ฏธํ•˜์ฃ . ์ปดํฌ๋„ŒํŠธ๋“ค์€ ์ด atom์„ ๊ตฌ๋…(subscribe)ํ•˜๊ณ , atom์˜ ๊ฐ’์ด ๋ณ€๊ฒฝ๋˜๋ฉด ๊ตฌ๋…ํ•˜๋Š” ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง ๋ฉ๋‹ˆ๋‹ค.

๋งˆ์น˜ ๋ฆฌ์•กํŠธ์˜ useState์™€ ๋น„์Šทํ•˜์ง€๋งŒ, ์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€์— ์ข…์†๋˜์ง€ ์•Š๊ณ  ์•ฑ ์–ด๋””์„œ๋“  ๊ณต์œ ํ•  ์ˆ˜ ์žˆ๋Š” ์ „์—ญ useState๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ์‰ฝ์Šต๋‹ˆ๋‹ค.

๐Ÿ“‹ Atom ์˜ˆ์ œ

// state/todoState.js
import { atom } from 'recoil';

// ํ•  ์ผ ๋ชฉ๋ก์„ ์ €์žฅํ•  atom
export const todoListState = atom({
  key: 'todoListState', // ๐Ÿ’ฅ ์•ฑ ์ „์ฒด์—์„œ ๊ณ ์œ ํ•ด์•ผ ํ•˜๋Š” ํ‚ค!
  default: [],          // ๊ธฐ๋ณธ๊ฐ’ (๋นˆ ๋ฐฐ์—ด)
});

// ํ•  ์ผ ์ž…๋ ฅ์ฐฝ์˜ ํ…์ŠคํŠธ๋ฅผ ๊ด€๋ฆฌํ•  atom
export const todoInputState = atom({
    key: 'todoInputState',
    default: '', // ๊ธฐ๋ณธ๊ฐ’ (๋นˆ ๋ฌธ์ž์—ด)
});

key๋Š” ๋‚˜์ค‘์— ์ƒํƒœ๋ฅผ ๋””๋ฒ„๊น…ํ•˜๊ฑฐ๋‚˜ ํŠน์ • ์ƒํƒœ๋ฅผ ์‹๋ณ„ํ•  ๋•Œ ์‚ฌ์šฉ๋˜๋ฏ€๋กœ, ์ถฉ๋Œํ•˜์ง€ ์•Š๋„๋ก ๋ช…ํ™•ํ•˜๊ฒŒ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

2. Selector: ํŒŒ์ƒ๋œ ์ƒํƒœ โœจ

Selector๋Š” Recoil์˜ ์ง„์ •ํ•œ ๊ฐ•๋ ฅํ•จ์ด ๋“œ๋Ÿฌ๋‚˜๋Š” ๋ถ€๋ถ„์ž…๋‹ˆ๋‹ค. selector๋Š” ๊ธฐ์กด atom์ด๋‚˜ ๋‹ค๋ฅธ selector์˜ ์ƒํƒœ ๊ฐ’์„ ๊ฐ€์ ธ์™€, ๊ทธ๊ฒƒ์„ ๋ฐ”ํƒ•์œผ๋กœ ์ƒˆ๋กœ์šด ํŒŒ์ƒ๋œ ๋ฐ์ดํ„ฐ(Derived State)๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ์ˆœ์ˆ˜ ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, ์ „์ฒดํ•  ์ผ ๋ชฉ๋ก(atom)์—์„œ '์™„๋ฃŒ๋œ ํ•  ์ผ'์˜ ๊ฐœ์ˆ˜๋งŒ ๋”ฐ๋กœ ๊ณ„์‚ฐํ•˜๊ณ  ์‹ถ์„ ๋•Œ selector๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฐ€์žฅ ์ค‘์š”ํ•œ ํŠน์ง•์€ ์บ์‹ฑ(Caching/Memoization) ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. selector๊ฐ€ ์˜์กดํ•˜๋Š” atom์˜ ๊ฐ’์ด ๋ณ€๊ฒฝ๋  ๋•Œ๋งŒ ์žฌ๊ณ„์‚ฐ๋ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์˜์กดํ•˜๋Š” ์ƒํƒœ๊ฐ€ ๊ฐ™๋‹ค๋ฉด, ์ด์ „์— ๊ณ„์‚ฐํ–ˆ๋˜ ๊ฐ’์„ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜ํ•˜์—ฌ ๋ถˆํ•„์š”ํ•œ ์—ฐ์‚ฐ์„ ๋ง‰์•„ ์„ฑ๋Šฅ์„ ์ตœ์ ํ™”ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“‹ Selector ์˜ˆ์ œ

// state/todoState.js
import { selector } from 'recoil';
import { todoListState } from './todoState';

export const todoStatsState = selector({
  key: 'todoStatsState',
  get: ({ get }) => {
    // get ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ๋‹ค๋ฅธ atom์ด๋‚˜ selector์˜ ๊ฐ’์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
    const todoList = get(todoListState);
    const totalNum = todoList.length;
    const totalCompletedNum = todoList.filter((item) => item.isComplete).length;
    const totalUncompletedNum = totalNum - totalCompletedNum;
    const percentCompleted = totalNum === 0 ? 0 : (totalCompletedNum / totalNum) * 100;

    return {
      totalNum,
      totalCompletedNum,
      totalUncompletedNum,
      percentCompleted,
    };
  },
});

์œ„ ์˜ˆ์ œ์—์„œ todoStatsState๋Š” todoListState์— ์˜์กดํ•ฉ๋‹ˆ๋‹ค. todoListState๊ฐ€ ๋ฐ”๋€” ๋•Œ๋งŒ ํ†ต๊ณ„ ๊ณ„์‚ฐ์„ ๋‹ค์‹œ ์ˆ˜ํ–‰ํ•˜๊ณ , ๋‹ค๋ฅธ ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋˜์–ด๋„ ์ „ํ˜€ ์˜ํ–ฅ์„ ๋ฐ›์ง€ ์•Š์Šต๋‹ˆ๋‹ค.


Recoil ์‹ค์ „ ์‚ฌ์šฉ๋ฒ•: A to Z ๐Ÿ”ง

1. ์„ค์น˜ ๋ฐ ์„ค์ •

๋จผ์ € ํ”„๋กœ์ ํŠธ์— Recoil์„ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค.

npm install recoil
# ๋˜๋Š”
yarn add recoil

๊ทธ๋‹ค์Œ, ์•ฑ์˜ ์ตœ์ƒ์œ„ ์ปดํฌ๋„ŒํŠธ(๋ณดํ†ต App.js๋‚˜ index.js)๋ฅผ RecoilRoot๋กœ ๊ฐ์‹ธ์ค๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•ด์•ผ ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ๋“ค์ด Recoil ์ƒํƒœ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

// index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { RecoilRoot } from 'recoil';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <RecoilRoot>
      <App />
    </RecoilRoot>
  </React.StrictMode>
);

2. ์ปดํฌ๋„ŒํŠธ์—์„œ ์ƒํƒœ ์‚ฌ์šฉํ•˜๊ธฐ (feat. Recoil Hooks)

Recoil์€ ๋‹ค์–‘ํ•œ Hooks๋ฅผ ์ œ๊ณตํ•˜์—ฌ ์ปดํฌ๋„ŒํŠธ์—์„œ ์ƒํƒœ๋ฅผ ์‰ฝ๊ฒŒ ์ฝ๊ณ  ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋•์Šต๋‹ˆ๋‹ค.

  • useRecoilState(atom): useState์™€ ์‚ฌ์šฉ๋ฒ•์ด ์™„์ „ํžˆ ๋™์ผํ•ฉ๋‹ˆ๋‹ค. [value, setValue] ๋ฐฐ์—ด์„ ๋ฐ˜ํ™˜ํ•˜๋ฉฐ, ์ƒํƒœ๋ฅผ ์ฝ๊ณ  ์—…๋ฐ์ดํŠธํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • useRecoilValue(atom): ์ƒํƒœ์˜ ๊ฐ’๋งŒ ํ•„์š”ํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ƒํƒœ๋ฅผ ์ˆ˜์ •ํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค๋ฉด, ์ด Hook์„ ์‚ฌ์šฉํ•ด ๋ถˆํ•„์š”ํ•œ ๋ฆฌ๋ Œ๋”๋ง์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • useSetRecoilState(atom): ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ํ•จ์ˆ˜๋งŒ ํ•„์š”ํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ์ƒํƒœ๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๋Š” ๊ธฐ๋Šฅ์— ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • useResetRecoilState(atom): ์ƒํƒœ๋ฅผ default ๊ฐ’์œผ๋กœ ์ดˆ๊ธฐํ™”ํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“‹ ์ปดํฌ๋„ŒํŠธ ํ™œ์šฉ ์˜ˆ์ œ

์•ž์„œ ๋งŒ๋“  atom๊ณผ selector๋ฅผ ํ™œ์šฉํ•œ ๊ฐ„๋‹จํ•œ ํ•  ์ผ ๊ด€๋ฆฌ ์•ฑ ์˜ˆ์ œ์ž…๋‹ˆ๋‹ค

import React from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { todoInputState, todoListState, todoStatsState } from './state';

function TodoApp() {
  const [text, setText] = useRecoilState(todoInputState);
  const [todos, setTodos] = useRecoilState(todoListState);
  const { totalNum, totalCompletedNum } = useRecoilValue(todoStatsState);

  const addTodo = () => {
    if (!text) return;
    setTodos((oldTodos) => [
      ...oldTodos,
      { id: Date.now(), text, isComplete: false },
    ]);
    setText(''); // ์ž…๋ ฅ์ฐฝ ์ดˆ๊ธฐํ™”
  };

  const toggleTodoCompletion = (id) => {
    setTodos(
      todos.map((todo) =>
        todo.id === id ? { ...todo, isComplete: !todo.isComplete } : todo
      )
    );
  };
  
  return (
    <div>
      <h1>Todo List</h1>
      <div>
        <input
          type="text"
          value={text}
          onChange={(e) => setText(e.target.value)}
          placeholder="ํ•  ์ผ์„ ์ž…๋ ฅํ•˜์„ธ์š”..."
        />
        <button onClick={addTodo}>์ถ”๊ฐ€</button>
      </div>
      
      <ul>
        {todos.map((todo) => (
          <li
            key={todo.id}
            onClick={() => toggleTodoCompletion(todo.id)}
            style={{ textDecoration: todo.isComplete ? 'line-through' : 'none' }}
          >
            {todo.text}
          </li>
        ))}
      </ul>

      <div>
        <h3>ํ†ต๊ณ„</h3>
        <p>์ด {totalNum}๊ฐœ์˜ ํ•  ์ผ ์ค‘ {totalCompletedNum}๊ฐœ ์™„๋ฃŒ!</p>
      </div>
    </div>
  );
}

export default TodoApp;

๋น„๋™๊ธฐ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ๐ŸŒธ

Recoil์˜ selector๋Š” ๋น„๋™๊ธฐ ์ž‘์—…๋„ ๋งค์šฐ ์šฐ์•„ํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. get ํ•จ์ˆ˜ ๋‚ด์—์„œ Promise๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด Recoil์ด ์•Œ์•„์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•ด ์ค๋‹ˆ๋‹ค. ์ด๋Š” ์„œ๋ฒ„ API ํ˜ธ์ถœ ๊ฒฐ๊ณผ๋ฅผ ์ƒํƒœ๋กœ ๊ด€๋ฆฌํ•  ๋•Œ ํŠนํžˆ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

๋ฆฌ์•กํŠธ์˜ <Suspense>์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด ๋กœ๋”ฉ ์ƒํƒœ๋ฅผ ์„ ์–ธ์ ์œผ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ“‹ ๋น„๋™๊ธฐ Selector ์˜ˆ์ œ

// state/userState.js
import { atom, selector } from 'recoil';

// ํ˜„์žฌ ์กฐํšŒํ•  ์‚ฌ์šฉ์ž์˜ ID๋ฅผ ์ €์žฅํ•˜๋Š” atom
export const currentUserIDState = atom({
  key: 'currentUserIDState',
  default: 1,
});

// ์‚ฌ์šฉ์ž ID๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ API๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋น„๋™๊ธฐ selector
export const currentUserInfoQuery = selector({
  key: 'currentUserInfoQuery',
  get: async ({ get }) => {
    const userID = get(currentUserIDState);
    const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userID}`);
    if (!response.ok) {
      throw new Error('Failed to fetch user.');
    }
    const userData = await response.json();
    return userData;
  },
});

๐Ÿ’ป ์ปดํฌ๋„ŒํŠธ์—์„œ ๋น„๋™๊ธฐ ๋ฐ์ดํ„ฐ ์‚ฌ์šฉํ•˜๊ธฐ

import React, { Suspense } from 'react';
import { useRecoilValue } from 'recoil';
import { currentUserInfoQuery, currentUserIDState } from './state/userState';

function UserInfo() {
  // ๋น„๋™๊ธฐ selector๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ๋กœ๋”ฉ ์ค‘์ผ ๋•Œ ์ปดํฌ๋„ŒํŠธ๋ฅผ Suspense ์‹œํ‚ต๋‹ˆ๋‹ค.
  const currentUser = useRecoilValue(currentUserInfoQuery);
  
  return (
    <div>
      <h2>User Info</h2>
      <p><strong>Name:</strong> {currentUser.name}</p>
      <p><strong>Email:</strong> {currentUser.email}</p>
    </div>
  );
}

function App() {
  return (
    <div>
      {/* Suspense๋Š” ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ์˜ ๋น„๋™๊ธฐ ์ž‘์—…์ด ๋๋‚  ๋•Œ๊นŒ์ง€ fallback UI๋ฅผ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. */}
      <Suspense fallback={<div>Loading...</div>}>
        <UserInfo />
      </Suspense>
    </div>
  );
}

๋” ๋˜‘๋˜‘ํ•˜๊ฒŒ Recoil ์‚ฌ์šฉํ•˜๊ธฐ: ๊ฟ€ํŒ & ์ฃผ์˜์‚ฌํ•ญ ๐Ÿ“

  1. ๊ณ ์œ ํ•˜๊ณ  ์ผ๊ด€๋œ Key ์‚ฌ์šฉํ•˜๊ธฐ ๋ชจ๋“  atom๊ณผ selector์˜ key๋Š” ์•ฑ ์ „์ฒด์—์„œ ๊ณ ์œ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋””๋ฒ„๊น…๊ณผ ์ƒํƒœ ๊ด€๋ฆฌ๋ฅผ ์šฉ์ดํ•˜๊ฒŒ ํ•˜๋ ค๋ฉด '๋ชจ๋“ˆ๋ช…/์ƒํƒœ์ด๋ฆ„'๊ณผ ๊ฐ™์ด ์ผ๊ด€๋œ ๋„ค์ด๋ฐ ๊ทœ์น™์„ ์ •ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. (์˜ˆ: 'todo/listState')
  2. ๋ถˆํ•„์š”ํ•œ ๋ฆฌ๋ Œ๋”๋ง ์ตœ์†Œํ™” ์ƒํƒœ๋ฅผ ์ฝ๊ธฐ๋งŒ ํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ์—์„œ๋Š” useRecoilValue๋ฅผ, ์ƒํƒœ๋ฅผ ์“ฐ๊ธฐ๋งŒ ํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ์—์„œ๋Š” useSetRecoilState๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”. useRecoilState๋Š” ๊ฐ’์ด๋‚˜ ์„ธํ„ฐ ํ•จ์ˆ˜๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค ๋ฆฌ๋ Œ๋”๋ง์„ ์œ ๋ฐœํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ๊ผญ ํ•„์š”ํ•  ๋•Œ๋งŒ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์„ฑ๋Šฅ์— ์œ ๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
  3. ์ƒํƒœ ๋™๊ธฐํ™”๋ฅผ ์œ„ํ•œ atomEffects atomEffects๋Š” atom์˜ ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ ํŠน์ • ๋ถ€์ˆ˜ ํšจ๊ณผ(side effect)๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋กœ์ปฌ ์Šคํ† ๋ฆฌ์ง€์™€ ์ƒํƒœ๋ฅผ ๋™๊ธฐํ™”ํ•˜๊ฑฐ๋‚˜, ํŠน์ • ์ƒํƒœ ๋ณ€๊ฒฝ์„ ๋กœ๊น…ํ•  ๋•Œ ๋งค์šฐ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.
    const localStorageEffect = key => ({setSelf, onSet}) => {
      const savedValue = localStorage.getItem(key)
      if (savedValue != null) {
        setSelf(JSON.parse(savedValue));
      }
    
      onSet((newValue, _, isReset) => {
        isReset
          ? localStorage.removeItem(key)
          : localStorage.setItem(key, JSON.stringify(newValue));
      });
    };
    
    export const todoListState = atom({
      key: 'todoListState',
      default: [],
      effects: [
        localStorageEffect('todo_list'),
      ]
    });
    
  4. ๊ฐœ๋ฐœ์ž ๋„๊ตฌ ํ™œ์šฉ Recoilize์™€ ๊ฐ™์€ ๋ธŒ๋ผ์šฐ์ € ํ™•์žฅ ํ”„๋กœ๊ทธ๋žจ์„ ์‚ฌ์šฉํ•˜๋ฉด Recoil ์ƒํƒœ ํŠธ๋ฆฌ, ์ƒํƒœ ๊ฐ’์˜ ๋ณ€ํ™”, ์˜์กด์„ฑ ๊ทธ๋ž˜ํ”„ ๋“ฑ์„ ์‹œ๊ฐ์ ์œผ๋กœ ํ™•์ธํ•  ์ˆ˜ ์žˆ์–ด ๋””๋ฒ„๊น…์ด ํ›จ์”ฌ ์‰ฌ์›Œ์ง‘๋‹ˆ๋‹ค. ๐Ÿ› ๏ธ

๋งˆ๋ฌด๋ฆฌํ•˜๋ฉฐ ๐ŸŽ‰

Recoil์€ ๋ฆฌ์•กํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ณต์žกํ•œ ์ƒํƒœ ๊ด€๋ฆฌ๋ฅผ ๋‹จ์ˆœํ•˜๊ณ  ์šฐ์•„ํ•˜๊ฒŒ ๋งŒ๋“ค์–ด์ฃผ๋Š” ํ˜์‹ ์ ์ธ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค. ๊ฐ„๊ฒฐํ•œ ์ฝ”๋“œ, ๋†’์€ ์œ ์—ฐ์„ฑ, ๊ทธ๋ฆฌ๊ณ  ๋ฆฌ์•กํŠธ์™€์˜ ์™„๋ฒฝํ•œ ํ†ตํ•ฉ์€ ๊ฐœ๋ฐœ์ž๋“ค์ด ๋” ์ค‘์š”ํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์— ์ง‘์ค‘ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ค๋‹ˆ๋‹ค.

์•„์ง Recoil์„ ์‚ฌ์šฉํ•ด ๋ณด์ง€ ์•Š์œผ์…จ๋‹ค๋ฉด, ๋‹ค์Œ ํ”„๋กœ์ ํŠธ์— ๊ผญ ํ•œ๋ฒˆ ์ ์šฉํ•ด ๋ณด์„ธ์š”. ๋ฆฌ์•กํŠธ ์ƒํƒœ ๊ด€๋ฆฌ์˜ ์ƒˆ๋กœ์šด ํŒจ๋Ÿฌ๋‹ค์ž„์„ ๊ฒฝํ—˜ํ•˜๊ฒŒ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค!