Post

recoil ์ด์šฉํ•˜๊ธฐ

๐Ÿ“Œ์‹œ์ž‘ํ•˜๋ฉฐ

Flux ํŒจํ„ด์„ ์‚ฌ์šฉํ•˜๋Š” React๋Š” ์ „์ฒด์ ์œผ๋กœ ์ƒํƒœ๋ฅผ ๊ณต์œ ํ•˜๊ธฐ ์œ„ํ•ด์„œ ์ „์—ญ ์ƒํƒœ ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ(ํ˜น์€ ๊ทธ๊ฒƒ์„ ๋„์™€์ฃผ๋Š” ๋ฌด์–ธ๊ฐ€โ€ฆ)๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด ์ฒ˜์Œ React ํ”„๋กœ์ ํŠธ์—์„œ๋Š” Redux๋ฅผ ์‚ฌ์šฉํ–ˆ๊ณ , ๊ทธ ๋‹ค์Œ ํ”„๋กœ์ ํŠธ๋Š” ๋น„๊ต์  ๊ฐ„๋‹จํ•œ ์ƒํƒœ ๊ด€๋ฆฌ๊ฐ€ ํ•„์š”ํ•ด Context API๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค.

์‚ฌ์‹ค Context API๋ฅผ ์‚ฌ์šฉํ•œ ํ”„๋กœ์ ํŠธ๋„ ์ดˆ๋ฐ˜์—” Redux๋ฅผ ์‚ฌ์šฉํ• ๊นŒ? ์‹ถ๋‹ค๊ฐ€๋„ ์•„๋ฌด๋ž˜๋„ Redux๋ณด๋‹จ Context API๊ฐ€ ์ž‘์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ์‰ฝ๊ณ , ๊ฐ„๋‹จํ•˜๋‹ค๋ณด๋‹ˆ Context API๋ฅผ ์‚ฌ์šฉํ–ˆ์—ˆ๋‹ค.

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

โœ…Recoil

๋ฆฌ์ฝ”์ผ ํƒ„์ƒ๊ณผ ๊ด€๋ จํ•˜์—ฌ, ๊ณต์‹๋ฌธ์„œ์˜ ์„ค๋ช…์„ ์ฝ์–ด๋ณด์ž. (์˜์—ญ ์žˆ์Œ!)

(์ƒํƒœ ๊ด€๋ฆฌ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ๋•Œ) ํ˜ธํ™˜์„ฑ๊ณผ ๋‹จ์ˆœ์„ฑ์„ ์œ„ํ•ด ์™ธ๋ถ€ ์ „์—ญ ์ƒํƒœ๋ณด๋‹ค๋Š” React์˜ ๋‚ด์žฅ๋œ ์ƒํƒœ ๊ด€๋ฆฌ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€์žฅ ์ข‹์ง€๋งŒ React์—๋Š” ๋ช‡ ๊ฐ€์ง€ ํ•œ๊ณ„๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ์ปดํฌ๋„ŒํŠธ ์ƒํƒœ๋Š” ๊ณตํ†ต ์กฐ์ƒ์œผ๋กœ ์˜ฌ๋ ค ์ค€ ๋‹ค์Œ, ๊ณต์œ ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ์—ฌ๊ธฐ์—๋Š” ๊ทธ ์ƒํƒœ๋ฅผ ๋‹ค์‹œ ๋ Œ๋”๋งํ•ด์•ผ ํ•˜๋Š” ํฐ ํŠธ๋ฆฌ๋ฅผ ํฌํ•จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ์ปจํ…์ŠคํŠธ๋Š” ๋ฌดํ•œํ•œ ๊ฐ’์˜ ์ง‘ํ•ฉ์ด ์•„๋‹ˆ๋ผ, ์˜ค์ง ๋‹จ์ผ ๊ฐ’๋งŒ์„ ์ €์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ด ๋‘ ๊ฐ€์ง€ ๋ฌธ์ œ์ ์€, ์ƒํƒœ๊ฐ€ ์กด์žฌํ•ด์•ผ ํ•˜๋Š” ํŠธ๋ฆฌ์˜ ๋งจ ์œ— ๋ถ€๋ถ„(์ƒํƒœ๊ฐ€ ์žˆ์–ด์•ผ ํ•˜๋Š” ๊ณณ)์„ ์ฝ”๋“œ๋กœ ๋ถ„ํ• ํ•˜๊ธฐ ์–ด๋ ต๊ฒŒ ๋งŒ๋“ค๊ณ , ํŠธ๋ฆฌ์˜ ๋ง๋‹จ(์ƒํƒœ๊ฐ€ ์‚ฌ์šฉ๋˜๋Š” ๊ณณ)์„ ์ฝ”๋“œ๋กœ ๋ถ„ํ• ํ•˜๊ธฐ ์–ด๋ ต๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

(๋”ฐ๋ผ์„œ) ์šฐ๋ฆฌ๋Š”, API ๋ฐ ์˜๋ฏธ์™€ ๋™์ž‘์„ ๊ฐ€๋Šฅํ•œ ํ•œ React ์Šค๋Ÿฝ๊ฒŒ ์œ ์ง€ํ•˜๋ฉด์„œ ์ด๋ฅผ ๊ฐœ์„ ํ•˜๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค.

์ฆ‰ ์‰ฝ๊ฒŒ ๋งํ•ด, react ๋‚ด๋ถ€์˜ Conexxt api์˜ ๋‹จ์ ์„ ๊ฐœ์„ ํ•˜๊ณ , โ€˜reactโ€™์Šค๋Ÿฝ๊ฒŒ ์ „์—ญ์ ์œผ๋กœ ์ƒํƒœ๊ด€๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ฃผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ผ๋Š” ๊ฒƒ!๐Ÿ˜Ž

โœ…๊ธฐ๋ณธ ๊ฐœ๋…

Recoil์€ atoms(๊ณต์œ  ์ƒํƒœ)์—์„œ selectors(์ˆœ์ˆ˜ ํ•จ์ˆ˜)๋ฅผ ํ†ตํ•ด ํ๋ฅด๋Š” ๋ฐ์ดํ„ฐ ํ๋ฆ„ ๊ทธ๋ž˜ํ”„๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋ฉฐ, ์ด๋ฅผ React ์ปดํฌ๋„ŒํŠธ๋กœ ๋‚ด๋ ค๋ณด๋‚ธ๋‹ค. atoms๋Š” ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๊ตฌ๋…ํ•  ์ˆ˜ ์žˆ๋Š” ์ƒํƒœ์˜ ๋‹จ์œ„์ด๋ฉฐ, selectors๋Š” ์ด ์ƒํƒœ๋ฅผ ๋™๊ธฐ์  ๋˜๋Š” ๋น„๋™๊ธฐ์ ์œผ๋กœ ๋ณ€ํ™˜ํ•œ๋‹ค.

๊ทธ๋Ÿผ ์˜ˆ์ œ๋ฅผ ๋ณด๋ฉด์„œ ์ข€๋” ์ดํ•ดํ•ด๋ณด์ž!

โœ…์„ค์น˜

์„ค์น˜ํ•ด์ฃผ๊ณ , ์ตœ์ƒ์œ„ ์š”์†Œ์— RecoilRoot๋ฅผ ๊ฐ์‹ธ์ฃผ์ž.

1
yarn add recoil
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React from "react"
import {
  RecoilRoot,
  atom,
  selector,
  useRecoilState,
  useRecoilValue,
} from "recoil"

function App() {
  return (
    <RecoilRoot ๐Ÿ‘ˆ>
      <CharacterCounter />
    </RecoilRoot>
  )
}

โžก๏ธeslint ์„ค์ •

ํ”„๋กœ์ ํŠธ์—์„œ eslint-plugin-react-hook์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ์—” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

additionalHooks ๋ชฉ๋ก์— โ€˜useRecoilCallbackโ€™์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์€๋ฐ, useRecoilCallback()์— ์ „๋‹ฌ๋œ ์ข…์†์„ฑ์ด ์ž˜๋ชป ์ง€์ •๋˜๋ฉด, ESLint๊ฐ€ ๊ฒฝ๊ณ ํ•ด์ฃผ๋ฉด์„œ, ์ˆ˜์ •์‚ฌํ•ญ์„ ์ œ์•ˆํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. (additionalHooks์˜ ํ˜•์‹์€ ์ •๊ทœ์‹ ๋ฌธ์ž์—ด์ด๋‹ค.)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// modified .eslint config
{
  "plugins": [
    "react-hooks"
  ],
  "rules": {
    "react-hooks/rules-of-hooks": "error",
    "react-hooks/exhaustive-deps": [
      "warn", {
        "additionalHooks": "(useRecoilCallback|useRecoilTransaction_UNSTABLE)"
      }
    ]
  }
}

โœ…atom

โžก๏ธatom ์„ ์–ธํ•˜๊ธฐ

atom์€ ์ƒํƒœ์˜ ๋‹จ์œ„๋‹ค. ์—…๋ฐ์ดํŠธ ์™€ ๊ตฌ๋…์ด ๊ฐ€๋Šฅํ•˜๋ฏ€๋กœ atom์ด ์—…๋ฐ์ดํŠธ๋˜๋ฉด ๊ตฌ๋…๋œ ๊ฐ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ƒˆ ๊ฐ’์œผ๋กœ ๋‹ค์‹œ ๋ Œ๋”๋ง๋œ๋‹ค!

๋จผ์ € atom์—๋Š” key์™€ default ๊ฐ’์ด ์กด์žฌํ•œ๋‹ค. ๋จผ์ € key๋Š” ๊ณ ์œ ํ•œ ์†์„ฑ์œผ๋กœ, ๋””๋ฒ„๊น…, ์ง€์†์„ฑ, ๊ทธ๋ฆฌ๊ณ  ๋ชจ๋“  ์›์ž์˜ ๋งต์„ ๋ณผ ์ˆ˜ ์žˆ๋Š” ์ผ๋ถ€ ๊ณ ๊ธ‰ API์— ์‚ฌ์šฉ๋œ๋‹ค.

default๋Š” React ์ปดํฌ๋„ŒํŠธ์˜ state์™€ ๋น„์Šทํ•œ ๊ธฐ๋ณธ๊ฐ’์ด๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋œ๋‹ค.

1
2
3
4
const fontSizeState = atom({
  key: "fontSizeState",
  default: 14,
})

โžก๏ธuseRecoilState()

์ปดํฌ๋„ŒํŠธ์—์„œ ์œ„์—์„œ ์„ค์ •ํ•œ atom์„ ์ฝ๊ฑฐ๋‚˜ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ useRecoilState๋ผ๋Š” ํ›…์„ ์‚ฌ์šฉํ•œ๋‹ค. React์˜ useState์™€ ๋น„์Šทํ•ด์„œ ์ดํ•ดํ•˜๊ธฐ ์‰ฝ๋‹ค!

1
2
3
4
5
6
7
8
9
10
11
function FontButton() {
  const [fontSize, setFontSize] = useRecoilState(fontSizeState) ๐Ÿ‘ˆ
  return (
    <button
      onClick={() => setFontSize((size) => size + 1)}
      style=
    >
      Click to Enlarge
    </button>
  )
}

์ฒ˜์Œ recoil์„ ์‚ฌ์šฉํ•ด๋ณด๋ฉด์„œ, ํŠน์ • ์ปดํฌ๋„ŒํŠธ์—์„œ setFontSize๋ฅผ ๋ฐ”๋กœ ์‚ฌ์šฉํ•ด์„œ ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธ ํ•ด๋„ ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ์˜ ๋™์ผํ•œ atom๊ฐ’๋„ ๋ณ€ํ• ๊นŒ?๋ž€ ๋ฌผ์Œ์ด ์ƒ๊ฒผ๋‹ค.

(Redux์—์„œ๋Š” ๋”ฐ๋กœ ์•ก์…˜์„ ์ •์˜ํ•˜๊ณ , ์•ก์…˜์„ ๋””์ŠคํŒจ์น˜ ํ•˜๊ณ  ์—…๋ฐ์ดํŠธ ํ•˜๊ธฐ ๋•Œ๋ฌธ์—โ€ฆ)

๊ฒฐ๋ก ๋ถ€ํ„ฐ ๋งํ•˜์ž๋ฉด ๊ฐ€๋Šฅํ•˜๋‹ค! ๋„ˆ๋ฌด ๊ฐ„ํŽธํ•ด์„œ ๋‹นํ™ฉํ–ˆ์ง€๋งŒ, ์ •๋ง Recoil์€ ์ด๋ ‡๊ฒŒ ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธ ํ•  ์ˆ˜ ์žˆ๋‹ค. ์„ค๋ช… ๊ทธ๋Œ€๋กœ โ€˜reactโ€™์Šค๋Ÿฝ๊ฒŒ ๋ง์ด๋‹ค!

โœ…Selectors

selector์€ atom์ด๋‚˜ ๋‹ค๋ฅธ selector์€ ์ž…๋ ฅ์œผ๋กœ ๋ฐ›๋Š” ์ˆœ์ˆ˜ ํ•จ์ˆ˜๋กœ, ์ƒ์œ„ atom์ด๋‚˜ selector๊ฐ€ ์—…๋ฐ์ดํŠธ๋˜๋ฉด selectorํ•จ์ˆ˜๋Š” ๋‹ค์‹œ ํ‰๊ฐ€๋œ๋‹ค. ์ปดํฌ๋„ŒํŠธ๋Š” atom๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ selector๋ฅผ ๊ตฌ๋…ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, selector์€ ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค ๋‹ค์‹œ ๋ Œ๋”๋ง๋œ๋‹ค.

selector์€ ์ƒํƒœ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•œ ํŒŒ์ƒ ๋ฐ์ดํ„ฐ๋ฅผ ๊ณ„์‚ฐํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋œ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด atom์—” ์ตœ์†Œํ•œ์˜ ์ƒํƒœ๋งŒ ์ €์žฅ๋˜๊ณ , ๋‚˜๋จธ์ง€๋Š” ๊ทธ ์ตœ์†Œํ•œ์˜ ์ƒํƒœ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํšจ์œจ์ ์œผ๋กœ ๊ณ„์‚ฐ๋œ๋‹ค.

selector์€ get๊ณผ ์‚ฌ์šฉ๋œ๋‹ค. get ์†์„ฑ์€ ๊ณ„์‚ฐ ๋  ํ•จ์ˆ˜๋ฅผ ๋œปํ•œ๋‹ค! ์ด ํ•จ์ˆ˜๋Š” ์ „๋‹ฌ๋œ get ์ธ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด atom ๋ฐ ๋‹ค๋ฅธ selector์˜ ๊ฐ’์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ๋‹ค. ๋‹ค๋ฅธ atom์ด๋‚˜ selector์— ์ ‘๊ทผํ•ด, ํ•ด๋‹น atom์ด๋‚˜ selector๊ฐ€ ์—…๋ฐ์ดํŠธ๋˜๋ฉด ์ด ํ•จ์ˆ˜๋ฅผ ๋‹ค์‹œ ๊ณ„์‚ฐํ•˜๊ธฐ ์œ„ํ•œ ์ข…์†์„ฑ ๊ด€๊ณ„๊ฐ€ ์ƒ์„ฑ๋œ๋‹ค.

1
2
3
4
5
6
7
8
9
const fontSizeLabelState = selector({
  key: "fontSizeLabelState",
  get: ({ get }) => {
    const fontSize = get(fontSizeState)
    const unit = "px"

    return `${fontSize}${unit}`
  },
})

์ด ์˜ˆ์ œ์—์„œ๋Š” fontSizeState์˜ atom๊ฐ’์„ ๋ฐ›์•„์™€ fontSize ๋ณ€์ˆ˜์— ์ €์žฅํ•˜๊ณ , 14px ๊ณผ ๊ฐ™์ด px๊ฐ’์„ ๋‚˜ํƒ€๋‚ด๋Š” ํŠน์ • ๊ฐ’์„ ๋งŒ๋“ค์–ด return ํ•˜๊ณ  ์žˆ๋‹ค.

โžก๏ธ์‚ฌ์šฉ์˜ˆ์ œ

์‚ฌ์‹ค selector ์ž์ฒด๋Š” ๊ณต์‹๋ฌธ์„œ๋งŒ ์ฝ๊ณ  ์™„์ „ํžˆ ์ดํ•ด๋ฅผ ๋ชปํ–ˆ๋Š”๋ฐ, ์‚ฌ์šฉํ•ด๋ณด๋ฉด์„œ ๊ฐ์„ ์žก์•˜๋‹ค.

ํ˜„์žฌ ์ง„ํ–‰์ค‘์ธ ํ”„๋กœ์ ํŠธ๋Š” ๋‹ค๊ตญ์–ด ๋ชจ๋“œ๋ฅผ ์ง€์›ํ•˜๋ ค๊ณ  ํ•˜๊ณ , Header์—์„œ atom ์ƒํƒœ(ko or jp ์–ธ์–ด)๋ฅผ ๊ต์ฒดํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ทธ ๋ถ€๋ถ„์ด ํ•˜๋‹จ์˜ language ๋ถ€๋ถ„์ด๋‹ค!

์ด๋•Œ ์„ ํƒ๋œ language์— ๋”ฐ๋ผ ์–ธ์–ด๋ฐ์ดํ„ฐ๊ฐ€ ๋“ค์–ด์žˆ๋Š” json ํŒŒ์ผ์„ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š”๋ฐ, ์ด๋•Œ selector๋ฅผ ์‚ฌ์šฉํ•ด ๊ตฌํ˜„ํ–ˆ๋‹ค.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
"use client"
import { atom, selector } from "recoil"
import ko from "@/language/ko.json"
import jp from "@/language/jp.json"

export const language = atom<string>({
  key: "nowLanguage",
  default: "ko",
})

export const languageMode = selector({
  key: "languageDataSelector",
  get: ({ get }) => {
    const nowLanguageMode = get(language)
    return nowLanguageMode === "ko" ? ko : jp //์–ธ์–ด json ๋ฐ์ดํ„ฐ ์„ ํƒ
  },
})

๐Ÿ“ฉ๋งˆ๋ฌด๋ฆฌ

์ด๋ ‡๊ฒŒ ๊ฐ„๋‹จํ•˜๊ฒŒ recoil์— ๋Œ€ํ•ด ์•Œ์•„๋ดค๋‹ค! ํ™•์‹คํžˆ redux๋ณด๋‹ค ํŽธํ•˜๊ณ  โ€˜reactโ€™์Šค๋Ÿฝ๋‹ค๋Š”๊ฒŒ ์–ด๋–ค ์˜๋ฏธ์ธ์ง€ ์™€๋‹ฟ๋Š” ๊ฒƒ ๊ฐ™๋‹ค.

์ฐธ๊ณ ๋กœ recoil ๊ณต์‹๋ฌธ์„œ๋Š” ํ•œ๊ตญ์–ด๋ฅผ ์ง€์›ํ•œ๋‹ค! ์˜์–ด, ํ”„๋ž‘์Šค์–ด, ํ•œ๊ตญ์–ด, ์ค‘๊ตญ์–ด ์ˆœ์œผ๋กœ ์„ ํƒ์ด ๊ฐ€๋Šฅํ•œ๋ฐ, ํ•œ๊ตญ์–ด ๊ฐœ๋ฐœ์ž ๋ถ„๋“ค์ด ๊ธฐ์—ฌํ•˜์‹  ๋ถ€๋ถ„์ผ๊นŒ? (๋งŒ์•ฝ ๊ทธ๋ ‡๋‹ค๋ฉด ์ •๋ง ๊ฐ์‚ฌํ•˜๋‹ค)๐Ÿฅบ

๋‚˜๋„ ์ด๋Ÿฐ ๊ธฐ์—ฌ๋ฅผ ํ•ด๋ณด๊ณ  ์‹ถ๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ ๋‹ค.๐Ÿ˜Ž

๐Ÿ—‚๏ธ์ฐธ๊ณ  ์‚ฌ์ดํŠธ

This post is licensed under CC BY 4.0 by the author.