이번 포스팅에서 알아볼 것은 React에서 제공해주는 useContext 와 useReducer라는 내장 Hook이다.
< 목차 >
- useContext
- useReducer
1. useContext
useContext는 React에서 제공해주는 내장 Hook 중의 하나로 전역 상태 관리를 도와주는 함수이다. 일반적으로 React 애플리케이션에서는 데이터가 위에서 아래로 (즉, 부모로부터 자식에게) props를 통해 전달된다. 만약 애플리케이션 안의 여러 컴포넌트들에게 전해줘야 하는 props가 있다면 이러한 전달 방식은 상당히 번거로울 수 있다. 이 때 사용하는 것이 바로 context라는 것이다. context를 이용하면 트리 단계마다 명시적으로 props를 넘겨주지 않아도 많으 컴포넌트들이 데이터를 공유하도록 할 수 있다. 다시말해, context란 React 컴포넌트 트리 안에서 전역적(global)이라고 볼 수 있는 데이터를 공유할 수 있도록 고안된 방법이다.
// Context.jsx 파일
import React, { useState, createContext, useContext } from 'react'
export const Global = createContext()
// createContext() 메소드를 호출해서 Global이라는 context 객체를 만들어준다.
const D = () => {
const { name, setName } = useContext(Global)
// Global.Provider component에서 value로 전달한 값을 받기 위해서는
// useContext() 인자값으로 context 객체 자체를 넣어준다.
return (
<>
Hello context!! { name }
<p></p>
<button onClick={ ()=>{ setName('bitkunst2') } }>이름바꾸기</button>
</>
)
}
const C = () => {
return (
<>
<D />
</>
)
}
const B = () => {
return (
<>
<C />
</>
)
}
const A = () => {
return (
<>
<B />
</>
)
}
const Context = () => {
const [name, setName] = useState('bitkunst')
const obj = {
name,
setName
}
return (
<>
{/*
전역 상태를 관리할 Global.Provider component를 최상단에 위치시켜서 묶어준다.
value 값으로 전달할 데이터를 넣어준다. (반드시 value라는 이름으로 넘겨줘야 한다.)
*/}
<Global.Provider value={ obj }>
<A />
</Global.Provider>
</>
)
}
export default Context;
Context.jsx 파일 안에서 만들어진 component들 사이의 관계를 살펴보면 다음과 같다.
위와 같은 컴포넌트 트리에서 최상단에 위치한 부모 컴포넌트가 <D /> component에게 데이터를 전달하고 싶다면 Context => A => B => C => D 의 과정을 통해 props를 전달해야만 한다. 하지만 context를 이용하면 전역에서 상태를 관리할 수 있게 되기 때문에 <Context /> component에서 <D /> component에게 바로 데이터를 전달하는 게 가능하다. 다시말해, context를 사용하면 중간에 있는 컴포넌트들에게 props를 넘겨주지 않아도 된다.
context를 사용하기 위해 우선 const Global = createContext( )를 통해 Global이라는 이름의 context 객체를 만들어주었다. context 객체에는 상태, 리듀서 및 몇가지 내장함수들이 들어가 있다. 전역에서 상태를 관리해줄 Global이라는 context 객체를 만들었다면 Global.Provider 라는 이름으로 component를 호출하듯이 사용해주면 된다.
전역 상태 관리를 위해 <Global.Provider />를 컴포넌트 트리 최상단에 위치시킨 후 하위 컴포넌트들에게 전달하고 싶은 데이터를 value라는 이름의 props 값으로 넣어준다. 이 때 반드시 value라는 변수명을 사용해서 데이터를 전달해야 함에 유의하자.
// Context component
const Context = () => {
const [name, setName] = useState('bitkunst')
const obj = {
name,
setName
}
return (
<>
<Global.Provider value={obj}>
<A />
</Global.Provider>
</>
)
}
Context 함수 안에서 name이라는 상태와 해당 상태를 관리할 setName 함수를 만들었다. 그리고 name 변수와 setName 함수를 obj 객체 안에 넣어서 <Global.Provider />의 value라는 props 값으로 전달했다. 이제 Global 이라는 context에 들어간 obj 객체 데이터는 <Context /> component의 아무 자식 컴포넌트에서나 useContext( )를 사용해 그 값을 전달받을 수 있다.
useContext( )를 사용해서 데이터를 전달받는 방법은 다음과 같다.
const D = () => {
const { name, setName } = useContext(Global)
return (
<>
Hello context!! {name}
<p></p>
<button onClick={ ()=>{ setName('bitkunst2') } }>이름바꾸기</button>
</>
)
}
자식 컴포넌트에서 context를 이용해 데이터를 전달받기 위해서는 useContext Hook을 사용하면 되는데 useContext( )의 인자값으로 createContext( )로 만들어준 Global이라는 context를 넣어주면 된다. useContext( )의 인자값은 context 객체 그 자체여야 함에 유의하도록 하자. 현재 <Global.Provider />에서 객체 형태로 데이터를 전달해주었기 때문에 구조분해할당을 이용해 name 변수와 setName 함수를 가져왔다.
2. useReducer
React 공식문서에 따르면 useReducer는 useState의 대체 함수라고 명시되어 있다. 다수의 하위값을 포함하는 복합한 정적 로직을 만드는 경우나 다음 state가 이전 state에 의존적인 경우에 보통 useState 보다 useReducer를 선호한다고 한다.
useReducer는 useState와 마찬가지로 상태를 만들어주고 해당 상태를 변경할 수 있는 함수를 제공해준다. 즉, useReducer를 사용해도 useState처럼 상태를 만들고 변경하는 것이 가능해진다는 의미이다. 보통의 경우 상태를 변경하는 함수 및 코드들을 reducer라는 하나의 함수 안에 몰아넣고 싶을 때 useReducer를 사용한다. useReducer를 사용하면 component와 상태 업데이트 로직을 분리하여 component 외부에서도 상태 관리가 가능해지기 때문이다.
기본적인 사용법 및 작동원리는 다음과 같다.
const initialState = {
// 데이터
}
const reducer = (state, action) => {
// code block
}
const [state, dispatch] = useReducer(reducer, initialState)
useReducer Hook은 인자값으로 reducer 함수와 initialState 값을 받는다. 두번째 인자값으로 들어가는 initialState의 값이 state의 기본값으로 들어간다. 그리고 첫번째 인자값으로 들어가는 reducer 함수는 dispatch 함수에 의해 실행된다. 즉, dispatch 함수의 목적은 상태를 변경하기 위함이고 dispatch 함수가 실행되면 useReducer의 첫번째 인자값으로 들어간 reducer 함수가 실행된다.
reducer 함수는 state 와 action이라는 두 개의 인자값을 받는다. dispatch 함수가 reducer 함수의 첫번째 인자값으로 state를 넣어주고 dispatch 함수의 인자값이 reducer 함수의 두번째 인자값인 action으로 들어간다. 그리고 reducer 함수는 반드시 return 값이 존재해야 한다. return 값으로 객체를 반환하게 되는데 reducer 함수에서 return된 객체가 새로운 상태가 된다.
요약하면, dispatch 함수는 reducer 함수를 실행시키고 reducer 함수는 상태값을 변경한 후에 변경된 상태를 return한다. 이 때 dispatch의 인자값으로 넣어준 값을 reducer 함수에서 action으로 받을 수 있고 action을 이용하면 reducer 함수 안에서 조건에 따라 상태를 변경해서 return 하는 것이 가능해진다. 이러한 구조에 의해 상태를 변경하는 코드들을 reducer 함수 안에 모아놓고 관리할 수 있다.
실제로 useReducer 함수를 사용해서 카운터를 만들어보도록 하자.
import React, { useReducer } from 'react'
const INCREMENT = 'INCREMENT'
const DECREMENT = 'DECREMENT'
const HelloReducer = () => {
const initialState = {
count: 0
}
const reducer = (state, action) => {
switch (action.type) {
case DECREMENT :
return {
...state,
count: state.count - 1
}
case INCREMENT :
return {
...state,
count: state.count + 1
}
default :
return state
}
}
const [state, dispatch] = useReducer(reducer, initialState)
return (
<>
<h1>Hello useReducer!!</h1>
<p></p>
<h1>{state.count}</h1>
<p></p>
<button onClick={ () => dispatch({type: DECREMENT}) }>-</button>
<button onClick={ () => dispatch({type: INCREMENT}) }>+</button>
</>
)
}
export default HelloReducer;
<button>엘리먼트에 onClick 이벤트를 주고 클릭이 발생했을 때 dispatch 함수가 발동되도록 하였다. 그리고 dispatch의 인자값으로는 {type: DECREMENT} 와 {type: INCREMENT}를 넣어 reducer 함수의 action 인자값으로 받을 수 있도록 하였다. 참고로 보통의 경우 dispatch의 인자값으로 {type: " ", payload: " "} 와 같은 형식으로 데이터를 같이 넣어서 전달하는 경우가 많다. 마지막으로 reducer 함수 안에서는 인자값으로 받은 action을 사용해 switch문으로 type에 따라 각기 다른 처리를 해주도록 하였다.
잘 동작하는 것을 확인할 수 있다.
'React' 카테고리의 다른 글
React - Custom Hook (0) | 2022.05.01 |
---|---|
React - useCallback & useMemo (0) | 2022.04.30 |
React - Lifecycle & useEffect (0) | 2022.04.26 |
React - 함수 컴포넌트 & useState (2) | 2022.04.26 |
create-react-app 없이 React 구동하기 (0) | 2022.04.21 |