redux를 공부하면서 작성한 지난 포스팅에서 redux를 사용하는 이유와 더는 필수가 아니게 된 이유에 대해 공부했다. 리액트 훅으로 리덕스를 대체할 수 있는 방법에 대해 공부할 차례다!
리액트는 데이터를 전달하기 위해 상위 컴포넌트에서 각각의 하위 컴포넌트를 모두 거쳐야 했다.
이것을 prop drilling이라고 한다.
이것을 해결하기 위해 리액트 컴포넌트와 독립적으로 움직이는 리덕스 스토어를 이용했지만 리덕스는 굉장히 번거로운 점이 많고 세팅에도 필요한 과정이 많았다. 더군다나 이제는 리액트 훅 중 useReducer, useContext를 사용할 수 있게 되어 redux가 필수적으로 필요하지 않게 되었다.
useState를 사용하면 일일히 많은 상태를 업데이트하기 위한 코드를 작성해 가독성이 급격하게 떨어지게 된다.
const [state, dispatch] = useReducer(reducer, initialArg, init);
useState의 대체 함수이다.
(state, action) => newState
의 형태로 reducer를 받는다.
즉, reducer, action을 인자로 받는다.
1. action객체가 dispatch되면
2. reducer 함수에 state와 action객체를 매개변수로 실행한다.
3. 현재 state를 action type을 기준으로 업데이트한 뒤 반환한다.
useContext는 React.createContext가 반환한 context 객체를 인자로 받는다.
그 후 useContext가 호출된 컴포넌트로부터 가장 가까이 있는 해당 context의 Provider를 찾아서 그 Provider의 value를 반환한다.
즉, 최상위 컴포넌트를 context.Provider로 감싸준다면 코드 전역에서 해당 context를 참조할 수 있다.
또한 useContex를 호출한 컴포넌트는 해당 context의 값이 변경되면 항상 리렌더링 된다.
다만, context에 담긴 값을 하위 컴포넌트에서 수정할 수는 없다.
useReducer와 useContext를 조합하면 redux처럼 사용할 수 있다.
useReducer로 state와 dispatch를 생성한 다음,
useContext를 이용해 하위 컴포넌트가 이 값에 접근하고 수정할 수 있도록 만들어준다.
export const countStateContext = createContext(0); // 3
export const countDispatchContext = createContext<Dispatch<CountActionT> | null>(null); // 3
const App = () => {
const [state, dispatch] = useReducer(countReducer, initialState); // 2
return (
<countDispatchContext.Provider value={dispatch}> // 4
<countStateContext.Provider value={state.count}>
<Count />
</countStateContext.Provider>
</countDispatchContext.Provider>
);
};
App의 모든 하위컴포넌트에서 countState와 countDispatch를 사용할 수 있다.
import React, { useContext } from 'react'
import { countDispatchContext, countStateContext } from '../App'
const Count = () => {
const count = useContext(countStateContext); // 5
const dispatch = useContext(countDispatchContext);
만든 context를 임포트해와 useContext에 인자로 전달해준다.
return (
<div>
<p>Test count: {count}</p>
<button onClick={() => {dispatch({type: 'INCREASE'})}}>+</button>
</div>
)
}