Dog foot print

[React] Hook기본 다지기#3 context 본문

REACT

[React] Hook기본 다지기#3 context

개 발자국 2021. 3. 30. 22:59

 

서론

 

App 컴포넌트 트리가 혹시 길어지는 것을 경험 해본 적이 있는가 ? 짧은 토이 프로젝트의 경우  컴포넌트 노드의 레벨이 3정도  것이다. 이런 경우 root component에서  하위 컴포넌트 노드에게 정보를 전달 해주기 위해, props  레벨의 컴포넌트 노드에게 전달 해준 경험이 존재  것이다. 그렇게 어려운 일도 아니고, props  컴포넌트에게 전달 하면 장땡이다 ! 

 

 

이처럼 짧은 거리의 컴포넌트에게는 props 데이터를 전달해주는 것은 충분하다. 그러나 트리의 높이가 4이상을 넘어가게 되면, props 상위 컴포넌트의데이터를 전달해주는 것은 매우 힘들다 . 

 

불가능하다는 이야기가 아니다. 코드가 지저분해지고, 이를 하위 프롭스로 전달하는 과정에서, 개발자의 실수로 유지하지 못할  있다. 

 

 

 

 

 

context 

 

배워야  것들이 ~context API이니, 잠시 context 무엇인지 조금만 짚고 넘어가보자. Context 한국어로 직역하면, 문맥이 된다. 단순히 문맥이라고한다면 이해하기 매우 어려우니, 쉽게 접근 가능한 영역이라고 하면 좋을  같다. 

 

누군가는 이렇게 이야기  것이다. “ 컴퓨터 공학부에서 배운 컨텍스트는 멀티 태스킹환경에서 Task 변경   변경되는 Task 실행 상태 정보를 담고있는  입니다. “ 맞는 말이다.  컨텍스트라는 단어는 언어 차원의 Context 컴퓨터 공학에서 다루는 Context 두가지 의미가 존재한다. 여기서는 주로언어 차원에서 context 대해서 다룬다. 

 

const App = () => {
    const a = 10;
    Childe();
}
 
const Childe = () => {
    console.log(a);
}

 

 

예를 들어 위와 같이 App함수내에서 선언된 a변수를 Childe함수에서 접근하려 하면, 어떤 일이 발생할까 ? 당연히 같은 스코프를 공유하지 않기 때문에Childe입장에서는 a변수에 접근이 불가능하다. (결과는 undefined) 그렇다면 App 변수의 Context Childe에게 유지 시켜 전달하려면 어떻게 할까 ? (매개변수를 사용하지 않고 ! )

 

const App = () => {
    const a = 10;
    const Childe = () => {
        console.log(a);
    }
    Childe();
}

 

이렇게 Childe함수를 App 함수에서 선언하여, 캡처를 이용하는 방법도 존재한다. 그러나, 파일 별로 컴포넌트를 작성하는 리액트에서는 자주 사용되지 않는다. 캡처를 이용한 컨텍스트 공유를 하는 경우가 있지만 컴포넌트의 코드 흐름을 방해하지 하지 않도록  방법은 남발해서는 안된다. 

 

 다른 방법으로는 무엇이 있을까 ? 전역적으로 관리되는 변수를 이용하는 방법이 존재한다. 

let a = 10;
 
const App = () => {
    a = 20;
    
}
const Childe = () => {
    console.log(a);
}
Childe();
 

React에서 기본으로 제공되는 Context api 이렇게 전역적으로 관리되는 변수에 사이드 이펙트를 주어, 이를 하위 컴포넌트에서 구독하는 방법으로, 상위컴포넌트에서 전달해야하는 컨텍스트를 하위 컴포넌트에서도 읽기 가능하다. 

 

Create Context 

 

createContext 함수는 리액트에서 제공하는 contextAPI이다. createContext 함수의 기본 구조는 다음과 같다. createContext(defaultValue) => {Provider, Consumer}

 

Provider 컴포넌트는 Context 구독하고 있는 하위 컴포넌트에게 value 전달하는 역할을 한다. Consumer 컴포넌트는 Context 구독하고자 하는 하위 컴포넌트가 상위 컴포넌트에게 value 전달 받는 역할을 한다. 

 

/src  Profile.js 만들어 잠시 준비해 보자.

 

import React from 'react'
 
export default function Profile() {
    return (
        <div>
            <Hello></Hello>
        </div>
    )
}
 
function Hello(){
    return (
        <>
            <h1>hello !</h1>
        </>
    )
}

 

App 컴포넌트에서 Hello 컴포넌트까지의 경로는 App -> Profile -> Hello순이다. Props 또한 이와 같이 전달해주어야 Hello컴포넌트에서 사용   있다. 

 

이제 /src/app.js에서 context 만들어 보도록 하자. 

 

 
import './App.css';
import React, {useState,useEffect,createContext} from 'react'
import {useCDM} from './hooks/lifecycle'
import Profile from './Profile';
import { getInitData } from './api/api';
 
export const UserDataContext = createContext("");
 
function App() {
 
  const [name,setName] = useState('');
  
  
  const appInitialize = () => {
      getInitData("blue").then((initData)=>{
        console.log(initData);
        setName(initData);
      }).catch((error)=>{
        console.log(error);
      })
  }
  const isMount = useCDM(appInitialize);
 
  return (
    <div className="App">
        <UserDataContext.Provider value={name}>
          <Profile></Profile>
        </UserDataContext.Provider>
    </div>
  );
}
 

 

UserDataContext에는 defaultValue  스트링을 할당해주었다. 그리고 Provider value props에는 name state 전달하도록 한다. 

 

이제 /Profile.js에서 구독을 해보도록 하자. 

 

import React from 'react'
import { UserDataContext } from './App';
export default function Profile() {
    return (
        <div>
            <Hello></Hello>
        </div>
    )
}
 
function Hello() {
    return (
        <>
            <UserDataContext.Consumer>
            {
                name => <h1>{name}</h1>
            }
            </UserDataContext.Consumer>
        </>
    )
}

 

이렇게 Props 트리를 타고 전달하지 않아도, Context API 통해서 Props 하위 컴포넌트에게 전달 가능한 것을   있다. 

 

(결과)

 

useContext

 

지금  예제에서는 하위 컴포넌트에서 Consumer 사용하기 때문에, JSX문법이 꽤나 길어져 가독성이 떨어질 우려가 존재한다. 이럴  함수에서context 변수에 할당하여 사용   있는 useContext API 존재한다. 

 

/Profile.js

import React,{useContext} from 'react'
import { UserDataContext } from './App';
export default function Profile() {
    return (
        <div>
            <Hello></Hello>
        </div>
    )
}
 
function Hello() {
 
    const userName = useContext(UserDataContext)
    
    return (
        <>
            <h1>{userName}</h1>
        </>
    )
}

(동일한 결과)

 

memo 

 

우리가 context API 사용하면서, Provider props 전달하고자, 하는 하위 컴포넌트를 감쌌다. 그럼 state 변경   Context 직접 구독하지 않은 상위 컴포넌트 또한 다시 렌더링이 될까 ? 답은   그렇다. “

 

/Profile.js에서 정말 렌더링이 다시 발생하는지 console.log() 실행해보자 .

 

export default function Profile() {
    console.log("rendering")
    return (
        <div>
            <Hello></Hello>
        </div>
    )
}

결과는 렌더링이 발생한다.

 

 Profile 컴포넌트는 상태변화가 전혀 없기 때문에 굳이 render 발생 시켜, 자원을 소모 시킬 이유가 없다. 

 

React.memo  memo api 사용한다면, props state 변경된 사항이 존재하지 않다면, 해당 컴포넌트를 새롭게 렌더링 되지 않는다. 

 

import React,{useContext} from 'react'
import { UserDataContext } from './App';
 
function Profile() {
    console.log("render")
    return (
        <div>
            <Hello></Hello>
        </div>
    )
}
 
function Hello() {
 
    const userName = useContext(UserDataContext)
    
    return (
        <>
            <h1>{userName}</h1>
        </>
    )
}
 
const MemorizedProfile = React.memo(Profile);
 
export default MemorizedProfile

 

(결과는  한번의 render 된다.)

 

 

 

반응형
Comments