useReducer

useReducer

useState로 처리하기에 데이터나 작업의 종류가 복잡하고 느껴진다면 useReducer를 사용할 수 있다.

useReducer 사용법

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

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

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

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개 감소

  • -1 버튼을 눌러 달풍선 개수를 1개 감소

  • +1 버튼을 눌러 달풍선 개수를 1개 증가

  • +10 버튼을 눌러 달풍선 개수를 10개 증가

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

  • minusTen - 달풍선 10개 감소 함수

  • minusOne - 달풍선 1개 감소 함수

  • plusOne - 달풍선 1개 증가 함수

  • plusTen - 달풍선 10개 증가 함수

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

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

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는 다음과 같이 선언하였다.

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

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

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

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

예제 - 회원 가입 화면

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를 사용하여 해결할 수 있다.

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

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

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

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

<input name="memberId" onInput={inputProcessor}/>
const inputProcessor = e=>{
    dispatch(e.target);
};

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

class component와의 비교

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

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

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

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

예제 - 회원 가입 화면에 정규표현식을 추가하여 적합, 부적합을 판정하여 출력하는 예제이다.

Last updated