조금조금 REACT, HOOK - useCotext

Edwin·2023년 2월 28일
0

조금조금 REACT

목록 보기
9/31
post-thumbnail
  • 본 포스틴은 별코딩님의 강의를 요약하여 정리했습니다.

유용하지만 다소 번거로운 useState와 props를 보다 유용하게

리액트는 부모컴포넌트에서 자식컴포넌트에 정보를 전달하며, 동작합니다. 그러나 극단적으로 보면 이러한 결과를 맞이하게 될 수도 있습니다.(prop Drilling) 즉 최상위컴포넌트의 자료를, 최하위컴포넌트에서만 사용하는데도 말이죠. 그런데 이 과정에서 오류가 발생되지 않는다고 단정지을 수 있을까요?

UseContext

props 고리가 아니라, App.js가 만든 Context 방에 함께 있다면, 그 방 안에 들어 있는 모든 컴포넌트가 공유할 수 있는 데이터 시스템입니다.

01 먼저 단점부터

  • Context를 사용하면 컴포넌트를 재사용하기 어려워 질 수 있다는 것을 기억해야 한다.

리액트 공식문서에서도 단지 prop Drilling을 피하기 위한 목적이라면 Conponent Composition(컴포넌트 합성)을 먼저 고려할 것을 권장한다.

02 실제로 사용해 보자.

App.jsx에서 시작되는 isDark와 setIsDark -> props

App.jsx에서 시작된 props는 Page.jsx로 그리고 이곳에서 3곳으로 나눠집니다. 그런데 만약 중간컴포넌트도 여러 개이고, 하위 컴포넌트가 무수히 많다면, 곤란하겠죠?

(1) useContext 사용하기, CreateContext()

  • 먼저 context 폴더 > ThemContext.js 를 생성합시다.
import { createContext } from "react";
export const ThemeContext = createContext(null)

(2) 기존의 태그를 <Context파일명.Provider>로 감싸준다.

props를 전달하고자 하는 최상의 컴포넌트의 return (소괄호) 사이에 기록된 컴포넌트들을 <ThemeContext.Provider value={{isDark, setIsDark}}>과 같이 감싸준다. 이때 value 안에는 전달하고자 하는 변수를 넣어주면 되며, 복수의 경우 {객체}를 생성해서 보내주면 된다.

  return (
    <ThemeContext.Provider value={{isDark, setIsDark}}>
      <Page  />
    </ThemeContext.Provider>
  )

(3) props들을 제거한다. App.jsx/Page.jsx

// App.jsx
import React, { useState } from 'react'
import Page from './components/Page';
import './App.css'
import { ThemeContext } from './context/ThemeContext';

function App() {
  const [isDark, setIsDark] = useState(false);
  return (
    <ThemeContext.Provider value={{isDark, setIsDark}}>
      <Page  />
    </ThemeContext.Provider>
  )
}

export default App
  
// Page.jsx
import React, { useContext } from 'react'
import Header from './Header'
import Content from './Content'
import Footer from './Footer'
import { ThemeContext } from '../context/ThemeContext'

const Page = () => {
  const data = useContext(ThemeContext)
  console.log('data:', data)
  return (
    <div>
      {/* <Header isDark={isDark}/>
      <Content isDark={isDark}/>
      <Footer isDark={isDark} setIsDark={setIsDark}/> */}
    </div>
  )
}

export default Page  

이와 같이 props로 전달하지 않고, const data = useContext(ThemeContext)를 통해서 부모컴포넌트로부터 정보를 불러왔다.

4) 무한루프의 함정

import React, { useContext } from 'react'
import { ThemeContext } from '../context/ThemeContext'

const Footer = () => {
  const {isDark,setIsDark} = useContext(ThemeContext);
	setIsDark(!isDark)
  
const tooggleTheme = () => {
    setIsDark(!isDark)
  };
return (...) };

문제의 부분
const {isDark,setIsDark} = useContext(ThemeContext);
setIsDark(!isDark)

새벽4시에 유튜브를 보며 따라서 기록하다 보니 그때에는 발견하지 못했지만, 일어나서 확인했다. isDark의 값을 바꾸는 setIsDark 함수를 리렌더링이 될 때마다 무한 반복을 시켜놓은 것이다. 그 결과는 어떻게 되었을까? 1초 미만의 간격으로 함수가 무한 반복 실행되며, 화면이 Dark->whith/lightgray 의 현란한 광란이 되어 버렸다. 그 결과 버튼으로 만든 기능도 제대로 구현이 되지 않았던 것이다.

5) 정리하면 useContext란?

제로초님의 정리에 따르면, 한때 리액트 공식문서에서 쓰지 말라고 말하기까지 했던 context지만, 최근들어 활발하게 사용되고 있고, 심지어 Redux를 useContext로 대체하려는 사람들이 늘어나고 있다는 점에서 그는 useContext의 효율성을 소개한다. (별코딩님도 가장 애용하는 훅이라고 말씀하심)

(1) 정리의 내용은 간단하다. props Drilling의 비효율성을 피하기 위해서이다. 이를 위해서 선행해야 하는 한가지는 바로 createContext 선언이다.

부모컴포턴트에서 할 일, 이때 해당 부분을 분리시킨 파일로 만들어도 가용하다.

부모컴퍼넌트에서 생성시

  • App.jsx
import React, { createContext } from 'react'; 
export const Context이름 = createContext(초기값);
function App() { ...
	return (<ThemeContext.Provider data={data}> ... </ThemeContext.Provider>)
 		또는 return (<ThemeContext.Provider data={{구조,분해,할당}}> ... </ThemeContext.Provider>
)}  

파일분리시

  • App.jsx
import React, { createContext } from 'react'; 
function App() { ... 
	return (<ThemeContext.Provider data={data}> ... </ThemeContext.Provider>)
		또는 return (<ThemeContext.Provider data={{구조,분해,할당}}> ... </ThemeContext.Provider>
)}
  • Context이름.js
import { createContext } from 'react'; 
export const Context이름 = createContext(초기값);

(2) 부모태그의 값을 공유받을 자식컴포넌트에서 할 일은 props를 기다리는 것이 아니라, useContext(Context이름)을 통해서 사용하면 되는 것이다.

import React, { useContext } from 'react';
import { UserContext } from './GrandParent';

const Children = () => {
  const { setLoading, setLoggedIn } = useContext(UserContext);
  return ( ...)
}

6) 이미지로 내용 정리하기

아래와 같이 이해하면 되지 않을까? 싶다.

7) TODO Lists 에서 적용해 보기

  • App.js 에서 기록해 둔 useState를 자녀 컴포넌트에서도 적용해보자.
import React, { createContext, useState } from 'react'

export const TodolistContext = createContext();

function App() {
  const [todo, setTodo] = useState([
    {id:1677717617643, state:false, title:"리액트(1)", txt:"리액트 공부하기"}])  
  
return (
    <>
      <TodolistContext.Provider value={{todo, setTodo}}>
		...
		...
      </TodolistContext.Provider>
    </>
  )
}

export default App  

먼저 : import React, { createContext } from 'react' 선언

  • useContext를 사용하기 위해서는 상단에 임포트를 해주어야 한다. useContext는 React의 내장기능임으로 터미널에서 설치해줄 필요없이 바로 임포트함으로 사용이 가능하다.

둘째, App.js return 안에서 <TodolistContext.Provider value={{todo, setTodo}}> 과 같이 기록하며, 해당 App컴포넌트에서 선언된 변수들을 하위 컴포넌트들에게 props 전달할 수 있다. Provider 안에 있는 태그들에는 해당 정보들을 전달할 수 있다.

  return (
    <>
      <TodolistContext.Provider value={{todo, setTodo}}>
      <CreateGlobal/>
        <Layout>
          <Form />
          <Todolists />
        </Layout>
      </TodolistContext.Provider>
    </>
  )
}

셋째, useContext를 사용할 컴포넌트에서 부모 컴포넌트에서 선언한 createContext 사용할 수 있다. 이와 같이 해당 컴포넌트가 아니라 부모컴포넌트에 있는 변수들을 사용할 수 있다.

import React, { useContext, useState } from 'react'
import {TodolistContext} from '../App'

function Edit({el}) {
  const {todo, setTodo}  = useContext(TodolistContext)
  const updateHander = () => {
    const findThisTodo = todo.findIndex(find => find.id === thisTodo)
    const newtodo = [...todo]
    newtodo.splice(findThisTodo, 1, {id:el.id, state:el.state, title:el.title, txt})
  }

import {TodolistContext} from '../App'

내가 계속해서 누락하는 부분 때문에 오늘도 이 부분에서 한참을 고생했다. useContext를 사용하고자 하는 부분에서 해야 할 일이 있다면 결국 3가지를 체크해야 하는 것이다.

첫째, import React, { useContext } from 'react'
둘째, import {TodolistContext} from '../App'
셋째, const {todo, setTodo} = useContext(TodolistContext)

순서대로 useContext가 임포트 되었는지, 사용할 Context가 기록된 파일이 연결되어 있는지(export 했다면, import가 필요하지 않은가), 그리고 Context를 사용하겠다는 선언이 있는지를 확인해야 하는 것이다.

이를 다시 정리하면,

  • Context를 사용하려는 의지를 표현했는지,
  • 만들어진 Conetext를 자녀컴포넌트에 import 했는지
  • 컴포넌트 내에 선언(useContext)했는지 이상이다.
profile
신학전공자의 개발자 도전기!!

0개의 댓글