Recoil로 MVVM 사용하기 - (2) Recoil

towel1017·2021년 6월 3일
8

MVVM

목록 보기
2/2

저번엔 MVVM에 관련한 글을 썼는데, 이번엔 MVVM에 Recoil을 어떻게 무엇을 이용해서 MVVM을 구현했는지 말해보려한다.

🤦‍♂️ Recoil.. 또 새로운거야?

Recoil은 물론 나온지 얼마 되지 않았고, 점점 발전해 나가고 있는 상태관리 라이브러리이다.
상당히 경험해보니 쉽고, 접근하기도 어렵지 않아서 지금도 작은 프로젝트에서 자주 사용하고 있다.
Redux, Redux-saga 등을 이미 사용하고 있는 프로젝트에는 더 Recoil이 발전하면 사용하길 권한다. 그리고 새로 입문하기에도 좋은 라이브러리인 것 같다.

여기서는 Recoil의 기초보단 MVVM에 관련해서 어떻게 썼는지를 집중적으로 작성할 예정이다.

📚 구조

Atom + Selector 의 구조이고, atom은 state(상태)를 뜻합니다.
atom이 변경되면, 구독되어 있는 컴포넌트로 변경사항이 전달된다.

🧮 Atom

MVVM에서는 atom이 Model의 데이터의 역할을 한다.

interface DeliveryMethodItemType {
  id: number;
  name: string;
  delivery_price: number;
}

export const DeliveryMethodAtom = atom<DeliveryMethodItemType[]>({
    key: "DeliveryMethodState",
    default: [
        ...deliveryTypes
    ]
})

interface로 타입을 선언해주고, key로 다른 atom과 구분한다.
default에 atom의 기본값을 선언한다.
배열이나, 객체, 단일 값 모두 default로 선언 가능하다.

🪓 Selector

MVVM에서는 selector가 Model의 메서드 역할을 한다.

key : 다른 selector들과의 구분되는 id 역할을 한다.
get : 다른 atom들을 받아오거나, 있는 값으로 연산한 값을 return 한다.

// get만 존재하는 selector
export const PriceStatsSelector = selector({
    key: "PriceStats/Read",
    get: ({ get }) => {
        const productList = get(ProductListAtom)
        const allDeliveryPrice = get(SelectedMethodAtom).delivery_price
        const checkedList = productList.filter(item => item.checked)
        let allProductPrice = 0 
        let allAmount = 0
        checkedList.map(({ product_price, current_count }) => {
            allAmount += current_count
            allProductPrice += product_price * current_count
            return {allAmount, allProductPrice};
        })
        const allPrice = allProductPrice + allDeliveryPrice;
        return {allProductPrice, allAmount, allDeliveryPrice, allPrice }
    }
})

값을 읽는 코드는 이런식으로 작성한다.

const price = useRecoilValue(PriceStatsSelector)
const {allProductPrice, allAmount, allDeliveryPrice, allPrice } = price

또 다른 경우로 get, set모두가 있는 경우이다.

//get, set 모두가 있는 경우
export const AllCheckProductItemsSelector = selector({
    key: "ProductList/Item/AllCheck",
    get: ({ get }) => {
        const productList = get(ProductListAtom)
        if (productList) return false;
        return get(ProductListAtom).every(item => item.checked)
    },
    set: ({ set }) => {
        set(ProductListAtom, prevState => prevState.map(item => {
                const currentItem = { ...item };
                currentItem.checked = !currentItem.checked;
                return currentItem;
            })
        )
    }
})

set은 사용할 때 주의할 점이 있습니다.

  • set을 할 때는 자기 자신의 selector를 set하려고 하면 loop가 돌기 때문에 자기자신을 set해선 안된다.
  • selector는 read-only한 값을 가지기 때문에, writeable한 값을 가지고 set을 해야합니다.
  • 위 코드로 설명하자면, prevState.map 에서 currentItem 이라고 아이템을 복사한 후에 수정하고, 수정된 아이템을 return 하는 이유는 prevState.map(item) 의 item은 readonly한 value 이므로, 한번 복사해서, writeable한 value로 만들어서 return 하는 것입니다.

이런 식으로 state(상태)를 atom으로, 각 topic의 CRUD를 selector로 구현하여서 Model을 구성합니다.

불러오는 코드는 이렇게 작성합니다.

//selector - get만 있는 경우
  const price = useRecoilValue(PriceStatsSelector);
//selector 의 set만 불러오는 경우
  const [, toggleChecked] = useRecoilState(CheckProductItemSelector);
//selector의 get, set 모두 필요한 경우
  const [allChecked, allCheckedProductItem] = useRecoilState(
    AllCheckProductItemsSelector
  );
  const [, increaseAmount] = useRecoilState(
    IncreaseProductItemAmount
  );
  const [, decreaseAmount] = useRecoilState(
    DecreaseProductItemAmount
  );
  const [, handleDeleteProductItem] =
    useRecoilState(DeleteProductItem);

🤦‍♂️후기

recoil은 상당히 새로 배우기도 쉽고, 바로바로 적용시키는데도 그렇게 많은 리스크가 있지않아서, 한번쯤은 해볼만한 라이브러리인 것 같다.
selector 의 set에서 writeable, readonly 에서 개념에도 애를 먹고, 그걸 토대로 적용하는 데에도 애를 좀 먹었지만 막상 MVVM의 model로 적용하고 나니 MVVM 패턴에도 쓸만한 상태관리인 것 같아 뿌듯했다.

1개의 댓글

comment-user-thumbnail
2021년 8월 4일

setter만 가져올 때 useSetRecoliState를 쓰면 되겠군요!

답글 달기