useReducer
훅은 useState
훅처럼 상태를 관리하기 위해 사용하는 훅입니다.
useReducer
은 useState
훅의 대체로 사용 가능하며 상태가 "복잡한 경우"에 사용합니다.
import { useReducer } from 'react';
const [state, dispatchFn] = useReducer(reducerFn, initialState[, initFn]);
useReducer
훅은 두 개의 요소를 갖는 배열을 반환합니다.
반환된 배열의 첫 번째 요소는 "최신 상태값(state)" 이고, 두 번째 요소는 "dispatch 함수(dispatchFn)" 입니다.
useReducer
훅의 반환값인 배열의 첫 번째 요소로 작성한 변수에는 언제나 "최신 상태값"을 리액트에게 전달받습니다.
useReducer
훅의 반환값인 배열의 두 번째 요소 dispatchFn 함수는 상태를 업데이트하기 위한 "정보(action)"을 인수로 전달하여 호출하면 리액트가 useReducer
훅의 첫 번재 인수로 전달한 "reducerFn 함수를 호출"합니다.
즉, dispatch 함수를 호출하면 리액트가 useReducer 훅 호출시 첫 번째 인수로 전달한 reducer 함수를 실행합니다.
useReducer
훅의 첫 번째 인수로 전달되는 reducerFn 함수는 인수로 "가장 최신 상태(state)"와 dispatchFn 함수 호출시 인수로 전달한 "action"을 전달받아 호출되고 반환되는 값과 기존 상태를 "단순 비교"하여 상태를 변경합니다.
참고로 reducerFn 함수가 컴포넌트 내부에서 선언된 식별자를 사용하지 않는 경우 컴포넌트 외부에서 정의하는 것을 권장힙니다.
만약 컴포넌트 내부에서 정의한다면 컴포넌트가 재평가될 때마다 reducerFn 정의가 평가되어 매번 새로운 함수 객체가 생성하게 되어 불필요한 동작이 발생하게 됩니다.
action은 상태를 업데이트하기 위한 정보를 의미하며 문자열, 숫자 등 모두 전달 가능하지만 일반적으로는 "객체"를 전달합니다.
일반적으로 action 객체에는 type
이라는 프로퍼티에 "동작(상황, 조건)"을 지정하여 상태 변경을 여러 상황으로 분기하여 업데이트 가능합니다. type 프로퍼티 이외 추가적인 정보도 프로퍼티로 전달할 수 있습니다.
useReducer
훅의 두 번째 인수로 전달한 값은 "초기 상태값"을 전달하며, 세 번째 인수로 전달한 initFn은 초기 상태를 계산하는 함수로 옵션으로 전달 가능합니다.
초기 상태값은 상태값이 한 번도 초기화된 적이 없는 경우에 초기값으로 초기화합니다.
dispatchFn에게 action 객체를 전달하면 리액트가 reducerFn을 호출
리액트가 reducerFn을 실행할 때 인수로 "가장 최근 상태"와 "action 객체"가 reducerFn 함수의 인수로 전달하면서 실행합니다.
reducerFn 내부에서는 action 객체의 type 프로퍼티를 이용하여 여러 조건(상황)으로 분기하여 새로운 상태값을 반환값으로 작성하면 반환값과 기존 상태값을 단순 비교하여 일치하지 않는 경우에 반환값으로 상태값을 대체하고 컴포넌트를 재평가하고 리렌더링합니다.
// Login.jsx
import React, { useReducer } from 'react';
// reducerFn 함수 역할, 인수로 기존 상태와 action을 전달받음
const emailReducer = (state, action) => {
if (action.type === 'USER_INPUT') {
return { value: action.val, isValid: action.val.includes('@') };
}
if (action.type === 'INPUT_BLUR') {
return { value: state.value, isValid: state.value.includes('@') };
}
return { value: '', isValid: null };
};
const Login = props => {
const [emailState, dispatchEmail] = useReducer(emailReducer, { value: '', isValid: null });
const emailChangeHandler = event => {
// dispatchEamil 함수에 객체를 전달하면서 호출하면 리액트가 emailReducer가 호출한다
dispatchEamil({ type: 'USER_INPUT', val: event.target.value });
};
const validateEmailHandler = () => {
dispatchEamil({ type: 'INPUT_BLUR' });
};
return (
<form>
<input type="email"
value={emailState.value}
onChange={emailChangeHandler}
onBlur={validateEmailHandler}
/>
</form>
);
};
useRedcer
훅을 통해 input 입력 필드의 value와 isValid 상태를 emailState 하나로 관리하도록 만들었습니다. 이후 dispatchEmail을 통해서 상태를 업데이트할 수 있습니다.
서로 관련된 여러 상태들을 "하나의 객체"로 관리하는 경우
예를 들어, 앞에서 살펴본 input의 사용자 입력 값과 데이터 유효성을 하나의 객체로서 관리하는 경우
"다른 상태값에 의존하여 상태값을 변경"해야하는 경우
상태값은 비동기로 업데이트되기 때문에 기존 useState
훅을 사용하는 경우 상태 변경 함수의 인수로 콜백 함수를 전달하여 해결할 수 있었지만 콜백 함수의 경우 인수로 해당 상태값을 전달받기 때문에 다른 상태값에 의존하여 변경되는 경우에는 useReducer
훅을 사용하여 해결할 수 있다.
useRedcer
훅을 사용하면 "상태를 업데이트하는 복잡한 로직"을 reducerFn 함수 내부에 포함시킬 수 있습니다. 그리고 reducerFn은 인수로 "가장 최신의 상태"를 전달받는 것이 보장됩니다. 그리고 reducerFn을 컴포넌트 외부에 정의할 수 있으며 이렇게 정의한 reducerFn을 다른 컴포넌트 함수 안으로도 전달할 수 있습니다.