전체 방문자
오늘
어제
  • 전체 글
    • HTML
    • CSS
    • Javascript
    • React
    • Typescript
    • Next.js
    • Webpack
    • Vue.js
    • Git & GitHub
    • Error
    • Study
    • 개발 일지✨

블로그 메뉴

  • 💡
  • ⚙️
hELLO · Designed By 정상우.
하루

Home

useReducer
React

useReducer

2022. 5. 12. 03:07

[React/리액트] useReducer 를 사용하여 상태 업데이트 로직 분리하기

 

상태 관리 Hook 함수

  • useState: 설정하고 싶은 새로운 상태를 직접 지정하여 상태를 업데이트
  • useReducer: 컴포넌트의 상태 업데이트 로직을 컴포넌트에서 분리할 수 있다

useState 와 다르게 상태 업데이트 로직을 바깥에 작성할 수 있고, 다른 파일에 작성 후 불러와서 사용할 수도 있다.

현재 컴포넌트가 아닌 다른 곳에 state 를 저장하고 싶을 때 유용하다.

 

reducer

reducer 는 현재 상태와 액션 객체를 파라미터로 받아와서 새로운 상태를 반환해주는 함수이다. (상태 업데이트 함수)

반환해주는 상태는 곧 컴포넌트가 지닐 새로운 상태가 된다. 새로운 상태를 만들 때는 불변성을 지켜주어야 한다.

function reducer(state, action) {
  // 새로운 상태를 만드는 로직
  // const nextState = ...
  return nextState;
}
function reducer(state, action) {
    switch(action.type) {
        case 'INCREMENT':       // action type 이 INCREMENT 인 경우 기존상태 +1
            return state + 1;
        case 'DECREMENT':       // action type 이 DECREMENT 인 경우 기존상태 -1
            return state - 1;
        default: 
            return state;
    }
};

여기서 action 은 업데이트를 위한 정보로 객체의 형태는 자유이며 주로 type 값을 지니고 있다.

이 객체를 기반으로 참조하여 업데이트 한다.

 

useReducer 사용법

첫번째 파라미터는 reducer 함수이고, 두번째 파라미터는 초기상태이다.

const [state, dispatch] = useReducer(reducer, initialState);
  • state: 앞으로 컴포넌트에서 사용할 수 있는 상태를 가리킨다.
  • dispatch: 액션을 발생시키는 함수
  • initialState: 객체. 초기 정보를 담고 있는 useReducer 에게 전달하는 객체

 

dispatch 함수의 경우 다음과 같이 사용한다.

dispatch({ type: 'INCREMENT' })

 

useReducer vs useState

어떨 때 useReducer 를 쓰고 어떨 때 useState 를 써야하는지에는 정해진 답은 없다.

  • useState: 컴포넌트에서 관리하는 값이 하나이고, 그 값이 단순한 숫자, 문자열, boolean 값일 때
  • useReducer: 컴포넌트에서 관리하는 값이 여러개가 되어서 상태의 구조가 복잡할 때

setUsers, setInputs ... 등등 set 함수를 이용해서 관리하는 컴포넌트들이 늘어날때 useReducer로 관리하는 것을 고려해보면 좋을 것 같다.


예제

1. useReducer 를 사용하기에 앞서 먼저 reducer 함수를 생성해준다. 

function reducer(state, action) {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1;
    case 'DECREMENT':
      return state - 1;
    default:
      throw new Error('Unhandled action');
  }
}

2. 초기상태를 0으로 한 useReducer 함수를 만들어준다.

const [number, dispatch] = useReducer(reducer, 0);
const onIncrease = () => {
  setNumber(prevNumber =>  + prevNumber + 1);
}

const onDecrease = () => {
  setNumber(prevNumber => prevNumber - 1);
}

이전에 useState를 이용해서 set 함수로 증가와 감소를 위와 같이 작성해주었따면, useReducer 함수에서는 dispatch 를 이용해서 작성해준다.

const [number, dispatch] = useReducer(reducer, 0);
  
const onIncrease = () => {
  dispatch({
    type: 'INCREMENT'
  })
}

const onDecrease = () => {
  dispatch({
    type: 'DECREMENT'
  })
}

 

useState → useReducer 로 구현하기

1. App 컴포넌트에서 사용할 초기 상태(initialState)를 App 컴포넌트 밖에 선언해준다.

- inputs: inputs 로 받아왔던 useState 내부에 입력되어 있던 객체들

- users: users 로 받아왔던 useState 내부에 배열들

const initialState = {
  inputs: {
    username: '',
    email: ''
  },
  users: [
    {
      id: 1,
      username: 'velopert',
      email: 'public.velopert@gmail.com',
      active: true,
    },
    {
      id: 2,
      username: 'tester',
      email: 'tester@example.com',
      active: false,
    },
    {
      id: 3,
      username: 'liz',
      email: 'liz@example.com',
      active: false,
    }
  ],
}

2. reducer 함수를 생성한다.

function reducer(state, action) {
  return state;
}

3. useReducer를 사용해서 앞에서 작성한 값들을 받아온다.

첫번째 파라미터로 reducer 함수, 두번째 파라미터로 초기상태를 받아온다.

const [state, dispatch] = useReducer(reducer, initialState);

4. initialState 내부의 inpusts, users 값들을 비구조화 할당을 통해서 추출한 뒤, 컴포넌트에 props 로 전달하게 한다.

const { users } = state;
const { username, email } = state.inputs;

5. onChange 를 구현해서 이벤트가 발생했을 때 실행될 dispatch 액션을 설정한다.

useCallback 함수를 이용해서 컴포넌트가 처음 렌더링 될 때만 만들고, 그 다음부터는 재사용을 한다.

name과 value 값을 e.target 이벤트가 발생하는 DOM으로부터 추출한다.

dispatch 액션을 지정해준다.

const onChange = useCallback(e => {
    const { name, value } = e.target;
    dispatch({
      type: 'CHANGE_INPUT',
      name,
      value
    })
}, [])

그리고 CreateUser 에 전달해준다.

return (
    <>
        <CreateUser username={username} email={email} onChange={onChange} />
        <UserList users={users} />
        <div>활성 사용자 수: 0</div>
    </>
);

6. reducer 구현하기

onChange 에는 type, name, value 값이 있는데, 만약에 type이 'change_input' 이라면 현재 자신이 지니고 있는 특정값도 바뀌어야 한다. (initialState 내부값 변화)

switch 문을 이용하여 값에 따라 다른 값을 반환할 수 있도록 지정한다.

 

type 이 'change_input' 이라면 spread 문법을 이용하여 ...state 기존의 자신이 들고있는 상태를 복사해주고, 입력받은 inputs 값을 덮어쓸 수 있도록 한다.

function reducer(state, action) {
  switch (action.type) {
    case 'CHANGE_INPUT':
      return {
        ...state,
        inputs: {
          ...state.inputs,
          [action.name]: action.value
        }
      };
    default:
      throw new Error('Unhandled actoin'); 
      // 1. 지금처럼 throw new Error 를 통해서 설정하지 않은 액션에 대해서 받았을 때 확인할 수 있도록 하는 방법
      // 2. return state; 그냥 현재 상태를 업데이트 하도록 진행하는 방법
  }
}

7. onCreate 구현하기

마찬가지로 useCallback 함수를 이용해서 생성된 뒤 재사용할 수 있도록 한다.

dispatch를 통해 type 은 'create_user' 라는 액션을 발생시킨다.

user 내부에 id 값을 넣어줄 것인데 useRef 로 관리해주어야 한다.

username, email 도 넣어주고 deps 배열에 함수에서 기존 상태를 의존하고 있는 것이 있으므로 username과 email을 넣어준다.

const onCreate = useCallback(() => {
    dispatch({
        type: "CREATE_USER",
        user: {
            id: 1,
            username,
            email,
        },
    });
}, [username, email]);

8. nextId 값 useRef 로 관리하기

기존에 id 가 3개가 등록되어 있으므로 다음에 새로 등록되는 아이디의 기본값은 4이다.

const nextId = useRef(4);
더보기

useRef

1. DOM을 선택하는 용도

2. 컴포넌트 안에서 조회 및 수정을 할 수 있는 변수를 관리

 

- useRef 로 관리되는 변수는 값이 바뀌어도 컴포넌트가 리렌더링 되지 않는다.

- useRef( ) 파라미터로 넣어주는 값이 .current 값이 된다.

아까 onCreate 에서 생성해주었던 id 에 nextId.current 로 작성해주고, id 를 생성한 뒤에는 nextId.current 에 1을 더해준다.

const onCreate = useCallback(() => {
    dispatch({
        type: "CREATE_USER",
        user: {
            id: nextId.current,
            username,
            email,
        },
    });
    nextId.current += 1; // user정보를 생성하고, nextId 값을 1 증가시켜놓음
}, [username, email]);

9. reducer 에서 case 로 type 'create_user' 가 왔을 때 실행될 함수 지정해주기

case 'CREATE_USER':
      return {
        inputs: initialState.inputs, // 초기값으로 설정
        users: state.users.concat(action.user) // users 에 action.user 배열합침
      }

10. reducer 함수에 onToggle/onRemove case 작성

 

- reducer 함수에 'toggle_user' case 작성

case 'TOGGLE_USER':
      return {
        ...state, // 기존상태 복사
        users: state.users.map(user => // user 에 대하여 비교
          user.id === action.id        // id 가 action 을 통해 받아온 id 와 일치하는지
            ? { ...user, active: !user.active } // 같다면 active 값을 반전시켜준다. (true-false)
            : user // 같지 않다면 기존의 user 객체 유지
        )
      }

- 'remove_user' case 작성

case 'REMOVE_USER':
      return {
        ...state,
        users: state.users.filter(user => user.id !== action.id) // user.id 와 action.id 를 비교
      }                                             // 일치하지 않으면 유지, 일치하면 배열에서 제거

11. onToggle/onRemove 구현하기

const onToggle = useCallback((id) => {
    dispatch({
        type: "TOGGLE_USER",
        id,
    });
}, []);

const onRemove = useCallback((id) => {
    dispatch({
        type: "REMOVE_USER",
        id,
    });
}, []);

UserList 에 연결해주기

 

12. 활성사용자수 구현하기 (useMemo 사용)

 const count = useMemo(() => countActiveUsers(users), [users])

return 활성사용자 수 값에 {count} 연결해주기

 

    'React' 카테고리의 다른 글
    • Context API를 사용한 전역값 관리
    • Custom Hooks 만들기
    • 함수형 업데이트 useState
    • 컴포넌트 최적화 정리 (useMemo/useCallback/React.memo)

    티스토리툴바