리액트에서 데이터는 단뱡항으로만 흐름
이런 단점을 극복하기 위해 만들어진 변수가 바로 '전역 상태 변수'
State : 컴포넌트 내에서 변화하는 값, 변화 가능한 값
리코일 : 전역 상태 변수 라이브러리 ('페이스북' 에서 만듦)
전역 상태 변수 중 하나
예제 : 장바구니에 아이템을 담기 (장바구니에 담긴 데이터를 전역 상태 변수 'recoil state' 로 관리한다.)
여러 컴포넌트에서 공유하고 있는 상태를 리코일에서는 'atom' 이라고 한다.
[리코일 용어]
atom : 리코일 상태 최소 단위
- 예시
export const shoppingCart = atom ({
key : "shoppingCart" // 다른 atom 들과 독립적으로 구분되는 값이어야 함.
default : []
});
selector : atom 을 기반으로 별도 처리후 파생된 데이터
- 예시 : 아래 코드로 확인하기
리액트 프로젝트 작업 순
#1 npx create-react-app recoil_study
#2 npm i recoil
#3 npm install --save react-router-dom 로 라우터 설치
#3 index.js 에 root.render 부분 <RecoilRoot></RecoilRoot> 로 감싸주기
#4 App.js 에 route 작업
import logo from './logo.svg';
import './App.css';
import {BrowserRouter, Route, Routes} from "react-router-dom";
import Shop from "./layout/Shop";
import Cart from "./layout/Cart";
function App() {
return (
<BrowserRouter>
<Routes>
<Route exact path={'/'} element={<Shop/>}></Route>
<Route exact path={'/cart'} element={<Cart/>}></Route>
</Routes>
</BrowserRouter>
);
}
export default App;
만약 여기서 Header, Footer 컴포넌트를 만들어서 넣고 싶다면
<BrowserRouter> 와 <Routes> 사이에 위치시키면 된다.
<BrowserRouter>
<Header /> {/* 헤더 컴포넌트 */}
<Routes>
<Route exact path="/" element={<Shop />} />
<Route exact path="/cart" element={<Cart />} />
</Routes>
<Footer /> {/* 풋터 컴포넌트 */}
</BrowserRouter>
#5 패키지 경로 아래 이미지처럼 만들어주기
- layout
- recoil > atom (atom : 리코일 스테이트의 가장 작은 단위)
# 위 경로에서 존재하는 컴포넌트 파일 생성
#6 ShoppingCartAtom.jsx (장바구니 리코일 state)
- atom → 리코일 state 선언 및 초기화
- selector → 리코일 state 값을 원본 데이터에 지장없이 가공해서 사용하고 싶을 때 사용
꼭 selector를 사용해야 하는 것은 아니다. 가공할 컴포넌트에서 리코일 state를 불러와 가공해서 처리해도 된다.
import {atom, selector} from "recoil";
export const ShoppingCartAtom = atom({
key : 'ShoppingCartAtom',
default : []
});
export const CartItemTotalCount = selector({
key : "CartTotalCount",
get : ({get}) => {
const CartItems = get(ShoppingCartAtom); // 여기서 리코일 value 가지고 온다.
return CartItems.length; // 장바구니에 담긴 총 상품 개수 반환
// 여기서 return CartItems.filter() 이런 함수 사용하더라도 원본 데이터인 recoilValue 에는 영향이 가지 않기때문에
// selector 를 이용하면 recoilValue 가공 후 출력을 보다 안전하게 할 수 있다.
}
})
# Shop.js
- "장바구니" 라는 리코일 state에 물건을 담을 페이지 렌더링에 사용되는 컴포넌트
import React, {useEffect} from "react";
import {useRecoilState, useRecoilValue, useSetRecoilState} from "recoil";
import {ShoppingCartAtom} from "../recoil/atom/ShoppingCartAtom";
import {Link} from "react-router-dom";
const Shop = () => {
// 장바구니 atom
// #1 state, setState 생성하기
const [shoppingCart, setShoppingCart] = useRecoilState(ShoppingCartAtom);
// #2 atom 의 value 만 가지고 오기
const cartAtomValue = useRecoilValue(ShoppingCartAtom);
// #3 set으로 atom value 변동시키는 setState만 가지고 오기
const setCartAtomValue = useSetRecoilState(ShoppingCartAtom);
// ★ 쉽게 생각하면 #2 + #3 한게 #1 의 방법이라고 생각하면 된다.
// temp 쇼핑아이템들
const itemList = [
{
id: 1,
name: '책',
price: 10000
},
{
id: 2,
name: '가방',
price: 20000
},
{
id: 3,
name: '펜',
price: 5000
}
]
useEffect(() => {
console.log(shoppingCart);
}, [shoppingCart]);
const addCartItem = (item) => {
setShoppingCart((prev) => [...prev, item]); // 여기서 prev.push(값) 을 사용하지 않는다 -> .push 는 원본 배열에 변경이 생기기 때문에 위험해서
alert('상품이 장바구니에 담겼습니다.');
}
const itemListRender = () => {
return itemList?.map((item) => {
return (
<div style={{border: '0.5px solid black'}}>
<p>상품명 : {item.name}</p>
<p>가격 : {item.price} 원</p>
<p>
<button type='button' id='addCartItemBtn' onClick={() => {
addCartItem(item);
}}>장바구니 담기</button>
</p>
</div>
)
})
}
return (
<>
<Link to={"/cart"}>장바구니 가기</Link>
<h1>쇼핑몰</h1>
{itemListRender()}
</>
);
}
export default Shop;
# Cart.js
- "장바구니" 라는 리코일 state에 담은 물건 확인 페이지 렌더링에 사용되는 컴포넌트
import React, {useEffect} from "react";
import {useRecoilState, useRecoilValue} from "recoil";
import {CartItemTotalCount, ShoppingCartAtom} from "../recoil/atom/ShoppingCartAtom";
import {Link} from "react-router-dom";
const Cart = () => {
const [shoppingCart, setShoppingCart] = useRecoilState(ShoppingCartAtom);
const totalItemCount = useRecoilValue(CartItemTotalCount);
const removeItem = (item) => {
setShoppingCart((prev) => prev.filter((targetItem) => targetItem.id !== item.id)); // prev : 이전에 담겨있던 값
}
useEffect(() => {
console.log(shoppingCart);
}, [shoppingCart]);
return (
<>
<Link to={"/"}>쇼핑몰 가기</Link>
<h1>장바구니</h1>
{
shoppingCart.length !== 0 ?
shoppingCart.map((item) => {
return (
<div>
<p>{item.name}</p>
<p>{item.price}</p>
<p>
<button onClick={() => removeItem(item)}>삭제</button>
</p>
</div>
)
})
: <p>장바구니에 담긴 상품이 없습니다.</p>
}
<p>담긴 상품 개수 : {totalItemCount}</p>
</>
);
}
export default Cart;
# 테스트
#t1 npm start 로 어플리케이션 실행
- index 페이지는 Shop 컴포넌트로 라우터에 정의해줬다.
# 장바구니 담기 버튼 클릭 (장바구니_리코일 state에 상품 넣기)
# 책 2회, 펜 1회, 가방 1회 클릭했을 때 결과
- Shop.js에서 리코을 state 값이 변경되면 console.log 로 현재 리코일 state 값 출력시키도록
미리 작성해두었기에 콘솔 로그에서 state 에 제대로 값이 추가되는지 확인할 수 있다
# 상단 "장바구니 가기" 클릭해서 장바구니로 이동 (Cart.js 컴포넌트)
- 리코일 state 값에 담긴 상품 리스트를 확인할 수 있다.
# 삭제 버튼 클릭 (리코일 state 값에서 특정 상품의 아이템 객체를 제거하기)
- 상품 '가방' 의 삭제 버튼을 클릭했더니
- 리코일 state 에서도 제거된 것을 확인할 수 있다.
'react' 카테고리의 다른 글
useQuery(개인 참고용) (0) | 2025.01.13 |
---|---|
리액트(react) - 자식 컴포넌트에서 부모 컴포넌트 state 변경 (2) | 2024.08.20 |
redux, react-redux (리덕스 실습) : 다크모드 / 일반모드 (0) | 2024.08.10 |
[토이프로젝트-프론트(react)] 프로젝트명 : 커뮤니티 - 업데이트 중 (3) | 2024.07.21 |
node 모듈 재 설치 하는 법 (0) | 2024.07.21 |