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 />);
예제 - 회원 가입 화면 에 정규표현식을 추가하여 적합, 부적합을 판정하여 출력하는 예제이다.