본문 바로가기

react

리액트(react) - 리코일(Recoil) 과 Selector 사용 예제 학습

리액트에서 데이터는 단뱡항으로만 흐름

이런 단점을 극복하기 위해 만들어진 변수가 바로 '전역 상태 변수'

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 에서도 제거된 것을 확인할 수 있다.