본문 바로가기
Dev/React.js

[React18] 9. context

by YummYum 2023. 10. 2.


props 전달하기는 명시적으로 데이터를 전달하는 훌륭한 방법입니다. 하지만 이 방식은 props를 트리를 통해 깊이 전해줘야 하거나, 많은 컴포넌트에서 같은  props가 필요한 경우에 불편할 수 있습니다. 데이터가 필요한 여러 컴포넌트의 가장 가까운 공통 조상이 트리 상 높이 위치할 수 있고 그러면 state를 끌어올리는 것은 “props drilling”이라는 상황을 초래할 수 있습니다.

이를 해결하기 위해 context를 사용하면 부모 컴포넌트가 그 아래 트리 전체에 데이터를 전달할 수 있도록 해줍니다.
 
1. context 생성하기
먼저 context를 createContext로 만들어야 합니다. 그리고 컴포넌트에서 사용할 수 있도록 파일에서 내보내야 합니다.

 

import { createContext } from 'react';

export const LoginContext = createContext(false);

 
2. context 사용하기
React에서 useContext 훅과 생성한 context를 가져옵니다. useContext는 React에게 컴포넌트에게 LoginContext를 읽으려 한다고 알려줍니다.

 

import { useContext } from 'react';
import { LoginContext } from './LoginContext.js';
import { Profile } from './Profile.js';

export default function LoginButton(){
    //가장 가까이 있는 context provider 값을 가져옴
    const isLogin=useContext(LoginContext);
    return (
    	<>
        	{isLogin && <button>Go to Mypage</button>}
        	{!isLogin && <button>Go to Login</button>}
        </>
    )
}

 
3. context 제공하기
LoginContext를 자식들에게 제공하기 위해 context provider로 감싸줍니다. context를 사용하면 주변에 렌더링 되는 위치에 따라 자신을 다르게 표시하는 컴포넌트를 작성할 수 있습니다. 또한, 서로 다른 context는 영향을 주지 않으며 특정 context를 사용 및 제공하는 컴포넌트끼리 묶여 있습니다.

 

import { LoginContext } from './LoginContext.js';
import LoginButton from './LoginButton.js';

export default function Navigation({children}){
    return (
    	<LoginContext.Provider value={true}>
        	<LoginButton/>
        </LoginContext.Provider>
    );
}


props를 깊이 전달해야 한다고 해서 context에 넣어야 하는 것은 아닙니다. context를 사용하기 전 props 전달해 어떤 데이터를 사용하는지 명확하게 하는 것도 좋은 방법입니다. 또한 컴포넌트를 추출하고 JSX를 children를 사용하는 것도 방법입니다. (<Layout posts={posts} /> => <Layout><Posts posts={posts} /> </Layout>)

context로 주로 테마 지정(다크모드), 로그인, 라우팅, 상태관리(상태가 바뀌면 함께 같이 값을 변경할 경우)에 사용하곤 합니다.


reducer와 context를 결합하기

1. context 생성합니다.
2. state과 dispatch 함수를 context에 넣습니다.
3. 트리 안에 context를 사용합니다.
 
** useTasks와 useTasksDispatch 같은 함수들을 커스텀 훅이라고 합니다. 

 

import { createContext, useContext, useReducer } from 'react';

const TasksContext = createContext(null);

export function TasksProvider({ children }) {
  const tasks = useReducer(
    tasksReducer,
    initialTasks
  );

  return (
    <TasksContext.Provider value={tasks}>
        {children}
    </TasksContext.Provider>
  );
}

export function useTasks() {
  return useContext(TasksContext);
}

export function useTasksDispatch(){
	return useContext(TasksDispatchContext);
}

function tasksReducer(tasks, action) {
  switch (action.type) {
    case 'added': {
      return [...tasks, {
        id: action.id,
        text: action.text,
        done: false
      }];
    }
    case 'changed': {
      return tasks.map(t => {
        if (t.id === action.task.id) {
          return action.task;
        } else {
          return t;
        }
      });
    }
    case 'deleted': {
      return tasks.filter(t => t.id !== action.id);
    }
    default: {
      throw Error('Unknown action: ' + action.type);
    }
  }
}

 

import AddTask from './AddTask.js';
import TaskList from './TaskList.js';
import { TasksProvider } from './TasksContext.js';

export default function TaskApp() {
  return (
    <TasksProvider>
      <h1>Day off in Kyoto</h1>
      <AddTask />
      <TaskList />
    </TasksProvider>
  );
}

 

import { useState } from 'react';
import { useTasks } from './TasksContext.js';

export default function TaskList() {
  const [tasks] = useTasks();
  return (
    <ul>
      {tasks.map(task => (
        <li key={task.id}>
          <Task task={task} />
        </li>
      ))}
    </ul>
  );
}

function Task({ task }) {
  const [isEditing, setIsEditing] = useState(false);
  const [,dispatch] = useTasks();
  let taskContent;
  if (isEditing) {
    taskContent = (
      <>
        <input
          value={task.text}
          onChange={e => {
            dispatch({
              type: 'changed',
              task: {
                ...task,
                text: e.target.value
              }
            });
          }} />
        <button onClick={() => setIsEditing(false)}>
          Save
        </button>
      </>
    );
  } else {
    taskContent = (
      <>
        {task.text}
        <button onClick={() => setIsEditing(true)}>
          Edit
        </button>
      </>
    );
  }
  return (
    <label>
      <input
        type="checkbox"
        checked={task.done}
        onChange={e => {
          dispatch({
            type: 'changed',
            task: {
              ...task,
              done: e.target.checked
            }
          });
        }}
      />
      {taskContent}
      <button onClick={() => {
        dispatch({
          type: 'deleted',
          id: task.id
        });
      }}>
        Delete
      </button>
    </label>
  );
}

** React18 공식 문서 보고 정리한 내용입니다.

https://ko-react-exy5xcwjj-fbopensource.vercel.app/learn/passing-data-deeply-with-context

 

Context를 사용해 데이터를 깊게 전달하기 – React

The library for web and native user interfaces

ko.react.dev

https://ko-react-exy5xcwjj-fbopensource.vercel.app/learn/scaling-up-with-reducer-and-context

 

Reducer와 Context로 앱 확장하기 – React

The library for web and native user interfaces

ko.react.dev

'Dev > React.js' 카테고리의 다른 글

[React18] 11. Effect 작성하는 법 (useEffect)  (0) 2023.10.04
[React18] 10. ref  (1) 2023.10.03
[React18] 8. reducer (useReducer)  (0) 2023.10.01
[React18] 7. state 관리하기  (0) 2023.09.30
[React18] 6. state 업데이트  (0) 2023.09.29

댓글