본문 바로가기
SPA/React

[React] Hooks - useReducer

by J4J 2021. 9. 27.
300x250
반응형

안녕하세요. J4J입니다.

 

이번 포스팅은 hooks의 useReducer에 대해 적어보는 시간을 가져보려고 합니다.

 

 

 

useReducer란?

 

useReducer는 hooks에서 useState와 동일하게 데이터 변화에 따른 반응성을 제공해주는 역할을 담당합니다.

 ※ [React] Hooks - useState

 

하지만 useState와의 차이점은 명확합니다.

 

 

 

일반적으로 리액트를 이용하여 개발하다 보면 보통 useState를 이용하여 데이터에 반응성을 제공해주고는 합니다.

 

여기서 만약 사용해야 되는 하위 변수값들이 많을 경우 어떻게 될까요?

 

예를 들어 A라는 객체에 해당되는 변수가 10개, B라는 객체에 해당되는 변수가 10개 있다면 각각의 useState들이 어떤 목적을 위한 데이터인지가 점점 헷갈리기 시작하며 관리하기 어려워지게 됩니다.

 

 

 

위의 상황에서 현명하게 대처할 수 있는 것이 useReducer의 사용입니다.

 

useReducer를 사용하게 되면 서로 같이 사용되는 변수들끼리 묶어주며 관리하기 수월하게 만들어줍니다.

 

하지만 그에 따른 대가를 치르기는 해야됩니다.

 

useReducer를 사용하면 useState를 사용하는 것보다 코드의 양이 많아진다는 단점을 가지고 있습니다.

 

 

 

리액트에서 리덕스를 사용해보신 분들이라면 useReducer의 사용방법에 대해 매우 익숙하실 겁니다.

 

왜냐하면 리덕스 사용방법과 동일하기 때문이죠.

 ※ [React] 함수형 컴포넌트에서 Redux 사용하기

 ※ [React] 함수형 컴포넌트 + 타입스크립트 환경에서 Redux 사용하기

 

useReducer와 관련된 간단한 예시를 보며 사용방법을 확인해보겠습니다.

 

 

 

useState를 사용할 경우

 

먼저 useState를 사용할 경우에 대해 알아보겠습니다.

 

간단한 예시로 다음과 같이 실행될 수 있는 화면을 만들어 보겠습니다.

 

실행 결과

 

 

 

간단히 설명을 드리면 두 명의 학생에 대한 정보를 입력할 수 있고 초기화 버튼을 누르면 각각 학생들의 정보를 모두 초기화시킵니다.

 

useState를 사용하여 구현하게 되면 다음과 같이 구현할 수 있습니다.

 

 

반응형

 

 

import * as React from 'react';

const App = (): JSX.Element => {

    // 타입 스크립트 사용할 경우
    const [name1, setName1] = React.useState<string>('');
    const [age1, setAge1] = React.useState<number | ''>('');
    const [name2, setName2] = React.useState<string>('');
    const [age2, setAge2] = React.useState<number | ''>('');

    // 타입 스크립트 사용하지 않을 경우
    // const [name1, setName1] = React.useState('');
    // const [age1, setAge1] = React.useState('');
    // const [name2, setName2] = React.useState('');
    // const [age2, setAge2] = React.useState('');

    // 학생 1 정보 초기화
    const init1 = () => {
        setName1('');
        setAge1('');
    }

    // 학생 2 정보 초기화
    const init2 = () => {
        setName2('');
        setAge2('');
    }

    const style = {
        marginBottom: '10px',
    }
    
    return (
        <>
            <div>
                <table style={style}>
                    <thead>
                        <tr>
                            <td>학생 1</td>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td>
                                <span>이름</span>
                            </td>
                            <td>
                                <input type="text" value={name1} onChange={(e) => setName1(e.target.value)}/>
                            </td>
                        </tr>

                        <tr>
                            <td>
                                <span>나이</span>
                            </td>
                            <td>
                                <input type="number" value={age1} onChange={(e) => setAge1(e.target.value ? Number(e.target.value) : '')}/>
                            </td>
                        </tr>
                    </tbody>
                </table>

                <button onClick={init1} style={style}>초기화</button>
            </div>

            <div>
                <table style={style}>
                    <thead>
                        <tr>
                            <td>학생 2</td>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td>
                                <span>이름</span>
                            </td>
                            <td>
                                <input type="text" value={name2} onChange={(e) => setName2(e.target.value)}/>
                            </td>
                        </tr>

                        <tr>
                            <td>
                                <span>나이</span>
                            </td>
                            <td>
                                <input type="number" value={age2} onChange={(e) => setAge2(e.target.value ? Number(e.target.value) : '')}/>
                            </td>
                        </tr>
                    </tbody>
                </table>

                <button onClick={init2} style={style}>초기화</button>
            </div>
        </>
    )
}

export default App;

 

 

 

학생이 구분됨에 따라 각각의 학생과 관련된 이름, 나이에 대한 값들도 구분을 해줘야 합니다.

 

만약 동일한 변수 이름을 사용하지 않더라도 useState를 사용하여 여러 변수들을 나열하게 된다면 어떤 데이터가 어디에 사용되는 것인지 헷갈려할 여지가 충분히 존재합니다.

 

 

 

useReducer를 사용할 경우

 

이번엔 useReducer를 사용하여 위와 동일한 결과가 나오도록 해보겠습니다.

 

 

728x90

 

 

import * as React from 'react';

const App = (): JSX.Element => {

    // 타입 스크립트 사용할 경우
    // 인터페이스
    interface Iuser {
        name: string;
        age: number | '';
    }

    // 초기화
    const userInit: Iuser = {
        name: '',
        age: '',
    }

    // type
    const init = 'init';
    const setName = 'setName';
    const setAge = 'setAge';

    // action 인터페이스
    interface IinitAction {
        type: typeof init;
    }

    interface IsetNameAction {
        type: typeof setName;
        payload: string;
    }

    interface IsetAgeAction {
        type: typeof setAge;
        payload: number | '';
    }

    type Iaction = IinitAction | IsetNameAction | IsetAgeAction;

    // action
    const initAction = (): IinitAction => {
        return {
            type: init,
        }
    }

    const setNameAction = (res: string): IsetNameAction => {
        return {
            type: setName,
            payload: res,
        }
    }

    const setAgeAction = (res: number | ''): IsetAgeAction => {
        return {
            type: setAge,
            payload: res,
        }
    }

    // reducer
    const userReducer = (state: Iuser, action: Iaction) => {
        switch(action.type) {
            case init: {
                return {
                    ...userInit,
                }
            }
            case setName: {
                return {
                    ...state,
                    name: (action as IsetNameAction).payload,
                }
            }

            case setAge: {
                return {
                    ...state,
                    age: (action as IsetAgeAction).payload,
                }
            }

            default: {
                return {
                    ...state,
                }
            }
        }
    }

    // 타입 스크립트 사용하지 않을 경우
    // // 초기화
    // const userInit = {
    //     name: '',
    //     age: '',
    // }

    // // type
    // const init = 'init';
    // const setName = 'setName';
    // const setAge = 'setAge';

    // // action
    // const initAction = () => {
    //     return {
    //         type: init,
    //     }
    // }

    // const setNameAction = (res) => {
    //     return {
    //         type: setName,
    //         payload: res,
    //     }
    // }

    // const setAgeAction = (res) => {
    //     return {
    //         type: setAge,
    //         payload: res,
    //     }
    // }

    // // reducer
    // const userReducer = (state, action) => {
    //     switch(action.type) {
    //         case init: {
    //             return {
    //                 ...userInit,
    //             }
    //         }
    //         case setName: {
    //             return {
    //                 ...state,
    //                 name: action.payload,
    //             }
    //         }

    //         case setAge: {
    //             return {
    //                 ...state,
    //                 age: action.payload,
    //             }
    //         }

    //         default: {
    //             return {
    //                 ...state,
    //             }
    //         }
    //     }
    // }

    const [user1, user1Dispatch] = React.useReducer(userReducer, userInit); // [변수, dispatch] = useRedcer(리듀서, 초기값)
    const [user2, user2Dispatch] = React.useReducer(userReducer, userInit); // [변수, dispatch] = useRedcer(리듀서, 초기값)

    const style = {
        marginBottom: '10px',
    }

    return (
        <>
            <div>
                <table style={style}>
                    <thead>
                        <tr>
                            <td>학생 1</td>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td>
                                <span>이름</span>
                            </td>
                            <td>
                                <input type="text" value={user1.name} onChange={(e) => user1Dispatch(setNameAction(e.target.value))}/>
                            </td>
                        </tr>

                        <tr>
                            <td>
                                <span>나이</span>
                            </td>
                            <td>
                                <input type="number" value={user1.age} onChange={(e) => user1Dispatch(setAgeAction(e.target.value ? Number(e.target.value) : ''))}/>
                            </td>
                        </tr>
                    </tbody>
                </table>

                <button onClick={() => user1Dispatch(initAction())} style={style}>초기화</button>
            </div>

            <div>
                <table style={style}>
                    <thead>
                        <tr>
                            <td>학생 2</td>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td>
                                <span>이름</span>
                            </td>
                            <td>
                                <input type="text" value={user2.name} onChange={(e) => user2Dispatch(setNameAction(e.target.value))}/>
                            </td>
                        </tr>

                        <tr>
                            <td>
                                <span>나이</span>
                            </td>
                            <td>
                                <input type="number" value={user2.age} onChange={(e) => user2Dispatch(setAgeAction(e.target.value ? Number(e.target.value) : ''))}/>
                            </td>
                        </tr>
                    </tbody>
                </table>

                <button onClick={() => user2Dispatch(initAction())} style={style}>초기화</button>
            </div>
        </>
    )
}

export default App;

 

 

 

 

코드를 보면 user1과 user2로 나누어 서로 그룹화되어 관리하기 용이해진다는 것을 확인할 수 있습니다.

 

하지만 코드가 길어지는 것도 확인이 가능하고 심지어 타입 스크립트까지 사용해버린다면 더욱 크게 느껴지게 됩니다.

 

그렇지만 개인적인 경험으로는 useState를 남발하는 것보다는 개발 및 유지 보수하기 더 편하게 느껴졌기 때문에 다수의 변수를 사용해야 되는 상황에서는 useReducer 사용을 권장드립니다.

 

 

 

 

 

 

이상으로 hooks의 useReducer에 대해 간단하게 알아보는 시간이었습니다.

 

읽어주셔서 감사합니다.

 

 

 

728x90
반응형

'SPA > React' 카테고리의 다른 글

[React] recoil 사용하기  (0) 2021.10.24
[React] React.memo로 최적화하기  (0) 2021.09.30
[React] Hooks - useMemo  (0) 2021.09.22
[React] Hooks - useCallback  (0) 2021.09.15
[React] Hooks - useRef  (0) 2021.09.13

댓글