> For the complete documentation index, see [llms.txt](https://docs.sysout.co.kr/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.sysout.co.kr/web/develop-page/js/reactjs/cdn/component/functional-component/usereducer.md).

# useReducer

## useReducer

<mark style="color:red;">`useState`</mark>로 처리하기에 데이터나 작업의 종류가 복잡하고 느껴진다면 <mark style="color:red;">`useReducer`</mark>를 사용할 수 있다.

## useReducer 사용법

모듈 기반이 아닐 경우에는 React를 앞에 붙여서 사용해야 한다.

```jsx
const [state, dispatch] = React.useReducer(함수, {기본값});
```

## 예제 - useState를 사용한 달풍선 충전개수 선택 화면

```jsx
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/>);
```

{% embed url="<https://codepen.io/hiphop5782/pen/dyKzaJE>" %}

위 예제에서 수행하는 작업은 총 네 가지이다.

* -10 버튼을 눌러 달풍선 개수를 10개 감소
* -1 버튼을 눌러 달풍선 개수를 1개 감소
* +1 버튼을 눌러 달풍선 개수를 1개 증가
* +10 버튼을 눌러 달풍선 개수를 10개 증가

따라서 각각의 버튼에 대한 이벤트 처리 함수를 각각 준비하였다.

* minusTen - 달풍선 10개 감소 함수
* minusOne - 달풍선 1개 감소 함수
* plusOne - 달풍선 1개 증가 함수
* plusTen - 달풍선 10개 증가 함수

단순 작업이지만 이벤트가 다르기 때문에 처리 함수가 늘어나는 문제가 발생한다. 익명 함수 등으로 처리할 수 있지만 작업이 조금만 복잡해도 코드 작성이 어려워지는 문제가 발생한다.

<div align="left"><figure><img src="/files/GDQ46FR7g3XBxidt13bc" alt=""><figcaption></figcaption></figure></div>

## 예제 - useReducer를 사용한 달풍선 충전개수 선택 화면

```jsx
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/>);
```

{% embed url="<https://codepen.io/hiphop5782/pen/WNyEPMp>" %}

앞에 있는 useReducer 없이 구현한 달풍선 예제를 useReducer를 사용하여 변경하였다. useReducer는 다음과 같이 선언하였다.

```jsx
const [state, dispatch] = React.useReducer(reducer, {total:0});
```

React.useReducer에 처리 함수인 reducer와 데이터 객체를 넣고 생성한 결과를 **state**와 **dispatch**에 각각 할당한다. **state**는 데이터 상태 관리 객체이고, **dispatch**는 이벤트를 발생시키는 객체이다.

```jsx
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** 객체로 한다.

```jsx
dispatch({type:'minusTen'})//action.type이 'minusTen'인 작업 호출
dispatch({type:'minusOne'})//action.type이 'minusOne'인 작업 호출
dispatch({type:'plusOne'})//action.type이 'plusOne'인 작업 호출
dispatch({type:'plusTen'})//action.type이 'plusTen'인 작업 호출
```

<div align="left"><figure><img src="/files/DhuUdFiMvqx3Dqnhfglp" alt=""><figcaption></figcaption></figure></div>

## 예제 - 회원 가입 화면

```jsx
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/>);
```

{% embed url="<https://codepen.io/hiphop5782/pen/oNyemEO>" %}

useReducer를 사용하면 여러 개의 입력을 간소화하여 처리할 수 있다. 함수형 컴포넌트의 경우 **state** 객체를 사용할 수 없기 때문에 여러 개의 데이터를 입력에 따라 갱신해야 할 경우 작업이 많아질 수 있다. 이러한 문제들을 useReducer를 사용하여 해결할 수 있다.

```jsx
const [state, dispatch] = React.useReducer(reducer, {
    memberId:"",
    memberPw:"",
    memberNick:"",
    memberBirth:"",
    memberPhone:"",
    memberEmail:"",
});
```

우선 회원 정보가 입력될 변수 6개를 useReducer를 사용하여 선언한다. 그리고 변경을 처리할 함수인 reducer를 다음과 같이 생성한다.

```jsx
function reducer(state, action){
    return {
        ...state,
        [action.name]:action.value
    }
};
```

입력에 대한 이벤트를 다음과 같이 설정하면 자동 갱신이 이루어진다.

```jsx
<input name="memberId" onInput={inputProcessor}/>
```

```jsx
const inputProcessor = e=>{
    dispatch(e.target);
};
```

e.target은 이벤트가 발생한 태그 객체이며, 이를 **dispatch**를 통해 전달하여 **name**과 **value**를 추출해데이터를 갱신하는 구조이다. 입력된 항목을 제외한 나머지 값을 유지하기 위해 전개 연산자를 사용하여 기존 데이터를 유지하도록 `...state`를 추가해두었다.

<div align="left"><figure><img src="/files/DVpujvAr7A1eKfA9qOxX" alt=""><figcaption><p>아이디를 입력하는 경우 state의 변경과정</p></figcaption></figure></div>

{% hint style="info" %}

### class component와의 비교

reducer 함수만 보면 클래스 컴포넌트에서의 setState 형태와 유사하다.

```jsx
inputEventProcessor = e=>{
    this.setState(prev=>{
        ...prev,
        [e.target.name]:e.target.value
    });
};
```

{% endhint %}

## 예제 - 회원 가입 화면과 정규표현식 검사

```jsx
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/>);
```

{% embed url="<https://codepen.io/hiphop5782/pen/yLEoZKP>" %}

[#undefined](#undefined "mention")에 정규표현식을 추가하여 적합, 부적합을 판정하여 출력하는 예제이다.
