Front/React

Context API로 전역 상태 관리하기

oodada 2024. 3. 20. 12:56
반응형

React Context

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

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

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

Context API란

  • 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>
    )
}

전세계 나라의 현재 시간을 보여주는 Context 예제

// TimeContext.js
import React, { createContext } from 'react'

// Context 생성
const TimeContext = createContext()

export default TimeContext
// TimeApp.js
import React, { useState } from 'react'
import TimeContext from './TimeContext'

function TimeApp() {
    const [time, setTime] = useState(new Date())

    const updateTime = () => {
        setTime(new Date()) // 현재 시간으로 업데이트
    }

    return (
        // Context.Provider로 전역 상태를 제공
        <TimeContext.Provider value={time}>
            <div>
                <h1>전세계 나라의 현재 시간</h1>
                <button onClick={updateTime}>시간 업데이트</button>
                <Time />
            </div>
        </TimeContext.Provider>
    )
}

function Time() {
    return (
        // Context.Consumer로 전역 상태를 사용
        <TimeContext.Consumer>
            {(time) => (
                <div>
                    <p>한국: {time.toLocaleString('ko-KR')}</p>
                    <p>미국: {time.toLocaleString('en-US')}</p>
                    <p>중국: {time.toLocaleString('zh-CN')}</p>
                </div>
            )}
        </TimeContext.Consumer>
    )
}

export default TimeApp

Context API 활용한 날씨 앱 만들기

// WeatherContext.js
import React, { createContext, useState, useEffect } from 'react'

const WeatherContext = createContext()

export function WeatherProvider({ children }) {
    const [weather, setWeather] = useState(null)

    useEffect(() => {
        fetch('https://api.openweathermap.org/data/2.5/weather?q=seoul&appid=YOUR_API_KEY&units=metric')
            .then((res) => res.json())
            .then((data) => {
                setWeather(data)
            })
    }, [])

    return <WeatherContext.Provider value={weather}>{children}</WeatherContext.Provider>
}

export default WeatherContext
// WeatherApp.js
import React from 'react'
import WeatherContext, { WeatherProvider } from './WeatherContext'

function WeatherApp() {
    return (
        <WeatherProvider>
            <div>
                <h1>날씨 앱</h1>
                <Weather />
            </div>
        </WeatherProvider>
    )
}

function Weather() {
    return (
        <WeatherContext.Consumer>
            {(weather) => (
                <div>
                    {weather ? (
                        <div>
                            <p>도시: {weather.name}</p>
                            <p>날씨: {weather.weather[0].main}</p>
                            <p>온도: {weather.main.temp}°C</p>
                        </div>
                    ) : (
                        <p>날씨 정보를 가져오는 중...</p>
                    )}
                </div>
            )}
        </WeatherContext.Consumer>
    )
}

export default WeatherApp
반응형
티스토리 친구하기