Pink Bobblehead Bunny Recoil: React ์ƒํƒœ ๊ด€๋ฆฌ์˜ ์ƒˆ๋กœ์šด ํŒจ๋Ÿฌ๋‹ค์ž„
 

Recoil: React ์ƒํƒœ ๊ด€๋ฆฌ์˜ ์ƒˆ๋กœ์šด ํŒจ๋Ÿฌ๋‹ค์ž„

React ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๊ทœ๋ชจ๊ฐ€ ์ปค์ง€๋ฉด์„œ ์ƒํƒœ ๊ด€๋ฆฌ๋Š” ๊ฐœ๋ฐœ์˜ ํ•ต์‹ฌ์ ์ธ ๊ณผ์ œ๊ฐ€ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๊ธฐ์กด์˜ Redux๋‚˜ MobX์™€ ๊ฐ™์€ ํ›Œ๋ฅญํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค์ด ์žˆ์ง€๋งŒ, ๋•Œ๋กœ๋Š” ์„ค์ •์˜ ๋ณต์žก์„ฑ์ด๋‚˜ ๋Ÿฌ๋‹ ์ปค๋ธŒ ๋•Œ๋ฌธ์— ์ž‘์€ ํ”„๋กœ์ ํŠธ์— ๋„์ž…ํ•˜๊ธฐ ๋ถ€๋‹ด์Šค๋Ÿฌ์šธ ๋•Œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐ”๋กœ ์ด๋Ÿฌํ•œ ๊ณ ๋ฏผ์„ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด Facebook์—์„œ ์ง์ ‘ ๊ฐœ๋ฐœํ•œ ์ƒํƒœ ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ Recoil์ž…๋‹ˆ๋‹ค.

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


1. Recoil์˜ ๋“ฑ์žฅ ๋ฐฐ๊ฒฝ: ์™œ ์ƒˆ๋กœ์šด ์ƒํƒœ ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ํ•„์š”ํ–ˆ์„๊นŒ?

Recoil์„ ์ดํ•ดํ•˜๊ธฐ ์ „์— ๋จผ์ € ๊ธฐ์กด ์ƒํƒœ ๊ด€๋ฆฌ ๋ฐฉ์‹์˜ ํ•œ๊ณ„๋ฅผ ์งš์–ด๋ณผ ํ•„์š”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ์ปดํฌ๋„ŒํŠธ ํŠธ๋ฆฌ๋ฅผ ํ†ตํ•œ props ์ „๋‹ฌ (Prop Drilling): ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ React์˜ ์ƒํƒœ ์ „๋‹ฌ ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ƒํƒœ๊ฐ€ ํ•„์š”ํ•œ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ตœ์ƒ์œ„ ์ปดํฌ๋„ŒํŠธ์™€ ๋ฉ€๋ฆฌ ๋–จ์–ด์ ธ ์žˆ์„ ๊ฒฝ์šฐ, ์ค‘๊ฐ„์— ์žˆ๋Š” ์ˆ˜๋งŽ์€ ์ปดํฌ๋„ŒํŠธ๋“ค์ด ์˜ค์ง ์ƒํƒœ๋ฅผ ์ „๋‹ฌํ•˜๊ธฐ ์œ„ํ•œ ๋ชฉ์ ์œผ๋กœ props๋ฅผ ๋ฐ›์•„์•ผ ํ•˜๋Š” 'Prop Drilling' ํ˜„์ƒ์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์ฝ”๋“œ์˜ ๊ฐ€๋…์„ฑ์„ ํ•ด์น˜๊ณ  ์œ ์ง€๋ณด์ˆ˜๋ฅผ ์–ด๋ ต๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
  • Context API: React์— ๋‚ด์žฅ๋œ Context API๋Š” Prop Drilling ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋“ฑ์žฅํ–ˆ์Šต๋‹ˆ๋‹ค. ์ „์—ญ์ ์ธ ์ƒํƒœ๋ฅผ ๋งŒ๋“ค์–ด ํ•„์š”ํ•œ ์ปดํฌe๋„ŒํŠธ์—์„œ ์ง์ ‘ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด ์ฃผ์ฃ . ํ•˜์ง€๋งŒ Context๋Š” ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ ํ•ด๋‹น Context๋ฅผ ๊ตฌ๋…ํ•˜๋Š” ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง ๋˜๋Š” ๋‹จ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ƒํƒœ์˜ ์ผ๋ถ€๋งŒ ์‚ฌ์šฉํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ๊นŒ์ง€ ๋ถˆํ•„์š”ํ•˜๊ฒŒ ๋ฆฌ๋ Œ๋”๋ง ๋˜์–ด ์„ฑ๋Šฅ ์ €ํ•˜์˜ ์›์ธ์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • Redux: Redux๋Š” ์˜ค๋žซ๋™์•ˆ React ์ƒํƒœ๊ณ„์˜ ํ‘œ์ค€์ฒ˜๋Ÿผ ์—ฌ๊ฒจ์ ธ ์˜จ ๊ฐ•๋ ฅํ•œ ์ƒํƒœ ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ Action, Reducer, Store, Middleware ๋“ฑ ๋ฐฐ์›Œ์•ผ ํ•  ๊ฐœ๋…์ด ๋งŽ๊ณ , ๊ฐ„๋‹จํ•œ ์ƒํƒœ ํ•˜๋‚˜๋ฅผ ์ถ”๊ฐ€ํ•˜๋”๋ผ๋„ ์ž‘์„ฑํ•ด์•ผ ํ•˜๋Š” ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ ์ฝ”๋“œ๊ฐ€ ์ƒ๋‹นํ•ฉ๋‹ˆ๋‹ค. ์ด๋กœ ์ธํ•ด ๊ฐœ๋ฐœ ์†๋„๊ฐ€ ์ €ํ•˜๋˜๊ณ  ์ž…๋ฌธ์ž์—๊ฒŒ ๋†’์€ ์žฅ๋ฒฝ์œผ๋กœ ๋А๊ปด์ง€๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค.

Recoil์€ ๋ฐ”๋กœ ์ด๋Ÿฌํ•œ ๋ฌธ์ œ๋“ค์„ ํ•ด๊ฒฐํ•˜๊ณ , ๋” 'React'์Šค๋Ÿฌ์šด ๋ฐฉ์‹์œผ๋กœ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ํƒ„์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. React์˜ useState ํ›…์ฒ˜๋Ÿผ ๊ฐ„๋‹จํ•˜๊ฒŒ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ์œผ๋ฉด์„œ๋„, ๋Œ€๊ทœ๋ชจ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋„ ์œ ์—ฐํ•˜๊ณ  ๊ฐ•๋ ฅํ•˜๊ฒŒ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๋„๊ตฌ๋ฅผ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์ด Recoil์˜ ํ•ต์‹ฌ ์ฒ ํ•™์ž…๋‹ˆ๋‹ค.


2. Recoil์˜ ํ•ต์‹ฌ ๊ฐœ๋…: Atom๊ณผ Selector

Recoil์˜ API๋Š” ๋งค์šฐ ์ง๊ด€์ ์ด๊ณ  ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค. ๊ทธ ์ค‘์‹ฌ์—๋Š” Atom๊ณผ Selector๋ผ๋Š” ๋‘ ๊ฐ€์ง€ ํ•ต์‹ฌ ๊ฐœ๋…์ด ์žˆ์Šต๋‹ˆ๋‹ค.

Atom: ์ƒํƒœ์˜ ์ตœ์†Œ ๋‹จ์œ„

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

Atom์„ ์ƒ์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๋งค์šฐ ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค.

// store/atoms.ts
import { atom } from 'recoil';

export const todoListState = atom<string[]>({
  key: 'todoListState', // ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์ฒด์—์„œ ๊ณ ์œ ํ•œ ํ‚ค
  default: [],         // ๊ธฐ๋ณธ๊ฐ’
});
  • key: ์ƒ์„ฑ๋œ Atom์„ ์‹๋ณ„ํ•˜๊ธฐ ์œ„ํ•œ ๊ณ ์œ ํ•œ ๋ฌธ์ž์—ด์ž…๋‹ˆ๋‹ค. ์ด ํ‚ค๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์ฒด์—์„œ ์œ ์ผํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • default: ํ•ด๋‹น Atom์˜ ์ดˆ๊ธฐ๊ฐ’์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

์ด์ œ ์ปดํฌ๋„ŒํŠธ์—์„œ ์ด Atom์„ ์‚ฌ์šฉํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. Recoil์€ React ํ›…๊ณผ ์œ ์‚ฌํ•œ API๋ฅผ ์ œ๊ณตํ•˜์—ฌ ์‰ฝ๊ฒŒ ์ƒํƒœ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

// components/TodoList.jsx
import { useRecoilState } from 'recoil';
import { todoListState } from '../store/atoms';

function TodoList() {
  // useState์™€ ๋งค์šฐ ์œ ์‚ฌํ•œ ๋ฐฉ์‹
  const [todos, setTodos] = useRecoilState(todoListState);
  
  // ... (์•„์ดํ…œ ์ถ”๊ฐ€, ์‚ญ์ œ ๋กœ์ง)
}

useRecoilState ํ›…์€ useState์™€ ๋˜‘๊ฐ™์ด [๊ฐ’, ์—…๋ฐ์ดํŠธ ํ•จ์ˆ˜] ํ˜•ํƒœ์˜ ๋ฐฐ์—ด์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์ด ์™ธ์—๋„ ๊ฐ’๋งŒ ํ•„์š”ํ•  ๋•Œ๋Š” useRecoilValue, ์—…๋ฐ์ดํŠธ ํ•จ์ˆ˜๋งŒ ํ•„์š”ํ•  ๋•Œ๋Š” useSetRecoilState๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ถˆํ•„์š”ํ•œ ๋ฆฌ๋ Œ๋”๋ง์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Selector: ํŒŒ์ƒ๋œ ์ƒํƒœ (Derived State)

Selector๋Š” Recoil์˜ ์ง„์ •ํ•œ ๊ฐ•๋ ฅํ•จ์ด ๋“œ๋Ÿฌ๋‚˜๋Š” ๋ถ€๋ถ„์ž…๋‹ˆ๋‹ค. Selector๋Š” ๊ธฐ์กด Atom์ด๋‚˜ ๋‹ค๋ฅธ Selector์˜ ์ƒํƒœ๋ฅผ ์ž…๋ ฅ๋ฐ›์•„ **์ƒˆ๋กœ์šด ํŒŒ์ƒ๋œ ๋ฐ์ดํ„ฐ(Derived State)**๋ฅผ ๋งŒ๋“ค์–ด๋‚ด๋Š” ์ˆœ์ˆ˜ ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.

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

// store/selectors.ts
import { selector } from 'recoil';
import { todoListState } from './atoms';

export const uncompletedTodoCountState = selector<number>({
  key: 'uncompletedTodoCountState',
  get: ({ get }) => {
    // get ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ๋‹ค๋ฅธ atom์ด๋‚˜ selector์˜ ๊ฐ’์„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋‹ค.
    const todoList = get(todoListState);
    return todoList.filter(item => !item.isCompleted).length;
  },
});
  • key: Selector๋ฅผ ์‹๋ณ„ํ•˜๊ธฐ ์œ„ํ•œ ๊ณ ์œ ํ•œ ํ‚ค์ž…๋‹ˆ๋‹ค.
  • get: ํŒŒ์ƒ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ณ„์‚ฐํ•˜๋Š” ์ˆœ์ˆ˜ ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. get ํ”„๋กœํผํ‹ฐ์˜ ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋Š” get์ด๋ผ๋Š” ์ธ์ž๋ฅผ ๋ฐ›๋Š”๋ฐ, ์ด๋ฅผ ํ†ตํ•ด ๋‹ค๋ฅธ Atom์ด๋‚˜ Selector์˜ ์ตœ์‹  ๊ฐ’์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Recoil์€ ์˜์กด์„ฑ ๊ทธ๋ž˜ํ”„๋ฅผ ์ž๋™์œผ๋กœ ์ถ”์ ํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰, uncompletedTodoCountState๋Š” todoListState์— ์˜์กดํ•˜๊ณ  ์žˆ์œผ๋ฏ€๋กœ, todoListState๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ๋งŒ get ํ•จ์ˆ˜๊ฐ€ ์žฌ์‹คํ–‰๋˜์–ด ๊ฐ’์„ ๋‹ค์‹œ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ๊ด€๋ จ ์—†๋Š” ๋‹ค๋ฅธ Atom์ด ๋ณ€๊ฒฝ๋˜๋”๋ผ๋„ ์ด Selector๋Š” ์žฌ๊ณ„์‚ฐ๋˜์ง€ ์•Š์•„ ๋›ฐ์–ด๋‚œ ์„ฑ๋Šฅ์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.

์ปดํฌ๋„ŒํŠธ์—์„œ๋Š” Selector๋ฅผ ์ฝ๊ธฐ ์ „์šฉ ๊ฐ’์ฒ˜๋Ÿผ useRecoilValue๋กœ ๊ตฌ๋…ํ•˜์—ฌ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

// components/TodoStats.jsx
import { useRecoilValue } from 'recoil';
import { uncompletedTodoCountState } from '../store/selectors';

function TodoStats() {
  const count = useRecoilValue(uncompletedTodoCountState);
  
  return <div>๋‚จ์€ ํ•  ์ผ: {count}๊ฐœ</div>;
}

์ด์ฒ˜๋Ÿผ Selector๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ƒํƒœ ๋กœ์ง์„ ์ปดํฌ๋„ŒํŠธ๋กœ๋ถ€ํ„ฐ ๋ถ„๋ฆฌํ•˜์—ฌ ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๊ณ  ๊ด€๋ฆฌํ•˜๊ธฐ ์‰ฌ์šด ํ˜•ํƒœ๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


3.Recoil ์ œ๋Œ€๋กœ ํ™œ์šฉํ•˜๊ธฐ

Atom๊ณผ Selector์˜ ๊ธฐ๋ณธ ๊ฐœ๋…์„ ์ดํ•ดํ–ˆ๋‹ค๋ฉด, ์ด์ œ Recoil์„ ๋”์šฑ ๊ฐ•๋ ฅํ•˜๊ฒŒ ๋งŒ๋“ค์–ด์ฃผ๋Š” ์‹ฌํ™” ๊ธฐ๋Šฅ๋“ค์„ ์•Œ์•„๋ณผ ์ฐจ๋ก€์ž…๋‹ˆ๋‹ค.

๋น„๋™๊ธฐ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ (Asynchronous Selectors)

ํ˜„๋Œ€ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ๋น„๋™๊ธฐ ๋ฐ์ดํ„ฐ ์š”์ฒญ์€ ํ•„์ˆ˜์ ์ž…๋‹ˆ๋‹ค. Recoil์˜ Selector๋Š” async/await๋ฅผ ์ง€์›ํ•˜์—ฌ ๋น„๋™๊ธฐ ๋กœ์ง์„ ๋งค์šฐ ์šฐ์•„ํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

get ํ•จ์ˆ˜๋ฅผ async๋กœ ๋งŒ๋“ค๋ฉด, Promise๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋น„๋™๊ธฐ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Recoil์€ ๋‚ด๋ถ€์ ์œผ๋กœ React Suspense์™€ ์—ฐ๋™ํ•˜์—ฌ ๋ฐ์ดํ„ฐ๊ฐ€ ๋กœ๋”ฉ ์ค‘์ผ ๋•Œ fallback UI๋ฅผ ๋ณด์—ฌ์ฃผ๊ณ , ๋กœ๋”ฉ์ด ์™„๋ฃŒ๋˜๋ฉด ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋ง ํ•ฉ๋‹ˆ๋‹ค.

// store/selectors.ts
import { selector } from 'recoil';
import axios from 'axios';

export const userState = selector({
  key: 'userState',
  get: async ({ get }) => {
    // ๋‹ค๋ฅธ atom์˜ ๊ฐ’์— ๋”ฐ๋ผ API ์š”์ฒญ์„ ๋™์ ์œผ๋กœ ๋ณ€๊ฒฝํ•  ์ˆ˜๋„ ์žˆ๋‹ค.
    // const userId = get(currentUserIDState); 
    
    try {
      const response = await axios.get(`https://api.example.com/user/1`);
      return response.data;
    } catch (error) {
      throw error; // ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ Error Boundary๋กœ ์ „ํŒŒ
    }
  },
});

์ปดํฌ๋„ŒํŠธ์—์„œ๋Š” ์ด ๋น„๋™๊ธฐ Selector๋ฅผ ์ผ๋ฐ˜ Selector์™€ ๋˜‘๊ฐ™์ด ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ์ƒํƒœ ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•ด ๋ณ„๋„์˜ isLoading, error ์ƒํƒœ๋ฅผ ๋งŒ๋“ค ํ•„์š” ์—†์ด, React Suspense์™€ Error Boundary๋ฅผ ์กฐํ•ฉํ•˜์—ฌ ์„ ์–ธ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

// components/UserInfo.jsx
import { Suspense } from 'react';
import { useRecoilValue } from 'recoil';
import { userState } from '../store/selectors';

function UserInfoComponent() {
  const user = useRecoilValue(userState);
  return <div>์‚ฌ์šฉ์ž ์ด๋ฆ„: {user.name}</div>;
}

function UserInfo() {
  return (
    <Suspense fallback={<div>๋กœ๋”ฉ ์ค‘...</div>}>
      <UserInfoComponent />
    </Suspense>
  );
}

์ƒํƒœ์™€ ์•ก์…˜์„ ๋ชจ๋‘ ํ•„์š”๋กœ ํ•  ๋•Œ: useRecoilCallback

๋•Œ๋กœ๋Š” ํŠน์ • ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ Recoil ์ƒํƒœ๋ฅผ ์ฝ๊ฑฐ๋‚˜ ์—…๋ฐ์ดํŠธํ•ด์•ผ ํ•˜์ง€๋งŒ, ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ฆฌ๋ Œ๋”๋ง ์‹œํ‚ค๊ณ  ์‹ถ์ง€๋Š” ์•Š์€ ๊ฒฝ์šฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ํ˜„์žฌ ์ƒํƒœ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ API ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ๊ฒฝ์šฐ๊ฐ€ ๊ทธ๋ ‡์Šต๋‹ˆ๋‹ค.

์ด๋•Œ useRecoilCallback ํ›…์„ ์‚ฌ์šฉํ•˜๋ฉด, ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํŠน์ • Recoil ์ƒํƒœ๋ฅผ ๊ตฌ๋…(๋ฆฌ๋ Œ๋”๋ง) ํ•˜์ง€ ์•Š์œผ๋ฉด์„œ๋„ ํ•„์š”ํ•  ๋•Œ ์ƒํƒœ์˜ ์Šค๋ƒ…์ˆ์„ ์ฝ๊ฑฐ๋‚˜ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

import { useRecoilCallback } from 'recoil';
import { todoListState } from '../store/atoms';

function TodoListLogger() {
  const logTodos = useRecoilCallback(({ snapshot }) => async () => {
    // loadable์„ ํ†ตํ•ด ๋น„๋™๊ธฐ selector์˜ ์ƒํƒœ(loading, hasValue, hasError)๋„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.
    const todoList = await snapshot.getPromise(todoListState);
    console.log('ํ˜„์žฌ ํ•  ์ผ ๋ชฉ๋ก:', todoList);
  });

  return <button onClick={logTodos}>ํ˜„์žฌ ๋ชฉ๋ก ์ฝ˜์†”์— ๊ธฐ๋ก</button>;
}

snapshot ๊ฐ์ฒด๋Š” ์ฝœ๋ฐฑ์ด ์‹คํ–‰๋˜๋Š” ์‹œ์ ์˜ Recoil ์ƒํƒœ๋ฅผ ๋‹ด๊ณ  ์žˆ์–ด, ์•ˆ์ „ํ•˜๊ฒŒ ์ƒํƒœ๋ฅผ ์ฝ์–ด์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ƒํƒœ ์ง€์†์„ฑ ๋ฐ ๋™๊ธฐํ™”: Atom Effects

์‚ฌ์šฉ์ž๊ฐ€ ์„ค์ •ํ•œ ํ…Œ๋งˆ๋‚˜ ๋กœ๊ทธ์ธ ์ •๋ณด ๋“ฑ์€ ํŽ˜์ด์ง€๋ฅผ ์ƒˆ ๋กœ๊ณ ์นจํ•ด๋„ ์œ ์ง€๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. Recoil์€ Atom Effects๋ผ๋Š” ๊ฐ•๋ ฅํ•œ ๋ถ€์ˆ˜ ํšจ๊ณผ API๋ฅผ ์ œ๊ณตํ•˜์—ฌ Atom์˜ ์ƒํƒœ๋ฅผ ์™ธ๋ถ€ ์Šคํ† ๋ฆฌ์ง€(localStorage, sessionStorage ๋“ฑ)์™€ ๋™๊ธฐํ™”ํ•˜๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ๋ถ€์ˆ˜์ ์ธ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด ์ค๋‹ˆ๋‹ค.

atom์„ ์ •์˜ํ•  ๋•Œ effects ๋ฐฐ์—ด์„ ์ถ”๊ฐ€ํ•˜์—ฌ ์ด ๊ธฐ๋Šฅ์„ ํ™œ์„ฑํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

import { atom } from 'recoil';

// localStorage์™€ ๋™๊ธฐํ™”ํ•˜๋Š” ๊ฐ„๋‹จํ•œ 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 themeState = atom<'light' | 'dark'>({
  key: 'themeState',
  default: 'light',
  effects: [
    localStorageEffect('theme'),
  ],
});
  • setSelf: Atom์ด ์ฒ˜์Œ ์ดˆ๊ธฐํ™”๋  ๋•Œ ํ˜ธ์ถœ๋˜๋ฉฐ, ์Šคํ† ๋ฆฌ์ง€์˜ ๊ฐ’์œผ๋กœ Atom ์ƒํƒœ๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
  • onSet: Atom์˜ ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค ํ˜ธ์ถœ๋˜๋ฉฐ, ๋ณ€๊ฒฝ๋œ ๊ฐ’์„ ์Šคํ† ๋ฆฌ์ง€์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.

์ด์ฒ˜๋Ÿผ Atom Effects๋ฅผ ํ™œ์šฉํ•˜๋ฉด ์ƒํƒœ ๊ด€๋ฆฌ ๋กœ์ง๊ณผ ๋™๊ธฐํ™” ๋กœ์ง์„ Atom ์ •์˜ ๋‚ด์— ์บก์Аํ™”ํ•˜์—ฌ ์ฝ”๋“œ์˜ ์‘์ง‘๋„๋ฅผ ๋†’์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (์„œ๋“œํŒŒํ‹ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ธ recoil-persist๋‚˜ recoil-sync๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋” ๊ฐ„ํŽธํ•˜๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.)


4. ๊ฒฐ๋ก : Recoil, ๋‹น์‹ ์˜ ๋‹ค์Œ ํ”„๋กœ์ ํŠธ๋ฅผ ์œ„ํ•œ ์„ ํƒ

Recoil์€ React์˜ ์ฒ ํ•™์„ ๊ณ„์Šนํ•˜์—ฌ ๋ฐฐ์šฐ๊ธฐ ์‰ฝ๊ณ  ๊ฐ„๊ฒฐํ•˜๋ฉด์„œ๋„, ๋Œ€๊ทœ๋ชจ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ณต์žกํ•œ ์ƒํƒœ๊นŒ์ง€ ํšจ๊ณผ์ ์œผ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ•๋ ฅํ•œ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค.

  • ๋‚ฎ์€ ๋Ÿฌ๋‹ ์ปค๋ธŒ: useState์™€ ์œ ์‚ฌํ•œ API๋กœ React ๊ฐœ๋ฐœ์ž๋ผ๋ฉด ๋ˆ„๊ตฌ๋‚˜ ์‰ฝ๊ฒŒ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ตœ์†Œํ•œ์˜ ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ: Redux์™€ ๋น„๊ตํ•˜์—ฌ ํ›จ์”ฌ ์ ์€ ์–‘์˜ ์ฝ”๋“œ๋กœ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์„ฑ๋Šฅ ์ตœ์ ํ™”: ์ƒํƒœ ๋ณ€๊ฒฝ ์‹œ ํ•ด๋‹น ์ƒํƒœ๋ฅผ ๊ตฌ๋…ํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ๋งŒ ์ •ํ™•ํžˆ ๋ฆฌ๋ Œ๋”๋ง ํ•˜์—ฌ ๋ถˆํ•„์š”ํ•œ ๋ Œ๋”๋ง์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.
  • ๊ฐ•๋ ฅํ•œ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ: React Suspense์™€ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ํ†ตํ•ฉ๋˜์–ด ๋น„๋™๊ธฐ ๋กœ์ง์„ ์„ ์–ธ์ ์ด๊ณ  ๊น”๋”ํ•˜๊ฒŒ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ฌผ๋ก  Recoil์ด ๋ชจ๋“  ํ”„๋กœ์ ํŠธ์˜ ๋งŒ๋ณ‘ํ†ต์น˜์•ฝ์€ ์•„๋‹ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋งค์šฐ ๋ณต์žกํ•œ ๋ฏธ๋“ค์›จ์–ด๊ฐ€ ํ•„์š”ํ•˜๊ฑฐ๋‚˜, ์ •ํ˜•ํ™”๋œ ์•ก์…˜-๋””์ŠคํŒจ์น˜ ํŒจํ„ด์ด ํŒ€์— ๋” ์ ํ•ฉํ•œ ๊ฒฝ์šฐ Redux๊ฐ€ ์—ฌ์ „ํžˆ ์ข‹์€ ์„ ํƒ์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ๋Œ€๋ถ€๋ถ„์˜ React ํ”„๋กœ์ ํŠธ์—์„œ Recoil์€ ๊ฐœ๋ฐœ ์ƒ์‚ฐ์„ฑ๊ณผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„ฑ๋Šฅ์ด๋ผ๋Š” ๋‘ ๋งˆ๋ฆฌ ํ† ๋ผ๋ฅผ ๋ชจ๋‘ ์žก์„ ์ˆ˜ ์žˆ๋Š” ํ›Œ๋ฅญํ•œ ๋Œ€์•ˆ์ด ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋‹น์‹ ์˜ ๋‹ค์Œ ํ”„๋กœ์ ํŠธ์— Recoil์„ ๋„์ž…ํ•˜์—ฌ, ์ง๊ด€์ ์ด๊ณ  ์ฆ๊ฑฐ์šด ์ƒํƒœ ๊ด€๋ฆฌ์˜ ์„ธ๊ณ„๋ฅผ ๊ฒฝํ—˜ํ•ด ๋ณด์‹œ๊ธธ ๋ฐ”๋ž๋‹ˆ๋‹ค.