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. Context.Consumer 컴포넌트를 사용하여 Context의 값을 사용합니다.

Context 사용하기

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

  • 주문 페이지

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

const OrderContext = React.createContext()

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

    return (
        <OrderContext.Provider value={{ order, setOrder }}>
            <Order />
            <Total />
        </OrderContext.Provider>
    )
}

function Order() {
    const { order, setOrder } = useContext(OrderContext)

    const handleChange = (e) => {
        setOrder({ ...order, option: e.target.value })
    }

    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 예제

// 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

- 할 일 관리 앱 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()

// 커스텀 훅 생성
export function useTodoState() {
    return useContext(TodoStateContext)
}

export function useTodoDispatch() {
    return useContext(TodoDispatchContext)
}

// 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>
    )
}
반응형
티스토리 친구하기