상세 컨텐츠

본문 제목

[React.js 1] 9장. 컴포넌트 트리에 데이터 공급하기

23-24/React.js 1

by ssxb 2024. 1. 5. 10:00

본문

728x90

 Context  

: 같은 문맥( 목적 ) 아래에 있는 컴포넌트 그룹에 데이터를 공급하는 기능

 

사용 이유 

리액트 컴포넌트 계층 구조에서 컴포넌트 간에 값을 전달할 때 발생하는 Props Drilling 문제를 해결하기 위해 사용됩니다.

→ Context 이용 시 단계마다 일일이 Props를 전달하지 않고도 컴포넌트 트리 전역에 데이터를 공급할 수 있어 Props Drilling 문제를 간단하게 해결할 수 있습니다.

 

 

 ContextAPI 

: Context를 만들고 다루는 리액트 기능으로 createContext, Context.Provider 등이 있습니다.

 

Context 만들기

import React from "react";
const MyContext = React.createContext(defaultValue);
  • 인수로 전달하는 값은 Context의 기본값으로 생략할 수 있습니다.

 

Context에 데이터 공급하고 사용하기

import React from "react";
const MyContext = React.createContext(defaultValue);

function App() {
	const data = "data";
	return (
		<div>
			<Header />
			<MyContext.Provider value={data}>
				<Body />
			</MyContext.Provider>
		<div>
	);
}

function Main() {
	const data = useContext(MyContext);
	( ... )
}
  • Context.Provider라는 Context객체에 기본으로 포함된 컴포넌트 사용하기
    → Provider 컴포넌트에 Props(value)를 전달해 MyContext가 공급할 값을 설정

  • useContext : 특정 Context가 공급하는 데이터를 불러오는 리액트 훅
    → 호출한 컴포넌트가 인수로 전달한 Context 그룹에 속해 있지 않으면 오류 발생

≫  정리하면 createContext를 이용해 Context를 만들고, 값을 공급할 컴포넌트를 Context.Provider로 감싼다음 함수 useContext를 호출해 Context가 공급하는 값을 불러와 사용합니다.


 Context로 [할 일 관리] 앱 리팩토링하기 

리팩토링

: 사용자에게 제공하는 기능은 변경하지 않으면서 내부 구조를 개선하는 작업

 

어떻게 Context 적용할지 생각해보기

TodoItem 컴포넌트가 Props를 사용하려면 TodoList 컴포넌트를 거쳐서 전달해야 해 Props Drilling 문제가 발생합니다.

→ TodoContext 생성 후 TodoContext.Provider 아래에 TodoEditor, TodoList, TodoItem 컴포넌트 배치

import React, { useCallback, useReducer, useRef } from "react";
( ... )
const TodoContext = React.createContext();

return (
    <div className="App">
      <Header />
      <TodoContext.Provider value={{ todo, onCreate, onUpdate, onDelete }}>
        <TodoEditor />
        <TodoList />
      </TodoContext.Provider>
    </div>
  );
}
export default App;
  • Context는 반드시 컴포넌트 밖에서 생성해야 한다는 점을 유의해야 합니다.
  • Provider 배치하고 데이터를 공급하기 위해 Props(value)를 설정합니다.
    → 하위에 배치한 컴포넌트는 다른 경로로 Props를 받을 필요 없으니 해당 부분을 삭제합니다.
  • todo 값이 undefined로 length 프로퍼티로 접근하면 오류가 발생하기에 TodoList 컴포넌트에 todo의 기본값을 설정합니다.
const TodoList = ({ todo, onUpdate, onDelete }) => {
( ... )
}

TodoList.defaultProps = {
	todo: [],
};
export default TodoList;

 

 

 각 컴포넌트에서 Context 데이터 사용하기 

App.js에 선언한 TodoContext를 다른 파일에서 불러오기 위해선 export로 내보내야 합니다.

( ... )
export const TodoContext = React.createContext();
( ... )

 

TodoList 컴포넌트

import { useContext, useMemo, useState } from "react";
import { TodoContext } from "../App";
( ... )

const TodoList = () => {
	const { todo } = useContext(TodoContext);
	( ... )
	return (
	<div className="TodoList">
		( ... )
		<div className="list_wrapper">
        	{getSearchResult().map((it) => ( 
          		<TodoItem
       	  	 	key={it.id}
        	 	{...it}
      		/>
      		))}
      	</div>
    </div>
  );
};
export default TodoList;
  • 매개 변수로 구조 분해 할당하는 기존 코드를 제거
  • TodoItem에 함수를 전달할 필요성이 사라졌으므로 해당 부분 제거

≫  나머지 다른 컴포넌트도 위와 같이 매개변수로 구조 분해 할당하던 기존 코드를 변경하면 됩니다.


 리팩토링 잘 되었는지 확인하기 

문제 확인

todo가 변하면 TodoContext.Provider에서 전달하는 모든 Props도 변하는 문제가 발생합니다.

→ 원인 : State 변수 todo와 dispatch관련 함수들이 하나의 객체로 묶여 동일한 Context에 Props로 전달되기 때문입니다.

 

 

재설계하기

Context를 역할에 따라 분리하기

  • TodoStateContext : todo 업데이트되면 영향받는 컴포넌트 위한 Context
  • TodoDispatchContext : dispatch 함수 onCreate, onUpdate, onDelete가 변경되면 영향받는 컴포넌트 위한 Context
export const TodoStateContext = React.createContext();
export const TodoDispatchContext = React.createContext();
( ... )

function App() {
  ( ... )
	const memoizedDispatches = useMemo(() => {
	    return { onCreate, onUpdate, onDelete };
	 }, []);
	
  return (
    <div className="App">
      <Header />
      <TodoStateContext.Provider value={ todo }>
        <TodoDispatchContext.Provider value={memoizedDispatches}>
          <TodoEditor />
          <TodoList />
        </TodoDispatchContext.Provider>
      </TodoStateContext.Provider>
    </div>
  );
}
export default App;
  • App 컴포넌트에서 todo 공급할 TodoStateContext와 dispatch 함수를 공급할 TodoDispatchContext를 만들어 배치
  • useMeomo를 이용해  TodoDispatchContext에 전달한 dispatch 함수를 다시 생성하지 않도록 합니다.

 

Context에서 데이터를 받는 자식 컴포넌트 수정하기

TodoEditor 컴포넌트

import { TodoDispatchContext } from "../App";
( ... )

const TodoEditor = () => {
  const { onCreate } = useContext(TodoDispatchContext);
	( ... )
};

 

≫ 나머지 컴포넌트도 알맞은 Context에서 데이터를 받도록 수정합니다.


Quiz

  1. ( Context )란 같은 문맥( 목적 ) 아래에 있는 컴포넌트 그룹에 데이터를 공급하는 기능이다.
  2. ( 1번 답 )은 리액트 컴포넌트 계층 구조에서 컴포넌트 간에 값을 전달할 때 발생하는 ( Props Drilling ) 문제를 해결하기 위해 사용된다.
  3. ( ContextAPI )란 ( 1번 답)을 만들고 다루는 리액트 기능으로 createContext, Context.Provider 등이 있다.
  4. ( useContext )란 특정 Context가 공급하는 데이터를 불러오는 리액트 훅이다.
  5. 사용자에게 제공하는 기능은 변경하지 않으면서 내부 구조를 개선하는 작업을 ( 리팩토링 )이라 한다.
  6. ( 1번 답 )은 반드시 컴포넌트 (  )에서 생성해야 한다는 점을 유의해야 한다. 

 

코드 작성

1. App 컴포넌트에서 todo 공급할 TodoStateContext와 dispatch 함수를 공급할 TodoDispatchContext를 각각 만들어 배치하기 → dispatch 함수를 다시 생성하지 않도록 useMeomo를 이용하는건 미리 작성되어 있다.

import React, { useCallback, useMemo, useReducer, useRef } from "react";
( ... )

// 코드 작성
( ... )

function App() {
  ( ... )
	const memoizedDispatches = useMemo(() => {
	    return { onCreate, onUpdate, onDelete };
	 }, []);
	
  return (
    <div className="App">
      <Header />
      // 코드 작성
    </div>
  );
}
export default App;
더보기
import React, { useCallback, useMemo, useReducer, useRef } from "react";
( ... )

export const TodoStateContext = React.createContext();
export const TodoDispatchContext = React.createContext();
( ... )

function App() {
  ( ... )
	const memoizedDispatches = useMemo(() => {
	    return { onCreate, onUpdate, onDelete };
	 }, []);
	
  return (
    <div className="App">
      <Header />
      <TodoStateContext.Provider value={ todo }>
        <TodoDispatchContext.Provider value={memoizedDispatches}>
          <TodoEditor />
          <TodoList />
        </TodoDispatchContext.Provider>
      </TodoStateContext.Provider>
    </div>
  );
}
export default App;

2. 1번에서 작성한 Context를 활용해 TodoList 컴포넌트가 todo를 받아 사용할 수 있도록 수정하기

더보기
import { TodoStateContext } from "../App";
( ... )

const TodoList = () => {
  const todo = useContext(TodoStateContext);
	( ... )
};

출처 :  이정환, 『한 입 크기로 잘라먹는 리액트』, 프로그래밍인사이트(2023)

Corner React.js 1

Editor: ssxbin

 

 

728x90

관련글 더보기