useReducer
useState
로 처리하기에 데이터나 작업의 종류가 복잡하고 느껴진다면 useReducer
를 사용할 수 있다.
useReducer 사용법
모듈 기반이 아닐 경우에는 React를 앞에 붙여서 사용해야 한다.
Copy const [state, dispatch] = React.useReducer(함수, {기본값});
예제 - useState를 사용한 달풍선 충전개수 선택 화면
Copy function MainComponent(){
const [total, setTotal] = React.useState(0);
const minusOne = e=>{
if(total < 1) return;
setTotal(total - 1);
};
const minusTen = e=>{
if(total < 10) {
setTotal(0);
}
else {
setTotal(total - 10);
}
};
const plusOne = e=>{
setTotal(total + 1);
};
const plusTen = e=>{
setTotal(total + 10);
}
return (
<>
<h1>충전할 달풍선 개수를 선택하세요</h1>
<button onClick={minusTen}>-10개</button>
<button onClick={minusOne}>-1개</button>
<button onClick={plusOne}>+1개</button>
<button onClick={plusTen}>+10개</button>
<hr/>
<h2>충전할 총 달풍선 개수 : {total}개</h2>
</>
);
}
const app = ReactDOM.createRoot(document.querySelector("#app"));
app.render(<MainComponent/>);
위 예제에서 수행하는 작업은 총 네 가지이다.
-10 버튼을 눌러 달풍선 개수를 10개 감소
+10 버튼을 눌러 달풍선 개수를 10개 증가
따라서 각각의 버튼에 대한 이벤트 처리 함수를 각각 준비하였다.
단순 작업이지만 이벤트가 다르기 때문에 처리 함수가 늘어나는 문제가 발생한다. 익명 함수 등으로 처리할 수 있지만 작업이 조금만 복잡해도 코드 작성이 어려워지는 문제가 발생한다.
예제 - useReducer를 사용한 달풍선 충전개수 선택 화면
Copy function MainComponent(){
const reducer = (state, action) => {
switch(action.type) {
case 'plusOne': return {total:state.total+1};
case 'plusTen': return {total:state.total+10};
case 'minusOne': return {total:Math.max(0, state.total-1)};
case 'minusTen': return {total:Math.max(0, state.total-10)};
}
};
const [state, dispatch] = React.useReducer(reducer, {total:0});
return (
<>
<h1>충전할 달풍선 개수를 선택하세요</h1>
<button onClick={()=>dispatch({type:'minusTen'})}>-10개</button>
<button onClick={()=>dispatch({type:'minusOne'})}>-1개</button>
<button onClick={()=>dispatch({type:'plusOne'})}>+1개</button>
<button onClick={()=>dispatch({type:'plusTen'})}>+10개</button>
<hr/>
<h2>충전할 총 달풍선 개수 : {state.total}개</h2>
</>
);
}
const app = ReactDOM.createRoot(document.querySelector("#app"));
app.render(<MainComponent/>);
앞에 있는 useReducer 없이 구현한 달풍선 예제를 useReducer를 사용하여 변경하였다. useReducer는 다음과 같이 선언하였다.
Copy const [state, dispatch] = React.useReducer(reducer, {total:0});
React.useReducer에 처리 함수인 reducer와 데이터 객체를 넣고 생성한 결과를 state 와 dispatch 에 각각 할당한다. state 는 데이터 상태 관리 객체이고, dispatch 는 이벤트를 발생시키는 객체이다.
Copy const reducer = (state, action) => {
switch(action.type) {
case 'plusOne': return {total:state.total+1};
case 'plusTen': return {total:state.total+10};
case 'minusOne': return {total:Math.max(0, state.total-1)};
case 'minusTen': return {total:Math.max(0, state.total-10)};
}
};
처리 함수인 reducer에서는 요청 인자인 action.type 에 따라 상태 객체를 변경하고 이를 반환하면 자동 갱신 된다. reducer 호출은 dispatch 객체로 한다.
Copy dispatch({type:'minusTen'})//action.type이 'minusTen'인 작업 호출
dispatch({type:'minusOne'})//action.type이 'minusOne'인 작업 호출
dispatch({type:'plusOne'})//action.type이 'plusOne'인 작업 호출
dispatch({type:'plusTen'})//action.type이 'plusTen'인 작업 호출
예제 - 회원 가입 화면
Copy function MainComponent(){
function reducer(state, action){
return {
...state,
[action.name]:action.value
}
};
const [state, dispatch] = React.useReducer(reducer, {
memberId:"",
memberPw:"",
memberNick:"",
memberBirth:"",
memberPhone:"",
memberEmail:"",
});
const inputProcessor = e=>{
dispatch(e.target);
};
const submitProcessor = e=>{
e.preventDefault();
//e.target.submit();
};
return (
<>
<h1>회원 정보 입력</h1>
<form onSubmit={submitProcessor}>
ID : <input type="text" name="memberId" onInput={inputProcessor}/><br/>
Password : <input type="password" name="memberPw" onInput={inputProcessor}/><br/>
Nickname : <input type="text" name="memberNick" onInput={inputProcessor}/><br/>
Birth : <input type="text" name="memberBirth" onInput={inputProcessor}/><br/>
Phone : <input type="tel" name="memberPhone" onInput={inputProcessor}/><br/>
E-mail : <input type="email" name="memberEmail" onInput={inputProcessor}/><br/>
<hr/>
<ul>
<li>ID : {state.memberId}</li>
<li>Password : {state.memberPw}</li>
<li>Nickname : {state.memberNick}</li>
<li>Birth : {state.memberBirth}</li>
<li>Phone : {state.memberPhone}</li>
<li>E-mail : {state.memberEmail}</li>
</ul>
<button type="submit">등록</button>
</form>
</>
);
}
const app = ReactDOM.createRoot(document.querySelector("#app"));
app.render(<MainComponent/>);
useReducer를 사용하면 여러 개의 입력을 간소화하여 처리할 수 있다. 함수형 컴포넌트의 경우 state 객체를 사용할 수 없기 때문에 여러 개의 데이터를 입력에 따라 갱신해야 할 경우 작업이 많아질 수 있다. 이러한 문제들을 useReducer를 사용하여 해결할 수 있다.
Copy const [state, dispatch] = React.useReducer(reducer, {
memberId:"",
memberPw:"",
memberNick:"",
memberBirth:"",
memberPhone:"",
memberEmail:"",
});
우선 회원 정보가 입력될 변수 6개를 useReducer를 사용하여 선언한다. 그리고 변경을 처리할 함수인 reducer를 다음과 같이 생성한다.
Copy function reducer(state, action){
return {
...state,
[action.name]:action.value
}
};
입력에 대한 이벤트를 다음과 같이 설정하면 자동 갱신이 이루어진다.
Copy <input name="memberId" onInput={inputProcessor}/>
Copy const inputProcessor = e=>{
dispatch(e.target);
};
e.target은 이벤트가 발생한 태그 객체이며, 이를 dispatch 를 통해 전달하여 name 과 value 를 추출해데이터를 갱신하는 구조이다. 입력된 항목을 제외한 나머지 값을 유지하기 위해 전개 연산자를 사용하여 기존 데이터를 유지하도록 ...state
를 추가해두었다.
class component와의 비교
reducer 함수만 보면 클래스 컴포넌트에서의 setState 형태와 유사하다.
Copy inputEventProcessor = e=>{
this.setState(prev=>{
...prev,
[e.target.name]:e.target.value
});
};
예제 - 회원 가입 화면과 정규표현식 검사
Copy function MainComponent(){
function reducer(state, action){
const regex = state[action.name+'Regex'];
const judge = regex.test(action.value);
return {
...state,//기존 state는 유지하도록 처리
[action.name]:action.value,
[action.name+'Valid']:judge
}
};
const [state, dispatch] = React.useReducer(reducer, {
memberId:"",memberIdValid:false,memberIdRegex:/^[a-z][a-z0-9]{7,19}$/,
memberPw:"",memberPwValid:false,memberPwRegex:/^[a-zA-Z0-9!@#$]{8,16}$/,
memberNick:"",memberNickValid:false,memberNickRegex:/^[가-힣0-9]{2,10}$/,
memberBirth:"",memberBirthValid:false,memberBirthRegex:/^(19|20)[0-9]{2}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/,
memberPhone:"",memberPhoneValid:false,memberPhoneRegex:/^010[0-9]{8}$/,
memberEmail:"",memberEmailValid:false,memberEmailRegex:/^[a-z][a-z0-9]{7,19}@(google\.com|naver\.com)$/
});
const inputProcessor = e=>{
dispatch(e.target);
};
const submitProcessor = e=>{
e.preventDefault();
//e.target.submit();
};
return (
<>
<h1>회원 정보 입력</h1>
<form onSubmit={submitProcessor}>
ID : <input type="text" name="memberId" onInput={inputProcessor}/><br/>
Password : <input type="password" name="memberPw" onInput={inputProcessor}/><br/>
Nickname : <input type="text" name="memberNick" onInput={inputProcessor}/><br/>
Birth : <input type="text" name="memberBirth" onInput={inputProcessor}/><br/>
Phone : <input type="tel" name="memberPhone" onInput={inputProcessor}/><br/>
E-mail : <input type="email" name="memberEmail" onInput={inputProcessor}/><br/>
<hr/>
<ul>
<li>ID : {state.memberId} ({state.memberIdValid?'적합':'부적합'})</li>
<li>Password : {state.memberPw} ({state.memberPwValid?'적합':'부적합'})</li>
<li>Nickname : {state.memberNick} ({state.memberNickValid?'적합':'부적합'})</li>
<li>Birth : {state.memberBirth} ({state.memberBirthValid?'적합':'부적합'})</li>
<li>Phone : {state.memberPhone} ({state.memberPhoneValid?'적합':'부적합'})</li>
<li>E-mail : {state.memberEmail} ({state.memberEmailValid?'적합':'부적합'})</li>
</ul>
<button type="submit">등록</button>
</form>
</>
);
}
const app = ReactDOM.createRoot(document.querySelector("#app"));
app.render(<MainComponent/>);
예제 - 회원 가입 화면 에 정규표현식을 추가하여 적합, 부적합을 판정하여 출력하는 예제이다.