ํ์ด์ค๋ถ์์ ๋ง๋ Recoil์ React์์ ์ํ๋ฅผ ๊ด๋ฆฌํ๋๋ฐ ์ฌ์ฉ๋๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋๋ค. React์์ ์ปดํฌ๋ํธ๋ ํ๋กญ์ค์ ์คํ ์ดํธ๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํ๊ณ ๊ด๋ฆฌํฉ๋๋ค. Recoil์ ์ด๋ฌํ ์ํ ๊ด๋ฆฌ๋ฅผ ๊ฐํธํ๊ฒ ๋ง๋ค์ด์ค๋๋ค.
recoil ์ํ๋ฅผ ์ฌ์ฉํ๋ ์ปดํฌ๋ํธ๋ ๋ถ๋ชจ ํธ๋ฆฌ ์ด๋๊ฐ์ ๋ํ๋๋ RecoilRoot ๊ฐ ํ์ํ๋ค. ๋ฃจํธ ์ปดํฌ๋ํธ๊ฐ RecoilRoot๋ฅผ ๋ฃ๊ธฐ์ ๊ฐ์ฅ ์ข์ ์ฅ์๋ค.
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๋ ํ์๋ ์ํ(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}</>;
}
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