Front/React

[Hooks] useContext

oodada 2024. 3. 20. 12:56

React Context

리액트의 하나의 컴포넌트에서 데이터를 생성하거나 업데이트하거나 다른 컴포넌트와 데이터를 공유해서 사용하는 여러 방법이 있습니다.

  • state 와 props를 사용해서 컴포넌트 간에 데이터를 전달

  • React Context API를 사용해서 컴포넌트 간에 데이터를 전달
  • Redux, MobX, Recoil 등의 상태 관리 라이브러리를 사용

useContext 란?

  • React Context는 Component 트리 전체에 props를 전달하지 않고도 Component 데이터를 제공하는 방법입니다.
  • Context는 전역 상태를 관리하기 위한 간단한 방법입니다.

Context 문법

  1. React.createContext 함수를 사용하여 Context생성합니다.
  2. Context.Provider 컴포넌트를 만나면, value prop을 통해 Context의 값을 제공합니다.
  3. useContext 컴포넌트를 사용하여 Context의 값을 사용합니다.

Context 사용하기

  • React.createContext 함수를 사용하여 Context를 생성합니다.
const MyContext = React.createContext(defaultValue);
  • Context.Provider 컴포넌트를 사용하여 Context를 제공합니다.
<MyContext.Provider value={/* 상태 값 */}>
  {/* 여기에 위치한 컴포넌트들은 이 Context의 값을 사용할 수 있음 */}
</MyContext.Provider>
  • Context 사용

일반 함수 컴포넌트에서 Context 값을 읽을 때는 useContext hook을 사용합니다.

const value = useContext(MyContext);

Context.Consumer 컴포넌트는 클래스 컴포넌트에서 Context 값을 읽을 때 주로 사용

<MyContext.Consumer>
  {value => /* 상태 값에 따라 UI를 렌더링 */}
</MyContext.Consumer>

Context를 사용한 Theme 예제

const themes = {
  light: {
    foreground: '#000000',
    background: '#eeeeee',
  },
  dark: {
    foreground: '#ffffff',
    background: '#222222',
  },
};

// createContext 함수를 사용하여 Context 생성
const ThemeContext = React.createContext(themes.light);

function App() {
  return (
    // context value를 공유할 컴포넌트를 감싸기
    // 공유하기 위한 value를 설정
    <ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar() {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton() {
  // context value를 사용하기 위해 useContext hook 사용
  const theme = useContext(ThemeContext);
  return <button style={{ background: theme.background, color: theme.foreground }}>I am styled by theme context!</button>;
}

export default App;

Context를 이용한 앱 만들기

  • 주문 페이지

여러 컴포넌트의 가격과 옵션을 공유하여 총액을 계산하여 보여주는 페이지를 만들어보겠습니다.

import React, { useState, useContext } from 'react';

// context 생성
const OrderContext = React.createContext();

function App() {
  const [order, setOrder] = useState({
    price: 0,
    option: 'none',
  });

  return (
    // context 제공
    // context value를 공유할 컴포넌트를 감싸기
    <OrderContext.Provider value={{ order, setOrder }}>
      <Order />
      <Total />
    </OrderContext.Provider>
  );
}

function Order() {
  // context value를 사용하기 위해 useContext hook 사용
  const { order, setOrder } = useContext(OrderContext);

  const handleChange = e => {
    let price = 0; // 기본 가격을 설정
    switch (e.target.value) {
      case 'sugar':
        price = 1000;
        break;
      case 'milk':
        price = 1500;
        break;
      case 'none':
      default:
        price = 0;
        break;
    }
    setOrder({ ...order, option: e.target.value, price: price });
  };

  return (
    <div>
      <h1>Order</h1>
      <select
        value={order.option}
        onChange={handleChange}
      >
        <option value="none">None</option>
        <option value="sugar">Sugar</option>
        <option value="milk">Milk</option>
      </select>
    </div>
  );
}

function Total() {
  const { order } = useContext(OrderContext);

  return (
    <div>
      <h1>Total</h1>
      <p>Price: {order.price}원</p>
      <p>Option: {order.option}</p>
    </div>
  );
}

export default App;

- 할 일 관리 앱 Context API 사용하기

// src/context/TodoContext.js
import React, { createContext, useReducer, useContext } from 'react';

// 초기 상태
const initialState = [
  {
    id: 1,
    isDone: false,
    task: '고양이 밥주기',
    createdDate: new Date().getTime(),
  },
  {
    id: 2,
    isDone: false,
    task: '감자 캐기',
    createdDate: new Date().getTime(),
  },
  {
    id: 3,
    isDone: false,
    task: '고양이 놀아주기',
    createdDate: new Date().getTime(),
  },
];

// reducer 함수
function reducer(state, action) {
  switch (action.type) {
    case 'ADD':
      return [action.payload, ...state];
    case 'UPDATE':
      return state.map(it => (it.id === action.payload ? { ...it, isDone: !it.isDone } : it));
    case 'DELETE':
      return state.filter(it => it.id !== action.payload);
    default:
      return state;
  }
}

// Context 객체 생성
const TodoStateContext = createContext(); // 상태 값 조회
const TodoDispatchContext = createContext(); // dispatch 함수 조회

// 커스텀 훅 생성
// 상태 값 조회
export function useTodoState() {
  return useContext(TodoStateContext); // Context 객체의 현재 값을 반환
}

export function useTodoDispatch() {
  return useContext(TodoDispatchContext); // Context 객체의 현재 값을 반환
}

// Provider 컴포넌트 생성
export function TodoProvider({ children }) {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <TodoStateContext.Provider value={state}>
      <TodoDispatchContext.Provider value={dispatch}>{children}</TodoDispatchContext.Provider>
    </TodoStateContext.Provider>
  );
}
// src/components/App.js
import React from 'react';
import TodoHd from './TodoHd';
import TodoEditor from './TodoEditor';
import TodoList from './TodoList';
import { TodoProvider } from '../context/TodoContext';

function App() {
  return (
    <TodoProvider>
      <div>
        <TodoHd />
        <TodoEditor />
        <TodoList />
      </div>
    </TodoProvider>
  );
}

export default App;
// src/components/TodoList.js
import React, { useState } from 'react';
import TodoItem from './TodoItem';
import { useTodoState, useTodoDispatch } from '../context/TodoContext';

export default function TodoList() {
  const todo = useTodoState();
  const dispatch = useTodoDispatch();
  const [search, setSearch] = useState('');

  const onChangeSearch = e => {
    setSearch(e.target.value);
  };

  const filteredTodo = () => {
    return todo.filter(item => item.task.toLowerCase().includes(search.toLowerCase()));
  };

  return (
    <div>
      <h2>할 일 목록 📃</h2>
      <input
        value={search}
        onChange={onChangeSearch}
        placeholder="검색어를 입력하세요"
      />
      <ul>
        {filteredTodo().map(item => (
          <TodoItem
            key={item.id}
            {...item}
          />
        ))}
      </ul>
    </div>
  );
}
// src/components/TodoEditor.js
import React, { useState } from 'react';
import { useTodoDispatch } from '../context/TodoContext';

export default function TodoEditor() {
  const dispatch = useTodoDispatch();
  const [task, setTask] = useState('');

  const onChangeTask = e => {
    setTask(e.target.value);
  };

  const onSubmit = () => {
    if (!task) {
      return;
    }
    dispatch({ type: 'ADD', payload: { id: Date.now(), isDone: false, task, createdDate: new Date().getTime() } });
    setTask('');
  };

  const onKeyDown = e => {
    if (e.key === 'Enter') {
      onSubmit();
    }
  };

  return (
    <div className="TodoEditor">
      <h2>새로운 Todo 작성하기 ✏ </h2>
      <div>
        <input
          value={task}
          onChange={onChangeTask}
          onKeyDown={onKeyDown}
          placeholder="할 일을 추가로 입력해주세요."
        />
        <button onClick={onSubmit}>추가</button>
      </div>
    </div>
  );
}
// src/components/TodoItem.js
import React from 'react';
import { useTodoDispatch } from '../context/TodoContext';

export default function TodoItem({ id, isDone, task, createdDate }) {
  const dispatch = useTodoDispatch();

  const onUpdate = () => {
    dispatch({ type: 'UPDATE', payload: id });
  };

  const onDelete = () => {
    dispatch({ type: 'DELETE', payload: id });
  };

  return (
    <div>
      <li key={id}>
        <input
          type="checkbox"
          checked={isDone}
          onChange={onUpdate}
        />
        <span style={{ textDecoration: isDone ? 'line-through' : 'none' }}>{task}</span>
        <span>{new Date(createdDate).toLocaleDateString()}</span>
        <button onClick={onDelete}>삭제</button>
      </li>
    </div>
  );
}

'Front > React' 카테고리의 다른 글

리액트 ES6 문법 정리 - 기본편  (0) 2024.03.29
리액트(React.js) 커리큘럼  (0) 2024.03.24
useReducer - React 배우기  (0) 2024.03.19
최적화 - React 배우기  (0) 2024.03.18
React를 이용한 할 일 관리 앱 만들기  (0) 2024.03.14
티스토리 친구하기