카테고리 없음

[React/오류해결] 처음만난 Context Hook과 혼신의 맞짱

kingmusung 2024. 7. 8. 23:08

 

개요.

https://github.com/2024-EveryCare/frontend

 

 

GitHub - 2024-EveryCare/frontend: 에브리케어 웹 서비스 프론트엔드 개발을 위한 레퍼지토리

에브리케어 웹 서비스 프론트엔드 개발을 위한 레퍼지토리. Contribute to 2024-EveryCare/frontend development by creating an account on GitHub.

github.com

 

Language: TypeScripts,

Library: React

IDE: VsCode

 

사용자가 처방전 이미지 파일을 업로드 -> 서버에서 OCR을 이용 후 처방전 데이터를 다시 넘겨줌

 

위 과정에서 파일을 업로드 후 서버에서 데이터를 받아 온 후 리다이렉트를 통해 다른 페이지에서 그 값을 기반으로 랜더링이 되도록 하고 싶었다. 아무래도 컴포넌트 안에 있는 변숫값을 다른 컴포넌트에서 이용을 하고 싶다 보니 생각나는 게 전역변수였다. 

 

전역변수를 키워드로 찾아보던와중 useState, useEffect, useRef 외 context Hook이라는 친구를 발견하였다.

 

통상 스타일을 전역적으로 주고싶을때(다크모드, 라이트 모드)도 사용하는 거 같았다.


간단한 정리

Context의 기본 개념

 

React의 Context API는 컴포넌트 트리 전체에서 전역적으로 데이터를 공유할 수 있는 메커니즘을 제공합니다. Context를 사용하면 중첩된 컴포넌트에서 props를 통해 데이터를 전달하는 번거로움을 줄일 수 있다.

 

저 같은 상황이 아니더라도, 여러 컴포넌트들이 얽히고설켜있어 props를 전달하기 위해 여러 다리를 건너는 과정이 많아진다면 코드를 짜는 입장에서 상당히 보기 불편하고 짜기도 불편할 것이다.. 이러한 이유로도 사용을 한다.

 

. Provider 컴포넌트는 특정 Context의 값을 설정하는 역할을 합니다. 이건 아래 코드와 함께 설명을 이어가겠다.


 

솔직히 프런트엔드가 처음이고 급하게 진행 한 프로젝트라 진짜 소스코드가 엉망이지만... 기록에 주안점을 두는 것이니.. 부끄러워도 올려보겠다...

 

step1

 

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

interface OCRDataType {
  // OCR 인식으로 받은 처방전 데이터 인터페이스
  drugName?: string[];
  intakeStart?: string;
  intakeEnd?: string;
  intakeCycle?: string;
  hospital?: string;
  disease?: string;
}

const initData: OCRDataType = {
  drugName: [],
  intakeStart: '',
  intakeEnd: '',
  intakeCycle: '',
  hospital: '',
  disease: '',
}; //초기화


export const RegisterContext = createContext({
  OCRData: initData,
});

export const RegisterContextProvider: React.FC = ({ children }) => {
  const [OCRData, setOCRData] = useState(initData);

  return (
    <RegisterContext.Provider value={{ OCRData, setOCRData }}>
      {children}
    </RegisterContext.Provider>
  );
};

 

현제 전역변수에 저장할 값들에 대한 초안? 을 제공하기 위해 인터페이스를 만들어준 후 초기화를 하였다.

 

 

interface OCRDataType {
  // OCR 인식으로 받은 처방전 데이터 인터페이스
  drugName?: string[];
  intakeStart?: string;
  intakeEnd?: string;
  intakeCycle?: string;
  hospital?: string;
  disease?: string;
}

const initData: OCRDataType = {
  drugName: [],
  intakeStart: '',
  intakeEnd: '',
  intakeCycle: '',
  hospital: '',
  disease: '',
}; //초기화

 

export const 변수명 = createContext(초기화할 값)

export const RegisterContext = createContext({
  OCRData: initData,
});

 

아래처럼 그냥 바로 집어도 실행은 되나 조금 더 명시적으로 넣기 위해 위의 방법을 선택했다.

export const RegisterContext = createContext(initData);

 

initDataOCRDataType 타입을 가진 객체로, 이 객체가 RegisterContext의 초기값이 된다. 즉, RegisterContext는 초기값으로 initData 객체를 가지는 React의 Context 객체를 생성하는 것이다.

 

export const RegisterContextProvider: React.FC = ({ children }) => {
  const [OCRData, setOCRData] = useState(initData);

  return (
    <RegisterContext.Provider value={{ OCRData, setOCRData }}>
      {children}
    </RegisterContext.Provider>
  );
};

 

보면 태그에 뜬금없이. provider가 붙어있고 그 뒤로 value={콘텍스트 값들} 이런 식으로 되어있는데. 하위 컴포넌트들에서 Context값들을 쓸 수 있도록 해주는 역할이다.

-> 사실 하위컴포넌트에서 쓰는 거랑 무슨 상관이지라고 이해를 못 했었다..

 

createContext로 초기화를 했는데 왜 useState로 초기화를 하지?

 

useState를 통한 초기화

사용 목적: 함수형 컴포넌트에서 상태를 관리하기 위해 사용됩니다.

 

동작 방식: useState(initialState)를 호출하여 상태와 해당 상태를 업데이트하는 함수를 제공받습니다. 컴포넌트가 렌더링 될 때마다 초기값이 설정되며, 이 값을 통해 상태를 관리하고 업데이트합니다.

 

createContext를 통한 초기화

사용 목적: React의 Context API를 사용하여 컴포넌트 트리 전체에서 상태를 공유하기 위해 초기값을 설정합니다.

=> null 값으로 두어도 별다른 오류는 발생하지 않는다,그래도 타입추론을 위해 사용하면 좋을거 같다.

 

동작 방식: createContext(initialValue)를 호출하여 Context 객체를 생성하고, 해당 Context의 초기값을 설정합니다. Provider가 값을 제공하지 않을 경우 이 초기값이 사용됩니다.


step2

신나게 만들었으니 이제 적용을 해야 한다. 그전 내가 했던 실수는

 

1. context를 최상위 컴포넌트에 두라고 했는데 최상위 컴포넌트가 app.tsx인지 main.tsx인지 아니면 page컴포넌트 최상단인지 헷갈렸다.

 

2. 이 값을 공유하고자 하는 페이지 컴포넌트 최상단에 아래와 같이 두었다. 소스 코드는 하나지만 저런 식으로 두 개의 페이지 컴포넌트 최상단에 두면 공유가 될 줄 알았다.

 

import React from 'react';
import BackLayout from '../../components/BackLayout';
import CenterLayout from '../../components/CenterLayout';
import NavBar from '../../components/NavBar';
import SmallLogo from '../../assets/SmallLogo.png';
import BackBtn from '../../components/register/button/BackBtn';
import PillSearch from '../../components/register/PillSearch';

const PillSearchPage: React.FC = () => {
  const LogoStyle = {
    width: '30%',
    marginTop: '3%',
    marginLeft: '3%',
  };
  return (
  <아까만든 컨텍스트>
    <BackLayout>
      <CenterLayout>
        <img src={SmallLogo} alt="" style={LogoStyle} />
        <BackBtn text="약 입력"></BackBtn>
        <PillSearch></PillSearch>
        <NavBar></NavBar>
      </CenterLayout>
    </BackLayout>
   </아까만든 컨텍스트>
  );
};

export default PillSearchPage;

 

진짜 콘텍스트 아래에 있는 컴포넌트들에게만 전역변수 값이 공유가 되고. 다른 페이지에서는 "초기값" 이 계속 나왔었다.

(위에서 설명했듯 2개의 페이지 컴포넌트 상단에 콘텍스트를 두었다고 했다.)

 

글을 정리하면서 생각을 해봤는데

더보기

동작 방식: useState(initialState)를 호출하여 상태와 해당 상태를 업데이트하는 함수를 제공받습니다. 컴포넌트가 렌더링 될 때마다 초기값이 설정되며, 이 값을 통해 상태를 관리하고 업데이트합니다.

useState 때문에 컴포넌트가 렌더링이 될 때마다 초기값이 설정이 되는데 당연히 각각 페이지 컴포넌트 상단에 콘텍스트를 두고 리다이렉트를 시키면 랜더링이 새로 되므로 초기값이 계속 나왔던 것이다.

 

그러면 어디다 두었는가!

 

import { RegisterContextProvider } from './components/register/context/RegisterContext';

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <React.StrictMode>
    <RegisterContextProvider>
      <Router>
        <Routes>
          <Route path="/" element={<MainPg />} />
          <Route path="/login" element={<LoginPage />} />
          <Route path="/signup" element={<SignupPage />} />
          <Route path="/signup2" element={<SiginupPage2 />} />
          <Route path="/scan-or-direct" element={<ScanOrDirectPage />} />
          <Route path="/direct-scan" element={<DirectScanPage />} />
          <Route path="/scan-confirm" element={<ScanConfirmPage />} />
          <Route path="/direct-register" element={<DirectRegisterPage />} />
          <Route path="/pill-search" element={<PillSearchPage />} />
          <Route path="/pill-register" element={<PillRegisterPage />} />
          <Route path="/calendar" element={<CalendarPage />} />
        </Routes>
      </Router>
    </RegisterContextProvider>
  </React.StrictMode>,
);

 

가장 최상단인 main.tsx에 두었다! 이렇게 되면 모든 컴포넌트에서 값이 공유가 된다!!


Step3.

설정도 다 했으니 이제 다른 컴포넌트에서 값을 넣어보도록 하자!

 

불필요한 코드는 전부 생략을 하고.

import React, { useContext, useRef } from 'react';
import {
  RegisterContext,
  RegisterContextProvider,
} from './context/RegisterContext';

const DirectScan: React.FC = () => {
  const { OCRData, setOCRData } = useContext(RegisterContext);
  
서버에 요청을 보내고 Response를 받는 코드임.
전부 생략하고 서버로 부터 성공적으로 response가 왔다고 생각하고 쭉쭉 내려가자
          setOCRData({
            drugName: response.data.drugName,
            intakeStart: response.data.intakeStart,
            intakeEnd: response.data.intakeEnd,
            intakeCycle: response.data.intakeCycle,
            hospital: response.data.hospital,
            disease: response.data.disease,
          });

  };

  const handleTest = () => {
    console.log(OCRData); //div 버튼을 누르면 context에 있는 OCRData의 값이 출력된다.
  };
  return (
          <div onClick={handleTest}>          >
            휴대폰으로 qr코드를 인식한 후 약 봉투를 스캔해주세요
          </div>
  );
};

export default DirectScan;

 

  const { OCRData, setOCRData } = useContext(RegisterContext); 를 통해 Context를 불러오자

(중괄호임에 유의하자.)

 

그러면 이제 콘텍스트를 사용할 준비가 완료되었다.

 

사용 예시를 보자면, 서버를 통해 "response"를 받고 아까 정의한 setOCRData를 통해 값들을 넣어준다.

 

그 후 div 태그를 눌러서 console.log를 찍어보면 값이 최신화된 것을 볼 수 있다.

 

이 상태로 다른 컴포넌트들에서도 똑같이 console.log로 값을 찍어보면 변경된 값이 나온다. 전역변수 설정완료!!