ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Recoil] ์ „์—ญ ์ƒํƒœ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ - Recoil ์ •๋ณต๊ธฐ
    Front-End(Web)/React.js 2021. 12. 30. 01:04
    ๋ฐ˜์‘ํ˜•

    ๐Ÿง ์„œ๋ก 

    ๊ต‰์žฅํžˆ ์˜ค๋žœ๋งŒ์— ์“ฐ๋Š” ์„œ๋ก ์ธ ๊ฒƒ ๊ฐ™๋‹ค!! ๊ทธ๋งŒํผ ์ด ๊ธ€์˜ ๊ธธ์ด๊ฐ€ ์งง์ง„ ์•Š์„๊ฑฐ๋ผ๋Š” ๋งˆ์Œ์˜ ์ค€๋น„ ์ฐจ์›์ผ์ง€๋„?

     

    ์˜ค๋žœ๋งŒ์— React๋ฅผ ๋ณต๊ธฐํ•˜๊ณ  Typescript๋ฅผ ์ˆ™๋‹ฌํ•  ๊ฒธ ์˜ˆ์ „์— ๋ฉด์ ‘๊ณผ์ œ๋กœ ๋ฐ›์•˜๋˜ ๋ฉ”๋ชจ์žฅ ์–ดํ”Œ์„ ๋‹ค์‹œ ๋งŒ๋“ค์–ด๋ณด์•˜๋‹ค.

    ์ „์—ญ ์ƒํƒœ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์—ญ์‹œ ์‚ฌ์šฉํ•˜๊ณ ์ž ํ–ˆ๊ณ , ์ด์ „ Redux์˜ ๋ถˆํ•„์š”ํ•œ ๊ตฌ์„ฑ๊ณผ ๋ณต์žกํ•œ ์›๋ฆฌ์— ํ•™์„ ๋—€์ง€๋ผ..

    ํŽ˜์ด์Šค๋ถ์—์„œ ์ถœ์‹œํ•œ React ์ „์šฉ ์ „์—ญ ์ƒํƒœ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ธ Recoil์„ ์ด๋ฒˆ ๊ธฐํšŒ์— ํ•™์Šต & ์‚ฌ์šฉํ•ด๋ณด๊ณ ์ž ๊ฒฐ์‹ฌํ–ˆ๋‹ค!

     

    ์šฐ์„ , ๋‘๊ด„์‹์œผ๋กœ ๊ฒฐ๋ก ์„ ๋‚ด๋ฆฌ์ž๋ฉด, ๋‚ด๊ฐ€ ์•ž์œผ๋กœ React ํ”„๋กœ์ ํŠธ๋ฅผ ์ž‘์—…ํ•œ๋‹ค๋ฉด ์™ ๋งŒํ•˜๋ฉด Recoil์ด๋‹ค!

    ๊ทธ๋งŒํผ, Redux์ฒ˜๋Ÿผ ๋‹ค์–‘ํ•œ ๊ตฌ์„ฑ(action, reducer ๋“ฑ)์„ ํ•  ํ•„์š”๊ฐ€ ์—†์œผ๋ฉฐ, ํŠนํžˆ ๋น„๋™๊ธฐ ์š”์ฒญ์ด ๋งค์šฐ ์‹ฌํ”Œํ•˜๋‹ค.

    (Redux์˜ ๊ฒฝ์šฐ ๋น„๋™๊ธฐ ์š”์ฒญ์„ ์œ„ํ•œ Redux-thunk, Redux-Saga ๋“ฑ์˜ ์„œ๋“œํŒŒํ‹ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ํ•„์š”ํ–ˆ์Œ!)

     

    ๋˜ํ•œ, Recoil ๋‚ด์—์„œ ํ…Œ๋งˆ๋ฅผ ๋‚˜๋ˆŒ ํ•„์š”๊ฐ€ ์—†์„ ๋งŒํผ ์‚ฌ์šฉ๋ฒ•์ด ๋งค์šฐ ์‹ฌํ”Œํ•˜๊ธฐ ๋•Œ๋ฌธ์—,

    ์ด ํฌ์ŠคํŒ… ํ•˜๋‚˜์— Recoil์˜ ๊ฐœ์š”, ์‚ฌ์šฉ๋ฒ•, ๊ทธ๋ฆฌ๊ณ  ํ† ์ด๋ฅผ ์ง„ํ–‰ํ•˜๋ฉฐ ๋Š๋‚€ ๋‚˜์˜ ์†Œ๊ฐ๊นŒ์ง€ ํ•œ ๋ฒˆ ์ •๋ฆฌํ•ด๋ณด๋„๋ก ํ•˜๊ฒ ๋‹ค!


    ๐Ÿงก Recoil ์ด๋ž€?

    Recoil์€ React ํ”„๋กœ์ ํŠธ๋ฅผ ์œ„ํ•œ ๋งŽ์€ ์ „์—ญ ์ƒํƒœ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค ์ค‘ ํ•˜๋‚˜๋กœ, 2020๋…„ 5์›” Facebook์—์„œ ์ถœ์‹œํ•˜์˜€๋‹ค.

    ๊ทธ๋ ‡๊ธฐ์—, ๋‹ค๋ฅธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ(Redux, Mobx)์™€๋Š” ๋‹ฌ๋ฆฌ React ์ „์šฉ์ด๋ฉฐ React์— ์ตœ์ ํ™”๋˜์–ด ์žˆ๋‹ค๊ณ  ํ•  ์ˆ˜ ์žˆ๋‹ค.

     

    - ์ด์ „ ์ „์—ญ ์ƒํƒœ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค์˜ ๋ฌธ์ œ?

    ๋Œ€ํ‘œ์ ์œผ๋กœ ์‚ฌ์šฉ๋ฌ๋˜ Redux, Mobx๋“ค์˜ ์„ฑ๋Šฅ ์ž์ฒด์— ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ๋˜ ๊ฒƒ์€ ์•„๋‹ˆ๋‹ค.

    ์˜คํžˆ๋ ค, ํŽ˜์ด์Šค๋ถ์—์„œ ๊ณ ์•ˆํ•œ Flux ํŒจํ„ด์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์„ค๊ณ„๋˜๋ฉด์„œ ์•ˆ์ •์ ์ธ ์ „์—ญ์ƒํƒœ ๊ด€๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•˜์˜€๋‹ค. (Flux ํŒจํ„ด ๊ด€๋ จ๋งํฌ)

    ์ฐธ๊ณ  : Flux ํŒจํ„ด

     

    ๊ทธ๋Ÿผ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ , Redux์˜ ์‚ฌ์šฉ๋„์™€ ๋งŒ์กฑ๋„๊ฐ€ ๊ฐ์†Œํ•˜๊ฒŒ ๋œ ๋ฐ์—๋Š” ์•„๋ž˜์™€ ๊ฐ™์€ ๋ฌธ์ œ๋“ค์ด ์ž”์กดํ•˜์˜€๋‹ค.

    • React ์ „์šฉ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์•„๋‹ˆ๋‹ค! React ๊ด€์ ์—์„  ์™ธ๋ถ€์š”์ธ์œผ๋กœ Store๊ฐ€ ์ทจ๊ธ‰๋˜๋ฉฐ, ๋™์‹œ์„ฑ ๋ชจ๋“œ๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ์— ํ˜ธํ™˜์„ฑ์ด ๋ถ€์กฑํ•˜๋‹ค.
    • ๋ณต์žกํ•œ Boiler Plate ์ดˆ๊ธฐ์„ธํŒ…์ด ์š”๊ตฌ๋œ๋‹ค! Store, Action, Reducer ๋“ฑ ๋‹ค์–‘ํ•œ ๊ตฌ์„ฑ์š”์†Œ๊ฐ€ ํ•„์š”ํ•ด ๋น„ํšจ์œจ์ ์ด๋ฉฐ ๋Ÿฌ๋‹์ปค๋ธŒ๊ฐ€ ๋†’๋‹ค.
    • ๋น„๋™๊ธฐ ๋ฐ์ดํ„ฐ์— ์ถ”๊ฐ€ ๋ฆฌ์†Œ์Šค๊ฐ€ ์š”๊ตฌ๋œ๋‹ค! Redux-saga ๋“ฑ ์ „์—ญ์ƒํƒœ์— ๋น„๋™๊ธฐ ๋ฐ์ดํ„ฐ๋ฅผ ํ˜ธ์ถœํ•˜๊ธฐ ์œ„ํ•œ ์„œ๋“œํŒŒํ‹ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

    ์ด๋Ÿฌํ•œ ๋‹จ์ ๋“ค์„ ๋ณด์™„ํ•œ, ๊ทธ๋ฆฌ๊ณ  React์— ์ข€ ๋” ์ตœ์ ํ™”๋œ ์ „์—ญ ์ƒํƒœ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ผ๋Š” ์‚ฌ๋ช…์„ ๊ฐ€์ง€๊ณ  ๋“ฑ์žฅํ•œ ๊ฒƒ์ด ๋ฐ”๋กœ Recoil ์ด๋‹ค!

     

     

    - Recoil ์ถœ์‹œ ๋ฐฐ๊ฒฝ

    Recoil์€ ์šฐ์„  React ์ „์šฉ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ธ ๋งŒํผ React ๋‚ด๋ถ€ ์ ‘๊ทผ์„ฑ์ด ์šฉ์ดํ•˜๋‹ค. 

    ํŠนํžˆ, React ๋™์‹œ์„ฑ ๋ชจ๋“œ, Suspense ๋“ฑ์„ ์ง€์›ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์‚ฌ์šฉ์ž ๊ฒฝํ—˜ ๊ด€์ ์—์„œ๋„ ๋งค์šฐ ์œ ๋ฆฌํ•œ ์›น ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

     

    ๋˜ํ•œ, ์ „์—ญ์ƒํƒœ์˜ ์„ค์ •/์ •์˜๊ฐ€ ๋งค์šฐ ์‰ฌ์šฐ๋ฉฐ, Recoil์ด ์ง€์›ํ•˜๋Š” Hooks๋กœ ์ด๋ฅผ get/set ํ•˜๊ธฐ ๋•Œ๋ฌธ์— React ๋ฌธ๋ฒ•๊ณผ ๋งค์šฐ ์œ ์‚ฌํ•˜๋‹ค.

    ์ „์—ญ์ƒํƒœ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ Boiler Plate ์–‘์ด ํ˜„์ €ํžˆ ์ ์œผ๋ฉฐ(recoil ๋””๋ ‰ํ† ๋ฆฌ๋งŒ ํ•„์š”), ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ Hooks ๊ธฐ๋ฐ˜์œผ๋กœ ๋งค์šฐ ์‹ฌํ”Œํ•˜๋ฉฐ React์Šค๋Ÿฝ๊ธฐ ๋•Œ๋ฌธ์— ๋Ÿฌ๋‹์ปค๋ธŒ๊ฐ€ ๋‚ฎ๋‹ค๋Š” ์žฅ์ ์ด ์žˆ๋‹ค.

     

    Recoil ๊ณต์‹๋ฌธ์„œ์—์„œ๋Š”, ์ถœ์‹œํ•œ ๋™๊ธฐ์— ๋Œ€ํ•ด ์•„๋ž˜์™€ ๊ฐ™์ด ์ •๋ฆฌํ•œ๋‹ค.

    • ๊ณต์œ ์ƒํƒœ(Shared State)๋„ React ๋‚ด๋ถ€์ƒํƒœ(Local State)์ฒ˜๋Ÿผ ๊ฐ„๋‹จํ•œ get/set ์ธํ„ฐํŽ˜์ด์Šค๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” Boilerplate-free API๋ฅผ ์ œ๊ณตํ•˜๊ณ ์ž ํ–ˆ๋‹ค.
    • ๋™์‹œ์„ฑ ๋ชจ๋“œ(Concurrent Mode)๋ฅผ ๋น„๋กฏํ•œ ๋‹ค๋ฅธ ์ƒˆ๋กœ์šด React์˜ ๊ธฐ๋Šฅ๋“ค๊ณผ์˜ ํ˜ธํ™˜ ๊ฐ€๋Šฅ์„ฑ๋„ ๊ฐ–๋Š”๋‹ค.
    • ์ƒํƒœ ์ •์˜๊ฐ€ ์ฆ๋ถ„ ๋ฐ ๋ถ„์‚ฐ๋˜๋ฏ€๋กœ ์ฝ”๋“œ๋ถ„ํ• ์ด ๊ฐ€๋Šฅํ•˜๋‹ค. 
    • ์ƒํƒœ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ์—์„œ ์ˆ˜์ • ํ•„์š”์—†์ด, ์ƒํƒœ์—์„œ ํŒŒ์ƒ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋‹ค. ๋˜ํ•œ, ํŒŒ์ƒ๋œ ๋ฐ์ดํ„ฐ๋Š” ๋™๊ธฐ/๋น„๋™๊ธฐ ๋ชจ๋‘ ๊ฐ€๋Šฅํ•˜๋‹ค.
    • ์šฐ๋ฆฌ๋Š” ํƒ์ƒ‰์„ ์ผ๊ธ‰ ๊ฐœ๋…์œผ๋กœ ์ทจ๊ธ‰ํ•  ์ˆ˜ ์žˆ๊ณ , ์‹ฌ์ง€์–ด ๋งํฌ์—์„œ ์ƒํƒœ ์ „ํ™˜์„ ์ธ์ฝ”๋”ฉํ•  ์ˆ˜ ์žˆ๋‹ค.
    • ์—ญํ˜ธํ™˜์„ฑ ๋ฐฉ์‹์œผ๋กœ ์ „์ฒด ์•ฑ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•˜๋Š” ๊ฒƒ์€ ์‰ฌ์šฐ๋ฏ€๋กœ, ์œ ์ง€๋œ ์ƒํƒœ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ณ€๊ฒฝ์—๋„ ์‚ด์•„๋‚จ์„ ์ˆ˜ ์žˆ๋‹ค. (์บ์‹ฑ)

     

    ๐Ÿงก Recoil ์‹œ์ž‘ํ•˜๊ธฐ

    - ์„ค์น˜ ๋ฐ ์ ์šฉ

    // npm(yarn)
    npm install recoil
    yarn add recoil
    
    // CDN
    <script src="https://cdn.jsdelivr.net/npm/recoil@0.0.11/umd/recoil.production.js"></script>

    ์„ค์น˜๋Š” ์œ„์ฒ˜๋Ÿผ npm(yarn) ํ˜น์€ CDN 2๊ฐ€์ง€ ๋ฐฉ์‹์œผ๋กœ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

    * Recoil์€ Webpack๊ณผ ๊ฐ™์€ ๋ชจ๋“ˆ ๋ฒˆ๋“ค๋Ÿฌ์™€ ํ˜ธํ™˜์ด ๋˜๋ฉฐ, ES6 โžก๏ธ 5๋กœ๋Š” ํŠธ๋žœ์ŠคํŒŒ์ผ๋ง ๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ํฌ๋กœ์Šค ๋ธŒ๋ผ์šฐ์ง•์„ ์œ„ํ•ด Babel์„ ํ†ตํ•œ ์ฝ”๋“œ ์ปดํŒŒ์ผ ๊ณผ์ •์ด ์š”๊ตฌ๋œ๋‹ค.

     

    ํ”„๋กœ์ ํŠธ์— ์ ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

    // index.tsx
    
    import React, { StrictMode } from 'react';
    import ReactDOM from 'react-dom';
    import { RecoilRoot } from 'recoil';
    import './index.css';
    import App from './App';
    
    ReactDOM.render(
      <StrictMode>
        <RecoilRoot>	// Recoil Root ์ ์šฉ!
          <React.Suspense fallback={<div>Loading... </div>}>
            <App />
          </React.Suspense>
        </RecoilRoot>
      </StrictMode>,
      document.getElementById('root'),
    );

    ๋จผ์ €, Recoil์„ ํ™œ์šฉํ•˜๊ธฐ ์œ„ํ•ด index.tsx ์ตœ์ƒ๋‹จ์˜ <App /> ์ปดํฌ๋„ŒํŠธ๋ฅผ <RecoilRoot> ๋กœ ๊ฐ์‹ธ์ฃผ๊ธฐ๋งŒ ํ•˜๋ฉด ๋œ๋‹ค! (๋งค์šฐ ์‰ฝ๋‹ค.)

     

     

    ๊ทธ๋ฆฌ๊ณ , /src ๋‚ด recoil ํด๋”๋ฅผ ๋งŒ๋“ค์–ด์„œ ์—ฌ๊ธฐ์— ์ „์—ญ์ƒํƒœ์— ๊ด€๋ จ๋œ Atoms, Selector ๋“ค์„ ์„ค์ •ํ•œ๋‹ค.

     

     

    - ์ฃผ์š”๊ฐœ๋…

    Recoil์„ ์‚ฌ์šฉํ•˜๋ฉด atoms (๊ณต์œ  ์ƒํƒœ)์—์„œ selector(์ˆœ์ˆ˜ ํ•จ์ˆ˜)๋ฅผ ๊ฑฐ์ณ React ์ปดํฌ๋„ŒํŠธ๋กœ ๋‚ด๋ ค๊ฐ€๋Š” data-flow graph๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

    Atoms๋Š” ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๊ตฌ๋…ํ•  ์ˆ˜ ์žˆ๋Š” ์ƒํƒœ์˜ ๋‹จ์œ„๋‹ค. Selectors๋Š” atoms ์ƒํƒœ๊ฐ’์„ ๋™๊ธฐ ๋˜๋Š” ๋น„๋™๊ธฐ ๋ฐฉ์‹์„ ํ†ตํ•ด ๋ณ€ํ™˜ํ•œ๋‹ค.

    ๋˜ํ•œ, Recoil์—์„œ ์ง€์›ํ•˜๋Š” Hooks๋“ค๋กœ atom ํ˜น์€ selecter์˜ get(์ ‘๊ทผ) ๋ฐ set(์ˆ˜์ •) ๋“ฑ์˜ ๋‹ค์–‘ํ•œ ๋™์ž‘์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

     

     

    1. Atoms

     

    Atoms๋Š” Recoil ์ƒํƒœ์˜ ๋‹จ์œ„๋ฅผ ์˜๋ฏธํ•œ๋‹ค. ์ปดํฌ๋„ŒํŠธ๊ฐ„์— ์ด ์ƒํƒœ๋Š” ๊ณต์œ ๋˜๋ฉฐ, ๊ตฌ๋… ๋ฐ ์—…๋ฐ์ดํŠธ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.

    ํŠนํžˆ, atom์˜ ์ƒํƒœ๊ฐ€ ์—…๋ฐ์ดํŠธ๋˜๋ฉด, ์ด๋ฅผ ๊ตฌ๋…ํ•˜๋˜ ์ปดํฌ๋„ŒํŠธ๋“ค์ด ๋ชจ๋‘ ๋ฆฌ๋ Œ๋”๋ง๋œ๋‹ค.

    * Redux๋Š” Reducer(ํ†ต์ƒ ์ƒ์œ„ ๋„๋ฉ”์ธ) ๋‹จ์œ„๋กœ state๋ฅผ ๊ตฌ์„ฑํ•˜๋‚˜, Recoil์€ Atoms(์ƒํƒœ) ๋‹จ์œ„๋กœ ์ข€ ๋” ์ž˜๊ฒŒ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

     

    Atoms์„ ์„ค์ •ํ•  ๋•, Recoil์˜ atom() ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ๋ณ€์ˆ˜์— ํ• ๋‹นํ•ด์ฃผ๋ฉด ๋œ๋‹ค. ์ด ๋•Œ, key, default 2๊ฐœ์˜ ํ”„๋กœํผํ‹ฐ๋ฅผ ํ•„์ˆ˜๋กœ ์„ค์ •ํ•ด์•ผํ•œ๋‹ค.

    • key : ๊ณ ์œ ํ•œ key ๊ฐ’ (๋ณดํ†ต ํ•ด๋‹น atom์„ ์ƒ์„ฑํ•˜๋Š” ๋ณ€์ˆ˜ ๋ช…์œผ๋กœ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.)
    • default : atom ์˜ ์ดˆ๊ธฐ๊ฐ’์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. ์ •์ ์ธ ๊ฐ’(int, string...), promise, ๋‹ค๋ฅธ atom ์˜ ๊ฐ’์œผ๋กœ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    // /src/recoil/index.ts
    
    import { atom } from 'recoil';
    
    export const focusMemoState = atom<MemoItem>({
      key: 'focusMemo',
      default: null,
    })
    
    export const checkedMemoListState = atom<string[]>({
      key: 'checkedMemoList',
      default: [],
    })

    ์ด๋Š”, ๋‚ด ํ† ์ด ํ”„๋กœ์ ํŠธ์—์„œ focus๋œ ๋ฉ”๋ชจ(Object), ์ฒดํฌ๋œ ๋ฉ”๋ชจ๋“ค์˜ id๋ฐฐ์—ด์„ ๊ฐ๊ฐ ์ „์—ญ์ƒํƒœ๋กœ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด atom์„ ์„ค์ •ํ•œ ๋ชจ์Šต์ด๋‹ค.

    key๋Š” ์ค‘์ฒฉ๋˜์ง€ ์•Š๊ฒŒ ์„ค์ •ํ•ด์•ผํ•˜๋ฉฐ, default ๊ฐ’ ์—ญ์‹œ ๋ถ€์—ฌํ•ด์ค€ ๋ชจ์Šต์ด๋‹ค.

    * default ๊ฐ’์€ Promise ๊ฐ์ฒด๋„ ์„ค์ •๊ฐ€๋Šฅํ•˜๋‚˜, atom์—์„œ ๋ฐ”๋กœ ๋น„๋™๊ธฐ ์š”์ฒญ์„ ํ•  ์ˆœ ์—†๋‹ค. ์ด๋Š”, ์ดํ›„ Selector์—์„œ ๋ฐฐ์›Œ๋ณด์ž!

     

     

     

    2. ์ „์—ญ์ƒํƒœ ๊ด€๋ จ Hooks

     

    ์ „์—ญ์ƒํƒœ(Atoms, Selector)๋ฅผ get/set ํ•˜๊ธฐ ์œ„ํ•ด Recoil์—์„œ ์ œ๊ณตํ•˜๋Š” Hooks๋“ค์„ ์‚ฌ์šฉํ•œ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ ์•„๋ž˜ 4๊ฐ€์ง€๊ฐ€ ํฌ๊ฒŒ ์‚ฌ์šฉ๋œ๋‹ค.

    • useRecoilState() : useState() ์™€ ์œ ์‚ฌํ•˜๋‹ค. [state, setState] ํŠœํ”Œ์— ํ• ๋‹นํ•˜๋ฉฐ, ์ธ์ž์— Atoms(ํ˜น์€ Selector)๋ฅผ ๋„ฃ์–ด์ค€๋‹ค.
    • useRecoilValue() : ์ „์—ญ์ƒํƒœ์˜ state ์ƒํƒœ๊ฐ’๋งŒ์„ ์ฐธ์กฐํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋œ๋‹ค. ์„ ์–ธ๋œ ๋ณ€์ˆ˜์— ํ• ๋‹นํ•˜์—ฌ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.
    • useSetRecoilState() : ์ „์—ญ์ƒํƒœ์˜ setter ํ•จ์ˆ˜๋งŒ์„ ํ™œ์šฉํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋œ๋‹ค. ์„ ์–ธ๋œ ํ•จ์ˆ˜๋ณ€์ˆ˜์— ํ• ๋‹นํ•˜์—ฌ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.
    • useResetRecoilState() : ์ „์—ญ์ƒํƒœ๋ฅผ default(์ดˆ๊ธฐ๊ฐ’)์œผ๋กœ Reset ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋œ๋‹ค. ์„ ์–ธ๋œ ํ•จ์ˆ˜๋ณ€์ˆ˜์— ํ• ๋‹นํ•˜์—ฌ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.
    // ์˜ˆ์‹œ
    
    import { useRecoilState, useSetRecoilState, useResetRecoilState } from 'recoil';
    import { countState } from '../../recoil/count';
    
    function ReadWriteCount() {
      const [ count, setCount ] = useRecoilState(countState); // useRecoilState ์„ ํ†ตํ•œ value, setter ๋ฐ˜ํ™˜
      const countValue = useRecoilValue(countState); // ๊ตฌ๋…ํ•˜๋Š” atom ์˜ ๊ฐ’๋งŒ ๋ฐ˜ํ™˜
      const setCountUseSetRecoilState = useSetRecoilState(countState); // ๊ฐ’์„ ๋ณ€๊ฒฝํ•˜๋Š” ํ•จ์ˆ˜๋งŒ ๋ฐ˜ํ™˜
      const resetCount = useResetRecoilState(countState); // ์„ค์ •๋œ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ๋ฆฌ์…‹
        
      return (
        <div>
          <h2>์ฝ๊ธฐ ์“ฐ๊ธฐ ์นด์šดํŠธ ์ปดํฌ๋„ŒํŠธ</h2>
          
          <p>์นด์šดํŠธ {count}</p>
          <p>์นด์šดํŠธ(useRecoilValue ์‚ฌ์šฉ) {countValue}</p>
          
          <button onClick={() => setCount(count + 1)}>์ˆซ์ž ์ฆ๊ฐ€</button>
          <button onClick={() => setCount(count - 1)}>์ˆซ์ž ๊ฐ์†Œ</button>
          <button onClick={() => setCountUseSetRecoilState(count + 1)}>์ˆซ์ž ์ฆ๊ฐ€ (useSetRecoilState ์‚ฌ์šฉ)</button>
          <button onClick={() => setCountUseSetRecoilState(count - 1)}>์ˆซ์ž ๊ฐ์†Œ (useSetRecoilState ์‚ฌ์šฉ)</button>
          <button onClick={resetCount}>์นด์šดํŠธ ๋ฆฌ์…‹</button>
        </div>
      );
    }
    
    export default ReadWriteCount;

     

     

     

    3. Selector

     

    Selector๋Š” atom ํ˜น์€ ๋‹ค๋ฅธ Selector ์ƒํƒœ๋ฅผ ์ž…๋ ฅ๋ฐ›์•„ ๋™์ ์ธ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ์ˆœ์ˆ˜ํ•จ์ˆ˜(Pure Function) ์ด๋‹ค.

    ์ƒํƒœ๊ฐ’์—์„œ ๋น„๋กฏ๋œ ํŒŒ์ƒ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋งŒ๋“ค ๋•Œ ์‚ฌ์šฉ๋˜๋ฉฐ, atom์ฒ˜๋Ÿผ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ด๋ฅผ ๊ตฌ๋…ํ•  ์ˆ˜ ์žˆ๋‹ค. (readonly ๊ฐ’์ด๋ฏ€๋กœ useRecoilValue)

    Selector๊ฐ€ ์ฐธ์กฐํ•˜๋˜ ๋‹ค๋ฅธ ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด ์ด๋„ ๊ฐ™์ด ์—…๋ฐ์ดํŠธ๋˜๋ฉฐ, ์ด ๋•Œ Selector๋ฅผ ๋ฐ”๋ผ๋ณด๋˜ ์ปดํฌ๋„ŒํŠธ๋“ค์ด ๋ฆฌ๋ Œ๋”๋ง ๋˜๋Š” ๊ฒƒ์ด๋‹ค.

     

    Selector๋ฅผ ์„ค์ •ํ•  ๋•Œ๋„, Recoil์˜ selector() ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ๋“ฑ๋กํ•˜๋ฉด ๋œ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋ก , key์™€ get 2๊ฐœ์˜ ํ”„๋กœํผํ‹ฐ๋ฅผ ์„ค์ •ํ•œ๋‹ค.

    • key : ๊ณ ์œ ํ•œ key ๊ฐ’
    • get : Selector ์ˆœ์ˆ˜ํ•จ์ˆ˜. ์‚ฌ์šฉํ•  ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋ฉฐ, ๋งค๊ฐœ๋ณ€์ˆ˜์ธ ์ฝœ๋ฐฑ๊ฐ์ฒด ๋‚ด get() ๋ฉ”์„œ๋“œ๋กœ ๋‹ค๋ฅธ atom ํ˜น์€ selector๋ฅผ ์ฐธ์กฐํ•œ๋‹ค.
    export const memoListSelector = selector<MemoList>({
      key: 'memoList',
      get: async ({ get }) => {
        const id = get(focusLabelState)?.id;
        const totalList = get(totalMemoListSelector);
        return id ? getMemoListByLabel(id) : totalList;
      }
    })

    ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ํ† ์ด์—์„œ ๋ฉ”๋ชจ๋ชฉ๋ก ๋ฐฐ์—ด๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋Š” Selector๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค.

    id๋Š” ํฌ์ปค์Šค๋œ ๋ผ๋ฒจ์˜ ์•„์ด๋””(atom) / totalList๋Š” ์ „์ฒด ๋ฉ”๋ชจ๋ฆฌ์ŠคํŠธ(selector)๋ฅผ ๊ฐ๊ฐ ๊ฐ€์ ธ์˜จ ๋’ค,

    ์•„์ด๋””๊ฐ€ ์žˆ์œผ๋ฉด ๋ผ๋ฒจ ์•„์ด๋””์— ํ•ด๋‹นํ•˜๋Š” ๋ฉ”๋ชจ๋ฆฌ์ŠคํŠธ๋ฅผ / ์—†๋‹ค๋ฉด ์ „์ฒด ๋ฉ”๋ชจ๋ฆฌ์ŠคํŠธ๋ฅผ ๋™์ ์œผ๋กœ ๋ฐ˜ํ™˜ํ•˜๋Š” selector ์ธ ๊ฒƒ์ด๋‹ค.

     

    ์ด๋ ‡๊ฒŒ ๋งŒ๋“ค์—ˆ์„ ๋•Œ ์žฅ์ ์€, focusLabel์˜ ์•„์ด๋””๊ฐ€ ๋ฐ”๋€” ๋•Œ๋งˆ๋‹ค ์ด selector๋„ ๋™์ ์œผ๋กœ ํ•ด๋‹นํ•˜๋Š” ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ฐ˜ํ™˜ํ•ด์ฃผ๋Š” ๋ถ€๋ถ„์ด๋‹ค.

    Selector๋Š” ์œ„์ฒ˜๋Ÿผ ํŒŒ์ƒ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ชฉ์ ์„ฑ ์™ธ์—๋„, ์œ ์šฉํ•œ ์‚ฌ์šฉ์„ฑ๋“ค์ด ๋ช‡ ๊ฐ€์ง€ ์กด์žฌํ•œ๋‹ค.

     

     

    * set ํ•จ์ˆ˜(ํ”„๋กœํผํ‹ฐ) : ์“ฐ๊ธฐ ๊ฐ€๋Šฅํ•œ Selector

     

    Selector์— set์„ ์„ค์ •ํ•˜๊ฒŒ ๋˜๋ฉด, ์“ฐ๊ธฐ ๊ฐ€๋Šฅํ•œ ๋ชจ๋“œ๋กœ ๋ณ€๊ฒฝ๋œ๋‹ค. set์€ ์ฝœ๋ฐฑ๊ฐ์ฒด, ์ƒˆ๋กœ์šด ๊ฐ’ 2๊ฐ€์ง€๋ฅผ ๊ฐ๊ฐ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋ฐ›๋Š”๋‹ค.

    ์ฝœ๋ฐฑ๊ฐ์ฒด ๋‚ด์—๋Š” ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ set() ๋ฉ”์„œ๋“œ๊ฐ€ ์กด์žฌํ•˜๋ฉฐ, ์ด๋Š” ๋‹ค๋ฅธ atom๋“ค์„ ์ƒˆ๋กœ์šด ๊ฐ’์œผ๋กœ ์„ธํŒ…ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋œ๋‹ค.

    (set ๋ฉ”์„œ๋“œ๋ฅผ ์“ธ ๋•Œ ์œ ์˜ํ•  ์ ์€ ๋ณธ์ธ ์Šค์Šค๋กœ๋ฅผ ์ˆ˜์ •ํ•  ์ˆ˜ ์—†๋‹ค๋Š” ๊ฒƒ์ด๋‹ค! ๋ณธ์ธ์„ ๊ฐฑ์‹ ํ•˜๊ธฐ ์œ„ํ•ด์„  ์ถ”๊ฐ€์ ์œผ๋กœ ์˜์กด์„ฑ์„ ์„ค์ •ํ•˜๋Š”๋ฐ ์ด ๋ฐฉ๋ฒ•์€ ๋” ์•„๋ž˜์— ์†Œ๊ฐœํ•˜๊ฒ ๋‹ค.)

    const proxySelector = selector({
      key: 'ProxySelector',
      get: ({get}) => ({...get(myAtom), extraField: 'hi'}),
      set: ({set}, newValue) => set(myAtom, newValue),
    });

    ์ด์ฒ˜๋Ÿผ, set() ๋ฉ”์„œ๋“œ๋Š” ์ƒํƒœ๋ณ€์ˆ˜, ๊ฐ’ 2๊ฐ€์ง€๋ฅผ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋ฐ›๋Š”๋‹ค. ๋˜ํ•œ, ํ•ด๋‹น ์ƒํƒœ๊ฐ’์„ newValue๋กœ ๊ฐฑ์‹ ํ•ด์ฃผ๋Š” ์—ญํ• ์„ ํ•œ๋‹ค.

    ์ปดํฌ๋„ŒํŠธ์—์„œ ์ด set์„ ์‚ฌ์šฉํ• ๋•Œ๋Š”, useRecoilState() Hooks๋กœ Selector๋ฅผ ๊ฐ€์ ธ์˜ค๋ฉด setter ํ•จ์ˆ˜๊ฐ€ ์ด set()์˜ ์—ญํ• ์„ ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

    // ์ปดํฌ๋„ŒํŠธ ์˜ˆ์‹œ
    
    // ...
      const [proxy, setProxy] = useRecoilState(proxySelector);
      
      setProxy('์ƒˆ๋กœ์šด ๊ฐ’!');
    }

     

     

    * ๋น„๋™๊ธฐ Selector

     

    ์–ด์ฉŒ๋ฉด, Recoil์˜ ๊ฐ•์  ์ค‘ ํ•˜๋‚˜์ด์ž ์ƒํƒœ๊ฐ’์— Selector๊ฐ€ ๋งŽ์ด ์“ฐ์ด๋Š” ์ด์œ ์ผ ๊ฒƒ์ด๋‹ค. 

    API ๋“ฑ ๋น„๋™๊ธฐ ์š”์ฒญ์„ ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ „์—ญ์ƒํƒœ์— ๋„ฃ๋Š” ๊ฒฝ์šฐ๊ฐ€ ์ข…์ข… ์žˆ๋‹ค. ์ด ๋•Œ, ์ด ๋น„๋™๊ธฐ ํ˜ธ์ถœ์„ ๋‚ด๋ถ€์— ์„ค์ •ํ•ด ์ƒํƒœ๊ฐ’์œผ๋กœ ๋ฐ˜ํ™˜ํ•˜๋Š” ์—ญํ•  ์—ญ์‹œ Selector๊ฐ€ ์ถฉ์‹คํ•˜๊ฒŒ ์ˆ˜ํ–‰ํ•ด์ค€๋‹ค!

    const myQuery = selector({
      key: 'MyQuery',
      get: async ({get}) => {
        return await myAsyncQuery(get(queryParamState));	// ๋น„๋™๊ธฐ ํ˜ธ์ถœ ๋ถ€๋ถ„
      },
    });

    ์œ ์˜ํ•ด์•ผ ํ•  ๋ถ€๋ถ„์€, ๋น„๋™๊ธฐ Promise ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ธฐ ๋•Œ๋ฌธ์— async/await ๋ฌธ์„ ์ ์šฉํ•ด์•ผ ํ•˜๋Š” ๋ถ€๋ถ„์ด๋‹ค.

    ๋˜ํ•œ, ์ด๋ ‡๊ฒŒ ๋น„๋™๊ธฐ Selector๋งŒ ์‚ฌ์šฉํ•˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š”๋ฐ, ์ด๋Š” Suspense์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜์ง€ ์•Š์•„์„œ ๋ฐœ์ƒํ•œ ๊ฒƒ์ด๋‹ค.

     

    Recoil์€ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ React์˜ suspense๋ฅผ ํ†ตํ•ด ์ง€์›ํ•œ๋‹ค. ์œ„ ์—๋Ÿฌ๋Š”, ๋น„๋™๊ธฐ ํ˜ธ์ถœ๊ฐ„ ๋…ธ์ถœํ•  fallback UI๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์•„ ๋ฐœ์ƒํ–ˆ๋‹ค.

    ๊ทธ๋ ‡๊ธฐ์—, ๋น„๋™๊ธฐ ํ˜ธ์ถœ์ด ์‚ฌ์šฉ๋˜๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ <React.Suspense>๋กœ ๋žฉํ•‘ํ•ด์ฃผ๊ณ , fallback์œผ๋กœ ํ˜ธ์ถœํ•  ์ปดํฌ๋„ŒํŠธ๋ฅผ ์„ค์ •ํ•ด์ฃผ๋ฉด ๋œ๋‹ค!

    <React.Suspense fallback={<div>Loading...</div>}>
      <Component />
    </React.Suspense>

     

     

    * Loadable : ๋˜ ๋‹ค๋ฅธ ๋น„๋™๊ธฐ ์ œ์–ด ๋ฐฉ๋ฒ•

     

    Suspense ์™ธ์— ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์œผ๋กœ Loadable Hooks(useRecoilValueLodable, useRecoilStateLodable) ์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค.

    Lodable ๊ฐ์ฒด๋Š” state(๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ์ƒํƒœ), contents(์ƒํƒœ๊ฐ’) 2๊ฐ€์ง€ ํ”„๋กœํผํ‹ฐ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. (Promise ํŒจํ„ด๊ณผ ์œ ์‚ฌ)

    • state : hasValue , hasError , loading 3๊ฐ€์ง€ ์ƒํƒœ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
    • contents : atom์ด๋‚˜ contents์˜ ์ƒํƒœ๊ฐ’์„ ์˜๋ฏธํ•œ๋‹ค. hasValue ์ƒํƒœ์ผ ๋• value๋ฅผ, hasError ์ผ ๋• Error ๊ฐ์ฒด๋ฅผ, ๊ทธ๋ฆฌ๊ณ  loading ์ผ ๋• Promise๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.
    import { useRecoilValueLodable } from 'recoil';
    import { lodaSelector } from '@/src/recoil';
    
    const Component = () => {
      const lodaState = useRecoilValueLodable(lodaSelector);
      
        switch(lodaState.state){
          case 'hasValue':
            return <div>{lodaState.contents}</div>
          case 'loading':
            return <Loading />;
          case 'hasError':
         	throw lodaState.contents;
        }
    }

     

     

    * ์บ์‹ฑ : Selector๋ฅผ ํ†ตํ•œ ์„ฑ๋Šฅ ํ–ฅ์ƒ

     

    selector๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๊ฐ’์„ ์ž์ฒด์ ์œผ๋กœ ์บ์‹ฑํ•œ๋‹ค. ์ž…๋ ฅ๋œ ์  ์žˆ๋Š” ๊ฐ’์„ ๊ธฐ์–ตํ•˜๊ณ , ์ด ๊ฐ’์ด ์žฌํ˜ธ์ถœ๋˜๋ฉด ์บ์‹ฑ๋œ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ”๋กœ ๋ณด์—ฌ์ฃผ๊ธฐ ๋•Œ๋ฌธ์— ๋น„๋™๊ธฐ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค๋ฃจ๋Š” ์ธก๋ฉด์—์„œ ์œ ๋ฆฌํ•˜๋‹ค.

     

     

     

    4. atomFamily(), selectorFamily()

     

    ์ด Family ๋ฉ”์„œ๋“œ๋“ค์€ atom(ํ˜น์€ selector)์„ ๋ฆฌํ„ดํ•˜๋Š” ํŒฉํ† ๋ฆฌ ํ•จ์ˆ˜์ด๋‹ค. ์ธ์ž(params)๋ฅผ ๋ฐ›์•„, ์ด๋ฅผ ๋ฐ˜์˜ํ•œ ๋™์ ์ธ ์ƒํƒœ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

    ๊ทธ๋ ‡๊ธฐ์—, Recoil ๋‚ด ๋‹ค๋ฅธ ์ƒํƒœ๊ฐ’์ด ์•„๋‹Œ, ์™ธ๋ถ€์ธ์ž(Query Params ๋“ฑ)๋ฅผ ํ™œ์šฉํ•œ ์ƒํƒœ๊ฐ’์„ ๊ด€๋ฆฌํ•˜๊ธฐ์— ์œ ๋ฆฌํ•˜๋‹ค.

    // /recoil/index.ts
    
    const todoItemState = atomFamily<Todo, string>({
      key: 'todoItemState',
      default: (id) => {
        return {
          id,
          title: '',
          isDone: false,
        };
      },
    });
    // TodoItem.tsx
    
    const TodoItem: React.FC<Props> = ({ id }) => {
      const [todo, setTodo] = useRecoilState(todoItemState(id));
      
      // ...
    }

     

    * ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋ฐ˜์˜ํ•œ ๋น„๋™๊ธฐ ๋ฐ์ดํ„ฐ : selectorFamily()

     

    ์ด์ฒ˜๋Ÿผ, Family์˜ get, set์€ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ params ๊ฐ์ฒด๋ฅผ ๋ฐ›๋Š”๋‹ค. ์ด๋ฅผ ๋ฐ˜์˜ํ•œ ๋น„๋™๊ธฐ ๋ฐ์ดํ„ฐ๋ฅผ ์ƒํƒœ๊ฐ’์œผ๋กœ ์“ฐ๊ธฐ ์œ„ํ•ด selectorFamily๊ฐ€ ์‚ฌ์šฉ๋˜๋Š” ๊ฒƒ์ด๋‹ค.

    // /recoil/index.ts
    
    export const githubRepo = selectorFamily({
      key: "github/get",
      get: (githubId) => async () => {
        if (!githubId) return "";
    
        const { data } = await axios.get(
          `https://api.github.com/repos/${githubId}`
        );
        return data;
      },
    });
    // Components.jsx
    
    import { useRecoilValue } from 'recoil';
    import { selectorFamily } from '../../state';
    const Github = () => {
      const githubId = 'juno7803';
      const githubRepos = useRecoilValue(githubRepo(githubId));
      
      return(
        <>
          <div>Repos : {githubRepos}</div>
        </>
      )
    
    }
    export default Github;

     

     

    * Trigger

     

    ์ด ๋‚ด์šฉ์€ Docs ํ˜น์€ ๊ณต์‹์ ์œผ๋กœ ์†Œ๊ฐœ๋˜๋Š” ๋‚ด์šฉ์€ ์•„๋‹ˆ๋‚˜, ๋‚ด๊ฐ€ Selector์˜ ์ƒํƒœ๊ฐฑ์‹ ์— ๋Œ€ํ•ด ๊ณ ๋ฏผํ•˜๋‹ค๊ฐ€ ๋ฐฐ์šฐ๊ฒŒ ๋œ ๋ถ€๋ถ„์ด๋‹ค.

    ์ด๋Š” ๋‚ด๊ฐ€ ์ง„ํ–‰ํ•œ ํ† ์ด ํ”„๋กœ์ ํŠธ์˜ ๋ธŒ๋ผ์šฐ์ € ํ™”๋ฉด์ด๋‹ค. (๋ฉ”๋ชจ์žฅ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜)

    ์ปดํฌ๋„ŒํŠธ๋“ค์€ Recoil ์ „์—ญ์ƒํƒœ๋ฅผ ๊ณต์œ ํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ์œ„์ฒ˜๋Ÿผ ๋ผ๋ฒจ์„ ์ถ”๊ฐ€ํ•˜๊ณ  ํ•ด๋‹น ๋ผ๋ฒจ์— ๋ฉ”๋ชจ๋“ค์„ ๋“ฑ๋กํ•˜๋Š” ๋™์ž‘์„ ๋งŒ๋“ค์—ˆ๋‹ค.

     

    ๋งŒ์•ฝ, ๊ฐ€์šด๋ฐ์˜ <MemoList> ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ฉ”๋ชจ๋“ค์„ ์ฒดํฌํ•˜๊ณ  ์ด๋ฅผ ๋ผ๋ฒจ์— ์ถ”๊ฐ€ํ•œ๋‹ค๋ฉด, ์ขŒ์ธก์˜ <LabelList>์˜ ํ•ด๋‹น ๋ผ๋ฒจ์˜ ๋ฉ”๋ชจ์ˆซ์ž๋„ ๊ฐฑ์‹ ๋˜์–ด์•ผ ํ•˜๋Š” ์ด์Šˆ๊ฐ€ ์ƒ๊ธธ ๊ฒƒ์ด๋‹ค.

     

    ์ด ๋ฆฌ์ŠคํŠธ๋“ค์€ ์„œ๋ฒ„ ๋ฐ์ดํ„ฐ์—์„œ ๊ฐ€์ ธ์˜ค๊ธฐ ๋•Œ๋ฌธ์— Selector๋ฅผ ์‚ฌ์šฉํ–ˆ๊ณ , ์ด๋Š” readonlyํ•œ ๊ฐ’์ด๊ธฐ์— ์ง์ ‘ ์ˆ˜์ •ํ•  ์ˆ˜ ์—†๋‹ค.

    ๊ทธ๋ ‡๋‹ค๊ณ , set๋ฅผ ํ†ตํ•ด ์ผ์ผํžˆ ๊ฐฑ์‹ ๋œ ๊ฐ’์„ ์ „๋‹ฌํ•ด์„œ ์ƒํƒœ๊ฐ’์„ ์ˆ˜์ •ํ•œ๋‹ค๊ฑฐ๋‚˜, ํŽ˜์ด์ง€ ์ž์ฒด๋ฅผ reload ํ•ด๋ฒ„๋ฆฌ๋Š” ๊ฒƒ์€ ์ ์ ˆํ•˜์ง€ ์•Š๋‹ค.

     

    ์ด selector๊ฐ€ ๋น„๋™๊ธฐ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค์‹œ ๋ถˆ๋Ÿฌ์™€ ๊ฐฑ์‹ ํ•  ๋ฐฉ๋ฒ•์„ ๊ณ ๋ฏผํ–ˆ๊ณ , ์ด๋ฅผ ๊ตฌํ˜„ํ•œ ๋ฐฉ๋ฒ•๊ณผ ๊ธ€์„ ์†Œ๊ฐœํ•˜๊ณ ์ž ํ•œ๋‹ค.

     

    // /recoil/index.ts
    
    import { atom, atomFamily, selector } from 'recoil';
    import { getLabelList, getMemoList } from "services";
    import { TriggerParams } from 'types/recoil';
    import { LabelItem, LabelList, MemoItem, MemoList } from '../types/data';
    
    export const selectorTrigger = atomFamily<number, TriggerParams>({
      key: 'selectorTrigger',
      default: Date.now(),
    })
    
    export const labelListSelector = selector<LabelList>({
      key: 'labelList',
      get: async ({ get }) => {
        get(selectorTrigger('labelList'))
        const list = await getLabelList();
        return list;
      },
      set: ({ set }) => {
        set(selectorTrigger('labelList'), Date.now())
      }
    })
    
    export const memoListSelector = selector<MemoList>({
      key: 'memoList',
      get: async ({ get }) => {
        get(selectorTrigger('memoList'));
        const totalList = get(totalMemoListSelector);
        const id = get(focusLabelState)?.id;
        return id ? getMemoListByLabel(id) : totalList;
      },
      set: ({ set }) => {
        set(selectorTrigger('memoList'), Date.now())
      }
    })
    
    // ...
    1. selectorTrigger ๋ผ๋Š” atomFamily๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค. ์—ฌ๊ธฐ์„œ ๋ฐ˜ํ™˜ํ•˜๋Š” atom๋“ค์˜ Default ๊ฐ’์€ ์ƒ์„ฑ๋œ ์‹œ์ ์˜ ์‹œ๊ฐ„์ด๋‹ค.
    2. ์ด ํŠธ๋ฆฌ๊ฑฐ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” selector๋“ค์€ get์—์„œ ํŠธ๋ฆฌ๊ฑฐ๋ฅผ getํ•ด์™€์„œ ๊ตฌ๋…ํ•˜๊ณ  ์žˆ์–ด์•ผ ํ•œ๋‹ค.
    3. ํŠธ๋ฆฌ๊ฑฐ atomFamily์˜ atom key๋ช…์€ ๊ฐ selector ๋ช…์œผ๋กœ ํ•œ๋‹ค. (selectorTrigger('LabelList') ๋“ฑ)
    4. set์€ selectorTrigger์˜ ํ•ด๋‹น selector์˜ ํŠธ๋ฆฌ๊ฑฐ atom์„ ๊ฐฑ์‹ ํ•œ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด, ์ด๋ฅผ ๊ตฌ๋…ํ•˜๋˜ selector๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ์žฌํ˜ธ์ถœํ•œ๋‹ค.

    ํŠธ๋ฆฌ๊ฑฐ๋ฅผ ๊ฐฑ์‹ ํ•˜๋Š” ๋ฐฉ๋ฒ•์€, Date.now() ์™ธ์—๋„ count++ ๋“ฑ ์ž์œ ๋กญ๊ฒŒ ๊ตฌํ˜„ํ•˜๋ฉด ๋œ๋‹ค.

    ์ด ํŠธ๋ฆฌ๊ฑฐ๋Š” ์›๋ฆฌ๊ฐ€ ๋น„์Šทํ•œ atom์ด ์–‘์‚ฐ๋˜๊ธฐ ๋•Œ๋ฌธ์—, ์—ฌ๋Ÿฌ ๊ฐœ๋ฅผ ๋งŒ๋“œ๋Š”๊ฒƒ๋ณด๋‹ค atomFamily๋กœ ๋ฌถ๋Š”๊ฒŒ ์ƒ์‚ฐ์„ฑ๊ณผ ์œ ์ง€๋ณด์ˆ˜์— ์œ ๋ฆฌํ•˜๋‹ค.

     

    ๋‹ค์Œ์œผ๋กœ, ์ด set ํ•จ์ˆ˜๋ฅผ ํ• ๋‹นํ•ด์„œ ์‚ฌ์šฉํ•œ ์ปดํฌ๋„ŒํŠธ์˜ ์ฝ”๋“œ๋ฅผ ์†Œ๊ฐœํ•˜๊ฒ ๋‹ค.

    const MemoListHeader: React.FC = () => {
      /* Recoil State */
      const labelList = useRecoilValue<LabelList>(labelListSelector);
      const checkedMemoList = useRecoilValue<string[]>(checkedMemoListState);
      const [focusLabel, setFocusLabel] = useRecoilState<LabelItem>(focusLabelState);
    
      /* Recoil Reseter */
      const updateLabelList = useResetRecoilState(labelListSelector);	// * Selector setter
      const resetCheckedMemoList = useResetRecoilState(checkedMemoListState);
    
      const attachLabel = async () => {
        if (!checkedMemoList.length) {
          alert('๋ฉ”๋ชจ๋ฅผ ๋จผ์ € ์„ ํƒํ•ด์ฃผ์„ธ์š”!');
          return;
        }
        const title = await window.prompt();
        const label = labelList.find(e => e.title === title)
        if (!label) return;
        await postAttachLabel({ id: label.id, memoIds: checkedMemoList });
        resetCheckedMemoList();
        updateLabelList();
        setFocusLabel(label);
      }
      
      // ... 
    }
    1. attachLabel() ๋ฉ”์„œ๋“œ๋Š” ์ฒดํฌ๋œ ๋ฉ”๋ชจ๋“ค์— ๋ผ๋ฒจ์„ ๋ถ€์—ฌํ•˜๋Š” ๋ฉ”์„œ๋“œ๋‹ค.
    2. ์ฒดํฌ ๋ฉ”๋ชจ๋ชฉ๋ก ํ™•์ธ โžก๏ธ ๋ผ๋ฒจ๋ช… ์ˆ˜์ง‘ โžก๏ธ ๋ผ๋ฒจ ํƒ์ƒ‰ โžก๏ธ ๋ผ๋ฒจ ์ถ”๊ฐ€(API) โžก๏ธ ์ฒดํฌ ๋ฉ”๋ชจ๋ชฉ๋ก ์ดˆ๊ธฐํ™” โžก๏ธ ๋ผ๋ฒจ๋ชฉ๋ก ๊ฐฑ์‹  โžก๏ธ ํฌ์ปค์Šค ๋ผ๋ฒจ ๋ณ€๊ฒฝ
    3. updateLabelList() ๋Š” useResetRecoilState() ๋กœ Selector์˜ set ํ•จ์ˆ˜๋ฅผ ๊ฐ€์ ธ์™€ ํ• ๋‹นํ•œ ๋ณ€์ˆ˜์ด๋‹ค.
    4. ์ด๋ฅผ, ๋ฉ”์„œ๋“œ ๋‚ด์—์„œ ํ•„์š”ํ•œ ๋ถ€๋ถ„์—์„œ ํ˜ธ์ถœ์‹œ์ผœ labelList ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐฑ์‹ ํ•ด์ค€๋‹ค.

    ์ฝ”๋“œ์ฒ˜๋Ÿผ, selector์˜ set ํ•จ์ˆ˜๋Š” useResetRecoilState() Hooks๋กœ ๊ตฌํ˜„ํ•œ๋‹ค. ์ด ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด, set ๋ถ€๋ถ„์ด ์‹คํ–‰๋˜๋Š” ๊ฒƒ์ด๋‹ค.

    (memoList ์—ญ์‹œ ๊ฐฑ์‹ ๋˜์–ด์•ผ ํ•˜๋‚˜, ์ด๋Š” ์œ„ ์˜ˆ์‹œ์—์„œ ์ƒ๋žต๋œ useEffect๋ฅผ ํ†ตํ•ด ์ œ์–ดํ•˜์˜€๋‹ค.)

     

    ์ด ๋ฐฉ๋ฒ•์ด ์ •๋‹ต์€ ์•„๋‹ ๊ฒƒ์ด๋‹ค. ์–ด์จŒ๋“  ๋งค ๊ฐฑ์‹ ์ด ์„œ๋ฒ„์š”์ฒญ์„ ์ „์†กํ•˜๋ฏ€๋กœ, ๋ฐ์ดํ„ฐ๊ฐ€ ๋งŽ์•„์งˆ์ˆ˜๋ก ๋„คํŠธ์›Œํฌ ๋ถ€ํ•˜๊ฐ€ ์ƒ๊ธธ ๊ฒƒ์ด๋‹ค.

    ํ•˜์ง€๋งŒ, ๋…ธ์…˜๊ณผ ๊ฐ™์ด ๊ณตํ†ต ์‚ฌ์šฉ์ž์˜ ๊ฒฝ์šฐ๋ฅผ ๊ฐ€์ •ํ–ˆ์„ ๋•Œ, ์ตœ์‹ ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋Š” ์ง๊ด€์ ์ด๊ณ  ์•ˆ์ „ํ•œ ๋ฐฉ๋ฒ•์ด๋ผ๋Š” ์žฅ์ ๋„ ์žˆ์„ ๊ฒƒ์ด๋‹ค.

     

    ์ด์™ธ์—๋„, useRecoilCallback() ์ฝœ๋ฐฑ๊ณผ ์Šค๋ƒ…์ƒท, waitForAll() ๋™์‹œ์„ฑ helper ๋“ฑ ๋‹ค์–‘ํ•œ API๋“ค์„ ์ œ๊ณตํ•˜๊ณ  ์žˆ๋‹ค. (๊ณต์‹๋ฌธ์„œ ๋งํฌ)


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

    Recoil์— ๋Œ€ํ•œ ์งง์ง€๋งŒ ๋‚˜๋ฆ„ ํž˜๊ฒจ์› ๋˜(?) ํฌ์ŠคํŒ…์ด ๋งˆ๋ฌด๋ฆฌ๋˜์—ˆ๋‹ค! ์ด์ „, Redux ํ•™์Šต(ํฌ์ŠคํŒ…)์„ ์ƒ๊ฐํ–ˆ์„ ๋•Œ, ๋ฆฌ์†Œ์Šค๊ฐ€ ํ˜„์ €ํžˆ ์ ๊ฒŒ ๋“ค์—ˆ๋‹ค!
    Recoil์„ ์‚ฌ์šฉํ•œ ๊ฒฝํ—˜์€ ์ ์ง€๋งŒ ๋งค์šฐ ๋งŽ์€ ์žฅ์ ๊ณผ ๋งค๋ ฅ์„ ๋Š๊ผˆ๋‹ค. (์‹ฌํ”Œํ•œ ์„ธํŒ…, ๊ฐ„๋‹จํ•˜๊ณ  React ์นœํ™”์ ์ธ ๋ฌธ๋ฒ•, ์‰ฌ์šด ๋น„๋™๊ธฐ ์š”์ฒญ ๋“ฑ)

     

    ํ•˜์ง€๋งŒ, Recoil์„ ๊ณผ๊ฐํžˆ ๋„์ž…ํ•˜๊ธฐ์—๋Š” ์ผ๋ถ€ ๋‹จ์ ๋“ค์ด ์กด์žฌํ•œ๋‹ค.

    • ๊ฐœ๋ฐœ์ž ๋„๊ตฌ๊ฐ€ ์™„๋ฒฝํ•˜์ง€ ์•Š๋‹ค. ๋””๋ฒ„๊น… ๋ฐ ์Šค๋ƒ…์ƒท ํ…Œ์ŠคํŠธ๋ฅผ ํ•˜๋Š”๋ฐ ์žˆ์–ด ์‹ ๋ขฐ์„ฑ์ด ๋ถ€์กฑํ•˜๋‹ค.
    • ๋ชจ๋“  API๋“ค์ด ๋†’์€ ์‹ ๋ขฐ์„ฑ์„ ๋ณด์žฅํ•˜์ง€ ์•Š๋Š”๋‹ค. useGetRecoilValue, useRecoilRefresher ๋“ฑ์€ ๊ณต์‹๋ฌธ์„œ๋„ UNSTABLE๋กœ ๋ถ„๋ฅ˜.

     

    ๊ทธ๋ ‡์ง€๋งŒ ์œ„ ๋ถ€๋ถ„๋“ค์„ ์–ด๋Š์ •๋„ ๊ฐ์ˆ˜ํ•˜๋”๋ผ๋„ ๋‚˜๋Š” ์•ž์œผ๋กœ React ํ”„๋กœ์ ํŠธ์— Recoil์„ ์‚ฌ์šฉํ•  ๊ฒƒ ๊ฐ™๋‹ค.

    ๋ถ€์กฑํ•œ ๊ธ€์ด์ง€๋งŒ, ์ด๋ฅผ ์ฝ์€ ๋ถ„๋“ค์ด ๋‚ด๊ฐ€ ๋Š๋‚€ Recoil์˜ ๊ฐ•์ ๋“ค์„ ๊ณต๊ฐํ•˜์‹œ๊ธฐ๋ฅผ ๋ฐ”๋ผ๋ฉฐ ์ด๋งŒ ์ด๋ฒˆ ํฌ์ŠคํŒ…๋„ ๋งˆ๋ฌด๋ฆฌํ•˜๊ฒ ๋‹ค!

     

     

    ๐Ÿ“Ž ์ถœ์ฒ˜

    - [Recoil] Recoil ๊ณต์‹๋ฌธ์„œ : https://recoiljs.org/ko/docs/introduction/getting-started  

    - [Recoil] junoflog ๋‹˜์˜ ๋ธ”๋กœ๊ทธ : https://velog.io/@juno7803/Recoil-Recoil-200-%ED%99%9C%EC%9A%A9%ED%95%98%EA%B8%B0  

    - [Recoil] woolta ๋‹˜์˜ ๋ธ”๋กœ๊ทธ : https://blog.woolta.com/categories/1/posts/209

     

    ๋ฐ˜์‘ํ˜•
    ๋ฐ˜์‘ํ˜•

    ๋Œ“๊ธ€ 0

Designed by Tistory.