지하 속에 있는 자식에게 props 전달하기 with Context

sucream·2023년 2월 4일
1

react

목록 보기
6/9

Lifting state up and Prop drilling

우리는 컴포넌트를 계층적으로 만들 수 있고, 자식 컴포넌트에 state나 props를 전달할 수 있다. 그리고 많은 컴포넌트에서 공통의 state 관리를 위해 lifring state up이라고 불리는 상태 끌어올리기라는 기법을 이용한다. 이는 state를 사용하는 컴포넌트들의 공통 부모 컴포넌트로 끌어올려 관리하는 방법이다. 그런데 이 방법에는 큰 불편함이 뒤따르게 된다. 만약, 아주 깊숙히 있는 자식 컴포넌트에게 데이터를 전달하고 싶다면 어떻게 해야 할까?
중간에 있는 컴포넌트들은 자신이 해당 prop이 필요하지 않더라도, 해당 state가 필요한 leaf node(최하단 컴포넌트)까지 props를 계속 전달해야 할 것이다. 이를 prop drilling이라고 한다.

아래 코드를 통해 prop drilling 예제를 확인해 보자.

// prop을 전달하는 부모 컴포넌트
import Parent from "./Parent";

import "./styles.css";

export default function App() {
  const clickHandler = (e) => {
    alert("메롱");
  };

  return (
    <div className="App">
      <h1>App Component</h1>
      <Parent onClick={clickHandler} />
    </div>
  );
}
// 중간에 컴포넌트를 전달만 하는 역할
import Child1 from "./Child1";

import classes from "./Parent.module.css";

const Parent = ({ onClick }) => {
  return (
    <div className={classes.Parent}>
      <h2>Parent Component</h2>
      <Child1 onClick={onClick} />
    </div>
  );
};

export default Parent;
// 중간에 prop을 전달만 하는 역할
import Child2 from "./Child2";

import classes from "./Child1.module.css";

const Child1 = ({ onClick }) => {
  return (
    <div className={classes.Child1}>
      <h3>Child1 Component</h3>
      <Child2 onClick={onClick} />
    </div>
  );
};

export default Child1;
// 실제 onClick을 사용할 최하단 컴포넌트
import classes from "./Child2.module.css";

const Child2 = ({ onClick }) => {
  return (
    <div className={classes.Child2}>
      <h4>Child2 Component</h4>
      {/* 실제 prop을 사용하는 부분 */}
      <button onClick={onClick}>버튼을 클릭해 보세요</button>
    </div>
  );
};

export default Child2;

App 컴포넌트에서 생성한 clickHandler라는 핸들러는 최종적으로 Child2 컴포넌트의 button에서 사용하고 있다. 하지만 Child2가 이를 사용하기 위해 상위 컴포넌트들은 필요하지 않지만 이를 prop으로 전달해 주고 있는 모습을 확인할 수 있다.

prop 텔레포트 시키기

만약 다른 상위 컴포넌트들의 prop drilling 없이 Child2 컴포넌트가 바로 clickHandler에 접근할 수 있으면 얼마나 좋을까? 이를 위해 리액트는 context라는 것을 제공한다.

컨텍스트를 사용하는 방법은 다음 세가지 순서를 통해 이루어 진다.

1. context 생성

useReducer를 사용할 때, reducer 함수를 전혀 다른 곳에 위치시켜도 괜찮다는 것을 기억하는가? context의 생성도 어디서 해도 상관이 없다. 따라서 context를 관리할 수 있는 별도의 파일에 작성해도 무관하다. Context의 생성은 createContext로 진행하며, 사용 방법은 useState와 같다. createContext 함수에 context가 가질 초깃값을 지정하면 해당 context가 생성된다.

// UserContext.js

import { createContext } from 'react';

// 초깃값이 객체인 UserContext를 생성함
const initUserInfo = {
  name: 'sucream',
  age: 29
};
export const UserContext = createContext(initUserInfo);

2. context 사용

context를 생성했다면, 사용할 컴포넌트에서 useContext와 사용할 context를 가져온다. useContext함수에 사용할 context를 넘기면 컴포넌트 내에서 해당 context에 접근할 수 있게 된다. 이렇게 접근하는 이유는 context도 일종의 state로, 컴포넌트 외부에서 관리되고 컴포넌트가 재렌더링 되더라도 최신의 값을 가지게 하기 위함이다.

import { useContext } from 'react';
import { UserContext } from './UserContext';

const Child = () => {
  // useContext를 이용해 context를 사용할 수 있도록 함
  const ctx = useContext(UserContext);
  
  return (
    <div>{ctx.name}님은 {ctx.age}살입니다.</div>
  );
};

export default Child;

3. context 제공

Context를 생성하고 사용하는 부분을 작성했지만, 해당 context가 어느 범위 내에서 사용될 것인지, context의 값을 지정해줘야 한다. 공통 부모에서 Provider라는 것을 전달할 수 있다. <UserContext.Provider></UserContext.Provider> 형태의 컴포넌트로 사용되며, 해당 컴포넌트의 children으로 context가 사용될 컴포넌트를 넣으면 된다. 이를 통해 해당 컴포넌트는 물론 자손 컴포넌트들에서 context에 접근할 수 있게 된다.
만약 자식/자손 컴포넌트에서 context를 사용하는데 provider를 넘기지 않으면, conetxt 접근시 createContext에 사용된 초깃값이 사용된다.

import Child from "./Child";
import { UserContext } from "./UserContext";

const Parent = () => {
  return (
    <div>
      {/* context.Provider로 제공 */}
      {/* value를 통해 context의 기본값을 지정할 수 있음 */}
      <UserContext.Provider value={{ name: "react", age: 10 }}>
        <Child />
      </UserContext.Provider>
      {/* 자식/자손에서 context를 사용하는데 provider를 제공하지 않으면 */}
      {/* createContext에서 사용한 초깃값이 사용됨 */}
      <Child />
    </div>
  );
};

export default Parent;

결과 확인

Reference

profile
작은 오븐의 작은 빵

0개의 댓글