Recoil ๐Ÿ–

c_yjยท2023๋…„ 5์›” 3์ผ
0

Recoil ์ด๋ž€?

ํŽ˜์ด์Šค๋ถ์—์„œ ๋งŒ๋“  Recoil์€ React์—์„œ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š”๋ฐ ์‚ฌ์šฉ๋˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค. React์—์„œ ์ปดํฌ๋„ŒํŠธ๋Š” ํ”„๋กญ์Šค์™€ ์Šคํ…Œ์ดํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•˜๊ณ  ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค. Recoil์€ ์ด๋Ÿฌํ•œ ์ƒํƒœ ๊ด€๋ฆฌ๋ฅผ ๊ฐ„ํŽธํ•˜๊ฒŒ ๋งŒ๋“ค์–ด์ค๋‹ˆ๋‹ค.

Recoil ํ•ต์‹ฌ โœ

RecoilRoot

recoil ์ƒํƒœ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ๋Š” ๋ถ€๋ชจ ํŠธ๋ฆฌ ์–ด๋”˜๊ฐ€์— ๋‚˜ํƒ€๋‚˜๋Š” RecoilRoot ๊ฐ€ ํ•„์š”ํ•˜๋‹ค. ๋ฃจํŠธ ์ปดํฌ๋„ŒํŠธ๊ฐ€ RecoilRoot๋ฅผ ๋„ฃ๊ธฐ์— ๊ฐ€์žฅ ์ข‹์€ ์žฅ์†Œ๋‹ค.

Atom

Atom์€ ์ƒํƒœ(state)์˜ ์ผ๋ถ€๋ฅผ ๋‚˜ํƒ€๋‚ธ๋‹ค. Atoms๋Š” ์–ด๋–ค ์ปดํฌ๋„ŒํŠธ์—์„œ๋‚˜ ์ฝ๊ณ  ์“ธ ์ˆ˜ ์žˆ๋‹ค. atom์˜ ๊ฐ’์„ ์ฝ๋Š” ์ปดํฌ๋„ŒํŠธ๋“ค์€ ์•”๋ฌต์ ์œผ๋กœ atom์„ ๊ตฌ๋…ํ•œ๋‹ค. ๊ทธ๋ž˜์„œ atom์— ์–ด๋–ค ๋ณ€ํ™”๊ฐ€ ์žˆ์œผ๋ฉด ๊ทธ atom์„ ๊ตฌ๋…ํ•˜๋Š” ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์žฌ ๋ Œ๋”๋ง ๋˜๋Š” ๊ฒฐ๊ณผ๊ฐ€ ๋ฐœ์ƒํ•  ๊ฒƒ์ด๋‹ค.

const textState = atom({
  key: 'textState', // ์œ ๋‹ˆํฌํ•œ ๊ฐ’์ด ๋“ค์–ด๊ฐ„๋‹ค
  default: '', // ๊ธฐ๋ณธ ๊ฐ’์ด ๋“ค์–ด๊ฐ„๋‹ค.
});

์ปดํฌ๋„ŒํŠธ๊ฐ€ atom์„ ์ฝ๊ณ  ์“ฐ๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” useState()์ฒ˜๋Ÿผ useRecoilState()๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค

function CharacterCounter() {
  return (
    <div>
      <TextInput />
      <CharacterCount />
    </div>
  );
}

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

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

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

Selector

Selector๋Š” ํŒŒ์ƒ๋œ ์ƒํƒœ(derived state)์˜ ์ผ๋ถ€๋ฅผ ๋‚˜ํƒ€๋‚ธ๋‹ค. ํŒŒ์ƒ๋œ ์ƒํƒœ๋Š” ์ƒํƒœ์˜ ๋ณ€ํ™”๋‹ค. ํŒŒ์ƒ๋œ ์ƒํƒœ๋ฅผ ์–ด๋–ค ๋ฐฉ๋ฒ•์œผ๋กœ๋“  ์ฃผ์–ด์ง„ ์ƒํƒœ๋ฅผ ์ˆ˜์ •ํ•˜๋Š” ์ˆœ์ˆ˜ ํ•จ์ˆ˜์— ์ „๋‹ฌ๋œ ์ƒํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ๋กœ ์ƒ๊ฐํ•  ์ˆ˜ ์žˆ๋‹ค.

const charCountState = selector({
  key: 'charCountState', // ์œ ๋‹ˆํฌํ•œ ๊ฐ’
  get: ({get}) => {
    const text = get(textState);

    return text.length;
  },
});

useRecoilValue() ํ›…์„ ์‚ฌ์šฉํ•ด์„œ charCountState ๊ฐ’์„ ์ฝ์„ ์ˆ˜ ์žˆ๋‹ค.

function CharacterCount() {
  const count = useRecoilValue(charCountState);

  return <>Character Count: {count}</>;
}

Recoil๋กœ ์žฅ๋ฐ”๊ตฌ๋‹ˆ ๋งŒ๋“ค์–ด๋ณด๊ธฐ

RecoilRoot๋กœ ์ผ๋‹จ ๊ฐ์‹ธ์ฃผ์ž

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import './index.css'
import { BrowserRouter } from 'react-router-dom'
import { RecoilRoot } from 'recoil'

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <RecoilRoot>
      <BrowserRouter>
      <App />
      </BrowserRouter>
    </RecoilRoot>
  </React.StrictMode>,
)

Cart์˜ ๊ฐ’์„ ์ €์žฅํ•จ atom์„ ๋งŒ๋“ ๋‹ค

// Cart.js
import { atom } from "recoil";

export const Cart = atom({
  key: "Cart",
  default: [],
});

์นดํŠธ ์•„์ดํ…œ ์ปดํฌ๋„ŒํŠธ๋กœ ๊ฐ€์„œ atom์„ค์ • ํ•œ๊ฑฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜จ๋‹ค


import React from 'react';
import { useRecoilState } from "recoil";
import { Cart } from "../recoil/Cart";

const CartItem = ({ data }) => {
  const { id, title, description, price } = data;
  const [cartItem, setCartItem] = useRecoilState(Cart);
  
  const isAlreadyInCart = cartItem.filter((e) => e.id === id).length;

  const AddToCart = () => {
    if (!isAlreadyInCart) {
      setCartItem((prev) => [...prev, data]);
    }
  };

  return (
    <div className="rounded-md shadow-md p-4 my-4 flex flex-col items-center">
      <div className="mb-4 h-40 w-full bg-gray-100 flex items-center justify-center rounded-md">
        <span className="text-gray-700 text-2xl font-bold">์ด๋ฏธ์ง€ ์ค€๋น„ ์ค‘์ž…๋‹ˆ๋‹ค.</span>
      </div>
      <div className="flex-grow w-full">
        <div className="font-bold text-lg mb-2">{title}</div>
        <div className="text-gray-600 text-base mb-2">{description}</div>
        <div className="text-gray-900 font-bold text-lg mb-2">{price}์›</div>
        <div className='flex justify-center mt-2'>
          <button 
          onClick={AddToCart}
          disabled={isAlreadyInCart}
          className="bg-zinc-600  hover:bg-zinc-900 text-white font-bold py-2 px-4 rounded ">
            {isAlreadyInCart
            ? `์žฅ๋ฐ”๊ตฌ๋‹ˆ์— ์ถ”๊ฐ€๋ฌ์Šต๋‹ˆ๋‹ค`
            : "์žฅ๋ฐ”๊ตฌ๋‹ˆ์— ์ถ”๊ฐ€"}
          </button>
        </div>
      </div>
    </div>
  );
}

export default CartItem;

console.log์„ ์ฐ์–ด๋ณด๋‹ˆ๊นŒ ์ž˜ ๋‚˜์˜จ๋‹ค

Recoil์—์„œ ์ƒํƒœ ์—…๋ฐ์ดํŠธ๋ฅผ ํ•  ๋•Œ๋Š” push()์™€ ๊ฐ™์€ ๋ถˆ๋ณ€์„ฑ์„ ๊นจ๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค. Recoil์ด ์ž์ฒด์ ์œผ๋กœ ์ƒํƒœ ์—…๋ฐ์ดํŠธ๋ฅผ ์ถ”์ ํ•˜์ง€ ๋ชปํ•˜๊ฒŒ ๋˜๋ฉฐ, ์ด๋กœ ์ธํ•ด ๋ Œ๋”๋ง์ด ๋˜์ง€ ์•Š๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. โŒ

๋‹ค์‹œ recoil Cart.js๋กœ ๊ฐ€์„œ ์ด ์นดํŠธ ์ˆ˜๋Ÿ‰์„ ๋‚˜ํƒ€๋‚ด๋Š” selector๋ฅผ ๋งŒ๋“ค์–ด์ค€๋‹ค

import { atom, selector } from "recoil";

export const Cart = atom({
  key: "Cart",
  default: [],
});

export const QuantitySelector = selector({
  key: "QuantitySelector",
  get: ({ get }) => {
    const CurrentItem = get(Cart);
    return CurrentItem.length.toLocaleString();
  },
});

Navbar ์ปดํฌ๋„ŒํŠธ๋กœ ๊ฐ€์„œ 0๋ณด๋‹ค ํด๋–„๋งŒ ๊ฐฏ์ˆ˜๊ฐ€ ๋‚˜์˜ค๊ฒŒ ์ ์šฉ

import React from 'react'
import { Link } from 'react-router-dom'
import { QuantitySelector } from '../recoil/Cart'
import { useRecoilValue } from 'recoil';

const Navbar = () => {
  const TotalQuantity = useRecoilValue(QuantitySelector);
  return (
    <div className='w-full h-14 flex justify-around items-center border bg-zinc-100'>
      <Link className='text-2xl font-bold' to="/">Home</Link>
      <Link 
      className='flex gap-2 relative'
      to="/cart">
        <p className='z-10 font-bold'>My Cart</p> 
        {TotalQuantity > 0
        ?
        <p className='absolute top-[-12px] right-[-16px] rounded-full bg-red-500 w-6 h-6 flex justify-center items-center'>
          {TotalQuantity}
        </p> :
        null
        }
      </Link>
    </div>
  )
}

export default Navbar


์ด๋Ÿฐ์‹์œผ๋กœ ์นดํŠธ์˜ ๊ฐฏ์ˆ˜๊ฐ€ ์ž˜ ๋‚˜์˜จ๋‹ค.
์ด์ œ Cartpage.js ์ปดํฌ๋„ŒํŠธ๋กœ ๊ฐ€์„œ ์นดํŠธ ํŽ˜์ด์ง€๋ฅผ ๊พธ๋ฉฐ๋ณด์ž
์ผ๋‹จ Recoil Cart.js๋กœ ๊ฐ€์„œ ์นดํŠธ ์ด ๊ฐ€๊ฒฉ selector๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ์ž

import { atom, selector } from "recoil";

export const Cart = atom({
  key: "Cart",
  default: [],
});

export const QuantitySelector = selector({
  key: "QuantitySelector",
  get: ({ get }) => {
    const CurrentItem = get(Cart);
    return CurrentItem.length.toLocaleString();
  },
});

export const TotalPriceSelector = selector({
  key: "TotlaPriceSelector",
  get: ({ get }) => {
    const CurrentItem = get(Cart);
    return CurrentItem.reduce(
      (acc, cur) => acc + cur.price,
      0
    ).toLocaleString();
  },
});

Cartpage.js

import { useState } from "react";
import { useRecoilValue, useSetRecoilState } from "recoil";
import {
  Cart,
  QuantitySelector,
  TotalPriceSelector,
} from "../recoil/Cart";
const Cartpage = () => {
  const cartItem = useRecoilValue(Cart); // Cart๋“ค์„ ๋‹ด์€ ๋ฐฐ์—ด
  const TotalQuantity = useRecoilValue(QuantitySelector); // Cart์˜ ์ด ๊ฐฏ์ˆ˜
  const TotalPrice = useRecoilValue(TotalPriceSelector); // Cart์˜ ์ด ๊ฐ€๊ฒฉ
  const setCartItem = useSetRecoilState(Cart); // Cart๋ฅผ ์‚ญ์ œํ•˜๊ธฐ ์œ„ํ•œ ํ›… ํ•จ์ˆ˜

  const removeFromCart = (id) => {
    setCartItem((prev) => prev.filter((e) => e.id !== id));
  };

  return (
    <div className="container mx-auto p-4 w-full md:w-4/5">
      <h1 className="text-2xl font-bold mb-4">My Cart</h1>
      {cartItem.length === 0 ? <h2 className="text-center mt-20 font-bold text-2xl">Cart๊ฐ€ ๋น„์–ด์žˆ์Šต๋‹ˆ๋‹ค</h2>
      :
      <>
      {cartItem.map((item) => (
        <div
          key={item.id}
          className="flex justify-between items-center border-b border-gray-300 py-4"
        >
          <div className="flex items-center">
            <img
              className="w-20 h-20 object-cover mr-4"
              src="https://via.placeholder.com/150"
              alt={item.title}
            />
            <div>
              <h2 className="text-lg font-bold">{item.title}</h2>
              <p className="text-sm text-gray-600">{item.price} ์›</p>
              <p className="text-sm text-gray-600">{item.description}</p>
            </div>
          </div>
          <button
            className="text-sm text-red-500 font-bold"
            onClick={() => removeFromCart(item.id)}
          >
            ์‚ญ์ œ
          </button>
        </div>
      ))}
      <div className="flex justify-end items-center mt-4">
        <p className="text-gray-600 font-bold text-lg">
          ์ด {TotalQuantity}๊ฐœ ์ƒํ’ˆ
        </p>
        <p className="text-gray-900 font-bold text-2xl ml-4">
          ์ดํ•ฉ๊ณ„: {TotalPrice} ์›
        </p>
      </div>
      </>
      }
    </div>
  );
};

export default Cartpage;

๊ฒฐ๊ณผ๋ฌผ

์ฐธ๊ณ 
https://recoiljs.org/ko/
https://wit.nts-corp.com/2022/10/13/6586
https://www.youtube.com/watch?v=k5DLjVmMC2w

profile
FrontEnd Developer

0๊ฐœ์˜ ๋Œ“๊ธ€

๊ด€๋ จ ์ฑ„์šฉ ์ •๋ณด