React

[React] 리액트 context API를 recoil로 바꾸기 / 상태 관리 도구 변경하기

solfa 2024. 2. 18. 21:47

이어지는글

https://5ffthewall.tistory.com/73

 

[React] 리액트 recoil 예제 만들어 사용해보기

이어지는 글 https://5ffthewall.tistory.com/72 [React] recoil로 전역 상태 관리하기 https://5ffthewall.tistory.com/71 [React] 리액트 context API의 리렌더링 방지를 통한 성능 최적화 하기 / useMemo 사용하기 https://5ffthewa

5ffthewall.tistory.com

recoil 예제까지 만들면서 recoil에 대한 공부를 했다.

이젠 context API로 상태 관리를 했던 내 코드를 recoil로 변경해 볼 것이다.


 

recoil을 선택한 이유

recoil은 클라이언트의 상태를 관리하고, react-query는 서버의 데이터를 관리하는 라이브러리!

따라서 context API -> recoil로 바꾸는 게 좋아보임!

또한 recoil은 실제로도 내부적으로 context를 사용한다

https://github.com/facebookexperimental/Recoil/blob/main/packages/recoil/core/Recoil_RecoilRoot.js

 

코드 리팩토링 하기

 

현재 페이지 구조는 다음과 같다.

 

여기서 바꿔야 할 것은

  1. provider대용으로 쓴 QuizContext.js를 atom으로 변경한다.
  2. Quiz 페이지들에서 useRecoilState로 atom의 값을 업데이트 한다.
  3. result 페이지들에서 useRecoilValue로 atom의 값을 가져온다.

+ selector는 굳이 쓰지 않았다!

 

1. Recoil 설치

yarn add recoil

Recoil 라이브러리를 프로젝트에 설치한다.

 

2. 기존의 QuizContext.js를 atom으로 변경

기존의 Context API를 사용하여 상태를 관리하는 부분을 Recoil의 atom으로 대체한다.

기존의 QuizContext.js 대신 atom.js를 저장해준다.

 

기존 provider 코드

import React, { createContext, useContext, useState } from 'react';

const QuizContext = createContext();

export const useQuizContext = () => useContext(QuizContext);

export const QuizProvider = ({ children }) => {
  const [selectedItem, setSelectedItem] = useState({
    quiz1: '',
    quiz2: '',
    quiz3: '',
    quiz4: '',
  });

  const setQuizItem = (quizId, item) => {
    setSelectedItem(prevItems => ({
      ...prevItems,
      [quizId]: item
    }));
  };

  return (
    <QuizContext.Provider value={{ selectedItem, setQuizItem }}>
      {children}
    </QuizContext.Provider>
  );
};

 

 

 

추가한 atom.js 파일 (경로 'Pages/recoil/atom')

import { atom, useRecoilState } from 'recoil';

export const quizState = atom({
  key: 'quizState',
  default: {
    quiz1: '',
    quiz2: '',
    quiz3: '',
    quiz4: '',
  },
});

 

 

 

3. 퀴즈 페이지 코드 수정 (useRecoilState 사용하기)

useQuizContext를 사용하여 상태를 관리하던 것을 Recoil의 useRecoilState를 사용하는 것으로 수정한다.

setQuizItem 함수를 useRecoilState 함수로 변경한다.

 

기존 코드

import React from 'react';
import { useNavigate } from 'react-router-dom';
import { useQuizContext } from './QuizContext';
import { Wrapper, Square, Count, Progress, QText, SelectButton } from 'Styles/styles';
import square from 'Images/main/Rectangle 4.svg';

export default function Quiz1(){
  const navigate = useNavigate();
  const { setQuizItem } = useQuizContext();

  const goQuiz = (selectedItem) => {
    setQuizItem('quiz1', selectedItem); 
    navigate("/quiz2");
  };

  return (
    <Wrapper>
      <Square src={square} alt='' />
      <Count>1/10</Count>
      <Progress value="10" min="0" max="100" />
      <QText>Q.1<br/>당신의 솔미 모에화는</QText>
      <SelectButton onClick={() => goQuiz('햄스터')}>햄스터 🐹</SelectButton>
      <SelectButton onClick={() => goQuiz('다람쥐')}>다람쥐 🐿️</SelectButton>
      <SelectButton onClick={() => goQuiz('고양이')}>고양이 🐈</SelectButton>
      <SelectButton onClick={() => goQuiz('강아지')}>강아지 🐶</SelectButton>
    </Wrapper>
  );
}

 

수정한 코드

import React from 'react';
import { useNavigate } from 'react-router-dom';
import { useRecoilState } from 'recoil'; // Recoil의 상태 훅 import
import { quizState } from 'Pages/recoil/atom'; // Recoil의 상태 import 경로 체크!!
import { Wrapper, Square, Count, Progress, QText, SelectButton } from 'Styles/styles';
import square from 'Images/main/Rectangle 4.svg';

export default function Quiz1(){
  const navigate = useNavigate();
  const [selectedItem, setSelectedItem] = useRecoilState(quizState); 
  // Recoil의 상태 훅 사용
  // useRecoilState로 값 업데이트!

  const goQuiz = (selectedItem) => {
    setSelectedItem(prevItems => ({
      ...prevItems,
      quiz1: selectedItem
    })); 
    // Recoil의 상태 업데이트 함수 사용
    navigate("/quiz2");
  };

  return (
    <Wrapper>
      <Square src={square} alt='' />
      <Count>1/10</Count>
      <Progress value="10" min="0" max="100" />
      <QText>Q.1<br/>당신의 솔미 모에화는</QText>
      <SelectButton onClick={() => goQuiz('햄스터')}>햄스터 🐹</SelectButton>
      <SelectButton onClick={() => goQuiz('다람쥐')}>다람쥐 🐿️</SelectButton>
      <SelectButton onClick={() => goQuiz('고양이')}>고양이 🐈</SelectButton>
      <SelectButton onClick={() => goQuiz('강아지')}>강아지 🐶</SelectButton>
    </Wrapper>
  );
}

 

4. 결과 반환 페이지 코드 수정 (useRecoilValue 사용하기)

 

기존 코드

import { AllResultImg, AllResultText, AllResultText2, QqText, Wrapper } from "Styles/styles";
import Result1 from 'Images/result/Rectangle 21.svg'
import Result2 from 'Images/result/Rectangle 22.svg'
import Result3 from 'Images/result/Rectangle 23.svg'
import Result4 from 'Images/result/Rectangle 24.svg'
import { useNavigate } from "react-router-dom";
import { useQuizContext } from "Pages/quiz/QuizContext";

export default function AllResult() {
    const {setQuizItem} = useQuizContext();
    
    const navigate = useNavigate();

    const goResult = (selectedItem) => {
        setQuizItem('result', selectedItem); 
        navigate("/result");
    }

    return(
        <Wrapper>
            <QqText style={{marginTop:'10%', fontSize:'25px'}}>모든 결과 모아보기</QqText>
            <AllResultImg onClick={() => goResult('공주')} src={Result1}></AllResultImg>
            <AllResultText style={{top:'15%'}}>ㅎㅎ <br/> 내 쌩얼이 싫어?</AllResultText>
            <AllResultText2 style={{top:'21%'}}>“공주 솔미”</AllResultText2>
            <AllResultImg onClick={() => goResult('애기')} src={Result2}></AllResultImg>
            <AllResultText style={{top:'37%', right:'20%'}} >응애</AllResultText>
            <AllResultText2 style={{top:'40%'}}>“애기 솔미”</AllResultText2>
            <AllResultImg onClick={() => goResult('여친')} src={Result3}></AllResultImg>
            <AllResultText style={{top:'58%', left:'43%' }}>2024 <br/> 사귀고 싶은 여자 1위</AllResultText>
            <AllResultText2 style={{top:'64%'}}>“여친 솔미”</AllResultText2>
            <AllResultImg onClick={() => goResult('솔냥이')} src={Result4}></AllResultImg>
            <AllResultText style={{top:'82%'}}>초치는 말 장인</AllResultText>
            <AllResultText2 style={{top:'85%', right:'20%'}}>“솔냥이”</AllResultText2>
        </Wrapper>
    );
}

 

수정한 코드

import React from 'react';
import { Wrapper, AllResultImg, AllResultText, AllResultText2, QqText } from 'Styles/styles';
import { useRecoilValue } from 'recoil'; // Recoil의 값 읽어오는 훅 import
import Result1 from 'Images/result/Rectangle 21.svg';
import Result2 from 'Images/result/Rectangle 22.svg';
import Result3 from 'Images/result/Rectangle 23.svg';
import Result4 from 'Images/result/Rectangle 24.svg';
import { quizResultState } from './RecoilState'; // Recoil 상태 import

export default function AllResult() {
    const quizResult = useRecoilValue(quizResultState); // Recoil 상태 값 읽어오기
    
    return (
        <Wrapper>
            <QqText style={{marginTop:'10%', fontSize:'25px'}}>모든 결과 모아보기</QqText>
            <AllResultImg src={Result1} onClick={() => goResult('공주')}></AllResultImg>
            <AllResultText style={{top:'15%'}}>ㅎㅎ <br/> 내 쌩얼이 싫어?</AllResultText>
            <AllResultText2 style={{top:'21%'}}>“{quizResult['공주']} 솔미”</AllResultText2>
            <AllResultImg src={Result2} onClick={() => goResult('애기')}></AllResultImg>
            <AllResultText style={{top:'37%', right:'20%'}} >응애</AllResultText>
            <AllResultText2 style={{top:'40%'}}>“{quizResult['애기']} 솔미”</AllResultText2>
            <AllResultImg src={Result3} onClick={() => goResult('여친')}></AllResultImg>
            <AllResultText style={{top:'58%', left:'43%' }}>2024 <br/> 사귀고 싶은 여자 1위</AllResultText>
            <AllResultText2 style={{top:'64%'}}>“{quizResult['여친']} 솔미”</AllResultText2>
            <AllResultImg src={Result4} onClick={() => goResult('솔냥이')}></AllResultImg>
            <AllResultText style={{top:'82%'}}>초치는 말 장인</AllResultText>
            <AllResultText2 style={{top:'85%', right:'20%'}}>“{quizResult['솔냥이']}”</AllResultText2>
        </Wrapper>
    );
}

 

5. index.js 수정

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { RecoilRoot } from 'recoil';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <RecoilRoot>
    <App />
  </RecoilRoot>
);

reportWebVitals();

<RecoilRoot>로 감싸주는 거 잊지 말기!!

 

이런 식으로 수정하면 된다!

데이터 값도 올바르게 전달된다!

 

전체 코드 깃허브

(링크 후에 추가)

 

근데 해보니까 context API나 recoil이나 둘이 비슷한 것 같다. 애초에 둘 다 context라 그런가?

오히려 바꾸기 전이 중복되는 코드 없이 더 깔끔하다는 생각이 든다.

하지만!!! 

context API는 Provider 하위의 모든 consumer들이 Provider 속성이 변경될 때마다 다시 렌더링 된다는 사실을 잊지 말자!
Recoil은 변경된 Atom의 상태를 공유하고 있는 컴포넌트만 리렌더링 되므로 Context API처럼 쓸모없는 리렌더링이 계속 일어나지 않는다.

하지만 중복되는 코드가 맘에 걸리는 걸...


 

뭐가 됐든 상태 관리가 가장 중요한 것 같다

상태 관리를 어떻게 할지 설계를 초반에 잘 해둬야 할 듯... 

다음 플젝에서는 패턴도 어떻게 할지, 상태 관리도 어떻게 할지 다 미리 정해두고 쓰고싶다!!!

솦프 재밌겠다 ㅎㅎㅎㅎㅎㅎㅎ 나 또라인가봐

사실 리액트 말고 다른 것들? 해보고 싶었는데 얘를 공부할 수록 할 게 늘어나는 느낌이라 ㅎ 이거나 제대로 써야겠다는 생각이 든다

암튼 recoil로는 프론트단의 전역 상태 관리를 하고 서버와 통신하면서 상태 관리를 할 때는 react-query를 꼭 써보고 싶다!

 

글을 작성하는데 도움을 준 게시물

https://lah1203.netlify.app/post/8

 

하리 블로그

 

lah1203.netlify.app

 

728x90