본문 바로가기

React/Velopert's React

[React] Study #19 | useReducer : 상태 업데이트 로직 분리

Study #19 까지의 상태관리는 어떻게 해왔을까?

  • 주요 상태 업데이트 로직을 App 컴포넌트 내부에서 이루어왔다
  • 상태를 업데이트 할 때, useState를 사용해서 새로운 상태를 설정했다.

 

💡useReduder?

  • useState를 사용하여 상태를 관리하는 것이 아닌 또다른 상태관리 방법
  • 이 Hook 함수를 사용하면 컴포넌트 상태 업데이트 로직을 컴포넌트에서 분리시킬 수 있다.
  • 상태 업데이트 로직을 컴포넌트 바깥에 작성할 수도 있다.
  • 심지어 다른 파일에 상테 업데이트 로직을 작성한  후, 사용할 수 있다.

💡reducer?

  • 현재 상태와 액션 객체를 파라미터로 받아와서 새로운 상태를 반환해주는 함수
  • function reduer(state, action) {
    	// 새로운 상태를 만드는 로직
        // const nextState = ...
        return nextState;
    }
  • 이렇게 reducer에서 반환하는 상태 =. 컴포넌트가 지닐 새로운 상태

➰ reducer함수의 두번째 파라미터 action?

  • action은 업데이트를 위한 정보를 가지고 있다.
  • 주로 type 값을 지닌 객체 형태로 사용 (하지만 꼭 따라야할 규칙은 따로 없다.)
  • 액션의 예시들( 아래의 코드 )
  • // 카운터에 1을 더하는 액션
    {
    	type: 'INCREMENT'
    }
    // 카운터에서 1을 빼는 액션
    {
    	type: 'DECREMENT'
    }
    // input 값을 바꾸는 액션
    {
    	type: 'CHANGE_INPUT',
        key: 'email',
        value: 'test@test.com'
    }
    // 새 할 일을 등록하는 액션
    {
    	type: 'ADD_TODO',
        todo: {
        	id: 1,
            text: 'reducer 공부중',
            done: false,
        }
    }

 

💡useReducer의 사용법

const [state, dispatch] = useReducer(reducer, initialState);
  • state : 우리가 앞으로 컴포넌트에서 사용할 수 있는 상태
  • dispatch : 액션을 발생시키는 함수
    • dispatch({ type: 'INCREMENT'})
  • useReducer의 첫번째 파라미터 : reducer함수
  • useReducer의 두번째 파라미터 : 초기 상태

 

일단 App 컴포넌트의 상태관리 방법을 바꾸기전에,

Counter.js 컴포넌트에 useReducer을 사용해보자.

 

이전 코드

Counter.js

import React,{useState} from 'react'
function Count() {

    const [number, setNumber] = useState(0);

    const onIncrease = () => {
        setNumber(prevNum => prevNum +1);
    }

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

    return (
        <div>
            <h1>{number}</h1>
            <button onClick={onIncrease}>+</button> 
            <button onClick={onDecrease}>-</button>
        </div>
    )
}

export default Count

 

useReducer로 구현한 Counter 컴포넌트

Counter.js

import React,{useReducer} from 'react' //useReducer 불러오기

// 현재 상태와 액션 객체를 파라미터로 받아와서 새로운 상태를 반환해주는 reducer 함수
function reducer(state, action) {
    switch(action.type){
        case 'INCREMENT':
            return state + 1;
        case 'DECREMENT':
            return state - 1;
        default:
            return state;
    }
}

function Count() {

    const [number, dispatch] = useReducer(reducer, 0);

    const onIncrease = () => {
        dispatch({type: 'INCREMENT'});
    }

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

    return (
        <div>
            <h1>{number}</h1>
            <button onClick={onIncrease}>+</button> 
            <button onClick={onDecrease}>-</button>
        </div>
    )
}

export default Count

 


💡App 컴포넌트를 useReducer로 구현하기

  1. App 에서 사용할 초기 상태를 컴포넌트 바깥으로 분리
  2. App 내부에 있던 기존 로직을 모두 제거 (차근차근 구현 할 예정임
  3. reducer 함수 틀만들기
  4. state에서 필요한 비구조화 할당 문법을 사용하고 추출하여 각 컴포넌트에게 전달
  5. import React,{useRef, useState, useMemo, useCallback} from 'react'; // useCallback 불러오기
    import UserList from './UserList';
    import CreateUser from './CreateUser';
    
    function countActiveUsers(users){
      console.log('활성 사용자 수를 세는 중...');
      return users.filter(user => user.active).length;
    }
    
    // 초기 상태 (default)
    const initialState = {
      // inputs의 초기 상태
      inputs: {
        uername: '', email: ''
      },
      // users의 초기 상태
      users: 
      [
        {id: 1, username: 'John', email: 'john@example.com',
        active: true,
      },
        {id: 2, username: 'Car', email: 'car@example.com',
        active: false,
      },
        {id: 3, username: 'lee', email: 'lee@example.com',
        active: false,
      },
      ]
    }
    
    // 현재 상태와 액션 객체를 파라미터로 받아와서 새로운 상태를 반환해주는 reducer 함수
    function reduer(state, action){
      return state;
    }
    
    function App() {
    
      //비구조화 할당
      const [state, dispatch] = useReducer(reducer, initialState);
      const { users } = state;
      const { username, email } = state.input;
    
      return (   
        <>
          <CreateUser username={username} email={email}/>
          <UserList users={[]} />
          <div>활성 사용자 수 : 0 </div>
        </>
      );
    
    }
      
    
    export default App;
  6. onChange 구현 : CHANGE_INPUT 이라는 액션 객체를 사용하여 inputs 상태를 업데이트
  7. import React,{useRef, useState, useMemo, useCallback, useReducer} from 'react'; // useCallback 불러오기
    import UserList from './UserList';
    import CreateUser from './CreateUser';
    
    function countActiveUsers(users){
      console.log('활성 사용자 수를 세는 중...');
      return users.filter(user => user.active).length;
    }
    
    const initialState = {
      inputs: {
        uername: '', email: ''
      },
      users: 
      [
        {id: 1, username: 'John', email: 'john@example.com',
        active: true,
      },
        {id: 2, username: 'Car', email: 'car@example.com',
        active: false,
      },
        {id: 3, username: 'lee', email: 'lee@example.com',
        active: false,
      },
      ]
    }
    
    // 현재 상태와 액션 객체를 파라미터로 받아와서 새로운 상태를 반환해주는 reducer 함수
    function reducer(state, action){
      switch (action.type) {
        case 'CHANGE_INPUT':
          return{
            ...state,
            inputs: {
              [action.name]: action.value
            }
          }
          default:
            return state;
      };
    }
    
    function App() {
    
      // useReducer 사용
      const [state, dispatch] = useReducer(reducer, initialState);
      const { users } = state;
      const { username, email } = state.input;
    
      const onChange = useCallback(e => {
        const {name, value} = e.target;
        // 액션 발생시키는 함수 = dispatch
        dispatch({
          type: 'CHANGE_INPUT',
          name,
          value,
        })
      },[]);
    
      return (   
        <>
          <CreateUser username={username} email={email} onChange={onChange}/>
          <UserList users={users} />
          <div>활성 사용자 수 : 0 </div>
        </>
      );
    
    }
      
    
    export default App;
  8. onCreate 구현
  9. import React,{useRef, useState, useMemo, useCallback, useReducer} from 'react'; // useCallback 불러오기
    import UserList from './UserList';
    import CreateUser from './CreateUser';
    
    function countActiveUsers(users){
      console.log('활성 사용자 수를 세는 중...');
      return users.filter(user => user.active).length;
    }
    
    const initialState = {
      inputs: {
        uername: '', email: ''
      },
      users: 
      [
        {id: 1, username: 'John', email: 'john@example.com',
        active: true,
      },
        {id: 2, username: 'Car', email: 'car@example.com',
        active: false,
      },
        {id: 3, username: 'lee', email: 'lee@example.com',
        active: false,
      },
      ]
    }
    
    // 현재 상태와 액션 객체를 파라미터로 받아와서 새로운 상태를 반환해주는 reducer 함수
    function reducer(state, action){
      switch (action.type) {
        case 'CHANGE_INPUT':
          return{
            ...state,
            inputs: {
              ...state.inputs,
              [action.name]: action.value
            }
          };
        case 'CREATE_USER':
          return{
            // inputs 초기화
            inputs: initialState.inputs,
            // 새로운 user 추가하기
            users: state.users.concat(action.user),
          }
        default:
          return state;
      };
    }
    
    function App() {
    
      // useReducer 사용
      const [state, dispatch] = useReducer(reducer, initialState);
    
      // user를 새로 만들기 위해 useRef 사용
      const nextId = useRef(4);
    
      const { users } = state;
      const { username, email } = state.inputs;
    
      const onChange = useCallback(e => {
        const {name, value} = e.target;
        dispatch({
          type: 'CHANGE_INPUT',
          name,
          value,
        },
        )
      },[]);
      
      // 액션 발생시키는 함수 = dispatch
      const onCreate = useCallback(()=>{
        dispatch({
          type: 'CREATE_USER',
          user: {
            id: nextId.current,
            username,
            email,
          }
        });
        nextId.current += 1;
      },[username, email]);
    
      return (   
        <>
          <CreateUser username={username} email={email} onChange={onChange} onCreate={onCreate}/>
          <UserList users={users} />
          <div>활성 사용자 수 : 0 </div>
        </>
      );
    
    }
      
    
    export default App;
  10. onToggle과 onRemove도 구현
  11. import React,{useRef, useState, useMemo, useCallback, useReducer} from 'react'; // useCallback 불러오기
    import UserList from './UserList';
    import CreateUser from './CreateUser';
    
    function countActiveUsers(users){
      console.log('활성 사용자 수를 세는 중...');
      return users.filter(user => user.active).length;
    }
    
    const initialState = {
      inputs: {
        uername: '', email: ''
      },
      users: 
      [
        {id: 1, username: 'John', email: 'john@example.com',
        active: true,
      },
        {id: 2, username: 'Car', email: 'car@example.com',
        active: false,
      },
        {id: 3, username: 'lee', email: 'lee@example.com',
        active: false,
      },
      ]
    }
    
    // 현재 상태와 액션 객체를 파라미터로 받아와서 새로운 상태를 반환해주는 reducer 함수
    function reducer(state, action){
      switch (action.type) {
        case 'CHANGE_INPUT':
          return{
            ...state,
            inputs: {
              ...state.inputs,
              [action.name]: action.value
            }
          };
        case 'CREATE_USER':
          return{
            // inputs 초기화
            inputs: initialState.inputs,
            // 새로운 user 추가하기
            users: state.users.concat(action.user),
          };
        case 'TOGGLE_USER':
          return{
            ...state,
            users: state.users.map(user=>
              user.id === action.id ? {...user, active: !user.active}: user)
          };
        case 'REMOVE_USER':
          return{
            ...state,
            users: state.users.filter(user => user.id != action.id)
          };
        default:
          return state;
      }; 
    }
    
    function App() {
    
      // useReducer 사용
      const [state, dispatch] = useReducer(reducer, initialState);
    
      // user를 새로 만들기 위해 useRef 사용
      const nextId = useRef(4);
    
      const { users } = state;
      const { username, email } = state.inputs;
    
      const onChange = useCallback(e => {
        const {name, value} = e.target;
        dispatch({
          type: 'CHANGE_INPUT',
          name,
          value,
        },
        )
      },[]);
      
      // 액션 발생시키는 함수 = dispatch
      const onCreate = useCallback(()=>{
        dispatch({
          type: 'CREATE_USER',
          user: {
            id: nextId.current,
            username,
            email,
          }
        });
        nextId.current += 1;
      },[username, email]);
    
      const onToggle = useCallback((id)=>{
        dispatch({
          type: 'TOGGLE_USER',
          id
        })
      },[])
    
      const onRemove = useCallback((id)=>{
        dispatch({
          type: 'REMOVE_USER',
          id
        })
      },[])
    
      return (   
        <>
          <CreateUser username={username} email={email} onChange={onChange} onCreate={onCreate}/>
          <UserList users={users} onToggle={onToggle} onRemove={onRemove} />
          <div>활성 사용자 수 : 0 </div>
        </>
      );
    
    }
      
    
    export default App;
  12. 활성 사용자 수 구하기
    import React,{useRef, useState, useMemo, useCallback, useReducer} from 'react'; // useCallback 불러오기
    import UserList from './UserList';
    import CreateUser from './CreateUser';
    
    function countActiveUsers(users){
      console.log('활성 사용자 수를 세는 중...');
      return users.filter(user => user.active).length;
    }
    
    const initialState = {
      inputs: {
        uername: '', email: ''
      },
      users: 
      [
        {id: 1, username: 'John', email: 'john@example.com',
        active: true,
      },
        {id: 2, username: 'Car', email: 'car@example.com',
        active: false,
      },
        {id: 3, username: 'lee', email: 'lee@example.com',
        active: false,
      },
      ]
    }
    
    // 현재 상태와 액션 객체를 파라미터로 받아와서 새로운 상태를 반환해주는 reducer 함수
    function reducer(state, action){
      switch (action.type) {
        case 'CHANGE_INPUT':
          return{
            ...state,
            inputs: {
              ...state.inputs,
              [action.name]: action.value
            }
          };
        case 'CREATE_USER':
          return{
            // inputs 초기화
            inputs: initialState.inputs,
            // 새로운 user 추가하기
            users: state.users.concat(action.user),
          };
        case 'TOGGLE_USER':
          return{
            ...state,
            users: state.users.map(user=>
              user.id === action.id ? {...user, active: !user.active}: user)
          };
        case 'REMOVE_USER':
          return{
            ...state,
            users: state.users.filter(user => user.id != action.id)
          };
        default:
          return state;
      }; 
    }
    
    function App() {
    
      // useReducer 사용
      const [state, dispatch] = useReducer(reducer, initialState);
    
      // user를 새로 만들기 위해 useRef 사용
      const nextId = useRef(4);
    
      const { users } = state;
      const { username, email } = state.inputs;
    
      const onChange = useCallback(e => {
        const {name, value} = e.target;
        dispatch({
          type: 'CHANGE_INPUT',
          name,
          value,
        },
        )
      },[]);
      
      // 액션 발생시키는 함수 = dispatch
      const onCreate = useCallback(()=>{
        dispatch({
          type: 'CREATE_USER',
          user: {
            id: nextId.current,
            username,
            email,
          }
        });
        nextId.current += 1;
      },[username, email]);
    
      const onToggle = useCallback((id)=>{
        dispatch({
          type: 'TOGGLE_USER',
          id
        })
      },[])
    
      const onRemove = useCallback((id)=>{
        dispatch({
          type: 'REMOVE_USER',
          id
        })
      },[])
    
      const count = useMemo(()=> countActiveUsers(users), [users]);
      return (   
        <>
          <CreateUser username={username} email={email} onChange={onChange} onCreate={onCreate}/>
          <UserList users={users} onToggle={onToggle} onRemove={onRemove} />
          <div>활성 사용자 수 : {count} </div>
        </>
      );
    
    }
      
    
    export default App;​

 

 


💡useReducer vs useState

useState로 관리하기 편할 경우

  • 컴포넌트에서 관리하는 값이 딱 하나고, 그 값이 단순한 숫자, 문자열 또는 boolean 값일 경우

useRefucer로 관리하기 편할 경우

  • 컴포넌트에서 관리하는 값이 여러개가 되어서 상태의 구조가 복잡해 질경우

 

 

 


Reference

https://react.vlpt.us/basic/20-useReducer.html