3 분 소요

recoil로 상태 관리하기

Main 화면 1

메인 화면은 이렇게 생겼다. 리스트에서 식당을 눌렀을 때 또는 지도에서 마커를 눌렀을 때 같은 식당인지 어떻게 확인할까?

1. props로 값 넘겨주기
Main에서 선택된 식당을 관리하고 List와 Map 컴포넌트에 props로 전달해주면 된다.

2. recoil로 전연 변수 관리하기
이 프로젝트에서는 recoil로 선택된 식당을 전역으로 관리했다. 아래에 그 방법을 설명해두었다.

설치

npm install recoil

RecoilRoot

App.js

import React from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import BrowserMain from "./pages/Main/BrowserMain";
import { RecoilRoot } from "recoil";

function App() {
  return (
    <RecoilRoot>
      <div className="App">
        <BrowserRouter>
          <div className="browser-main">
            <Routes>
              <Route path="/" element={<BrowserMain />} />
            </Routes>
          </div>
        </BrowserRouter>
      </div>
    </RecoilRoot>
  );
}

export default App;

recoil 상태를 사용하는 컴포넌트는 부모 트리 어딘가에 나타나는 RecoilRoot가 필요하다.
루트 컴포넌트가 RecoilRoot를 넣기에 가장 좋은 장소다.
코드짜는 곳의 가장 루트 컴포넌트인 App.js에 보통 추가한다.

Atom

  • Atom은 상태(state)의 일부를 나타낸다.
  • Atoms는 어떤 컴포넌트에서나 읽고 쓸 수 있다.
  • atom의 값을 읽는 컴포넌트들은 암묵적으로 atom을 구독한다. 그래서 atom에 어떤 변화가 있으면 그 atom을 구독하는 모든 컴포넌트가 재 렌더링 되는 결과가 발생할 것이다.

atom.js

import {
  RecoilRoot,
  atom,
  selector,
  useRecoilState,
  useRecoilValue,
} from "recoil";
import { recoilPersist } from "recoil-persist";

const { persistAtom } = recoilPersist();

export const selectStore = atom({
  key: "selectStore",
  default: null,
  effects_UNSTABLE: [persistAtom],
});

위에서 말한 선택된 식당을 atom에서 관리해준다.
다른 곳에서 사용하는 이름은 selectStore이다.

key는 유니크 이름이다.
default는 초깃값이다. useState()의 괄호 안에 들어가는 값
effects_UNSTABLE: [persistAtom]는 값을 localstorage에 보관해주어 새로고침 했을 때 값이 날라가는 걸 방지해준다.

Selector

  • Selector는 파생된 상태(derived state)의 일부를 나타낸다.
  • 파생된 상태는 상태의 변화다. 파생된 상태를 어떤 방법으로든 주어진 상태를 수정하는 순수 함수에 전달된 상태의 결과물로 생각할 수 있다.

프로젝트에서는 사용하진 않았지만 리코일의 주요 기능이므로 넣었다. Recoil은 변수만 관리하는게 아니라 함수처럼 사용할 수도 있다.

import { atom, selector } from "recoil";

export const wonState = atom({
  key: "wonState",
  default: 0,
});

export const dollarState = selector({
  key: "dollarState",
  get: ({ get }) => get(wonState) / 1400,
  set: ({ set }, dollar) => set(wonState, dollar * 1400),
});

wonState: 원화
dollarState: 달러

get: 원화를 가져와서(get) 1400으로 나누면 dollar가 된다.
set: dollar의 값에 1400을 곱하여 wonState 값을 갱신(set)한다.

컴포넌트 적용

CheckStore.js

import React from "react";
import { useRecoilState } from "recoil";
import { selectStore } from "../../../../Atom";

const CheckStore = (props) => {
  const [checkedStore, setcheckedStore] = useRecoilState(selectStore);

  //선택했을 때
  const clickStore = () => {
    setcheckedStore(store.id);
  };

  return (
    <div key={store.id} onClick={clickStore}>
      <!-- 리스트 한개 요소 -->
    </div>
  );
};

export default CheckStore;

Map.js

import { useState, useEffect, useRef } from "react";
import { useRecoilState } from "recoil";
import { selectStore } from "../../../Atom";

function Map() {
  const { naver } = window;
  const mapElement = useRef(null);
  const [store, setStore] = useRecoilState(selectStore);

  // 지도
  useEffect(() => {
    if (!mapElement.current || !naver) return;

    const location = new naver.maps.LatLng(centerLat, centerLong);

    const mapOptions: naver.maps.MapOptions = {
      //생략
    };

    const map = new naver.maps.Map(mapElement.current, mapOptions);

    //식당 위치
    for (let i = 0; i < stores.length; i++) {
      //지오코딩(주소->좌표)
      naver.maps.Service.geocode(
        { query: stores[i].address },
        function (status, response) {
          if (status === naver.maps.Service.Status.ERROR) {
            return alert("문제가 발생했습니다.");
          }

          //마커 찍기
          const otherMarkers = new naver.maps.Marker({
            //생략
          });

          //정보창
          const infowindow = new naver.maps.InfoWindow({
            //생략
          });

          //마커 클릭하면 정보창 뜨게함
          naver.maps.Event.addListener(otherMarkers, "click", function (e) {
            setStore(stores[i].id); //이 부분이 식당 선택
          });
          if (store === stores[i].id) {
            infowindow.open(map, otherMarkers);
          } else {
            infowindow.close();
          }
        }
      );
    }
  }, [store]);

  return <div ref={mapElement} />;
}

export default Map;

import recoil과 Atom.js에서 가져올 값

import { useRecoilState } from "recoil";
import { selectStore } from "../../../Atom";

문법

useState와 유사
useState를 useRecoilState로 바꾸고, 괄호 안의 값을 초깃값이 아니라 atom의 변수로 두면 된다.
ex) const [store, setStore] = useRecoilState(selectStore);

위 코드 설명

CheckStore에서는 const [checkedStore, setcheckedStore] = useRecoilState(selectStore);
Map에서는 const [store, setStore] = useRecoilState(selectStore);
useRecoilState() 괄호 안의 값이 같은 변수이기 때문에 같은 atom을 가리킨다.

결과보기

2

리스트에서 ‘소바쿠’를 선택했을 때도 지도에서 해당 식당의 정보창이 열리고,
지도에서 ‘요마시’를 선택했을 때도 리스트에서 해당 식당이 선택되어진 걸 볼 수 있다.

카테고리:

업데이트:

댓글남기기