useReducer

gyomni·2022년 2월 25일
0

React

목록 보기
7/9
post-thumbnail

useReducer 알아보기

이전에 컴포넌트 상태를 업데이트 할 때에는 useState사용해서 새로운 상태를 설정했다.
useState말고, useReducer라는 Hook을 사용해서 상태를 업데이트 할 수 있다.

useStateuseReducer 의 차이

  • useState
    ->설정하고 싶은 다음상태를 직접 지정해주는 방식으로 상태를 업데이트.

    setValue(5)
  • useReducer
    -> action이라는 객체를 기반으로 상태를 업데이트 ( action : 업데이트할때 참조하는 객체)
    -> 컴포넌트의 상태 업데이트 로직을 컴포넌트에서 분리시킬 수 있다.
    -> 상태 업데이트 로직을 컴포넌트 바깥에 작성 할 수 있다
    -> 다른 파일에 작성 후 불러와서 사용 할 수 있다.

    dispatch({type : 'INCREMENT'})

    -> type이라는 값을 사용해서 어떤 업데이트를 진행 할 것인지 명시를 할 수 잇음.

    dispatch({
      type : 'INCREMENT',
      diff : 4
    })

    -> 업데이트 할 때 필요한 참조하고 싶은 다른 값이 있다면, 이 객체 안에 넣을 수도 있다.

여기서 reducer는 상태를 업데이트 하는 함수이다.

redecer는 이렇게 생겼다~!

function reducer(state, action) {
 switch (action.type) {
   case 'INCREMENT':
     return state + 1;
   case 'DECREMENT':
     return state - 1;
   default:
     return state;
 }
}
  • 현재 상태와 액션 객체를 파라미터로 받아와서 새로운 상태(업데이트된 상태)를 반환해주는 형태를 갖추고 있어야 한다.
  • 첫번째 파라미터 : state (type은 뭐든지 될 수 있음)
    -> 결과값은 그 다음 상태여야함.
  • 두번째 파라미터 : action
    -> action type의 이름 직접 설정 가능.
{
  type: 'INCREMENT',
}
 
{
  type: 'DECREMENT'
}
  • action type이 뭐가 들어오냐에 따라 다르게 업데이트 해줌.
    -> 그렇게 하기 위해서 switch문을 사용함.

useReducer사용

const [number, dispatch] = useReducer(reducer, 0)
  • 첫번째 파라미터 : reducer함수를 넣어줌.
  • 두번째 파라미터 : 기본 값을 넣어줌 (숫자, 문자열, 객체, 배열...)
  • number : 현재 상태를 의미.
  • dispatch : 액션을 발생시키는 함수.

코드 파헤치기

📍
useReducer사용 전

  • 기존에는 useState를 사용해서 구현해줌
import React, {useState} from "react"; 

function Counter() { // Counter 컴포넌트 만들기
        const [number, setNumber] = useState(0);
        const onIncrease = () => {
            setNumber(preNumber => preNumber+1);
        }

        const onDecrease = () => {
            setNumber(preNumber => preNumber-1);
        }
        return ( 
            <div>
                <h1>{number}</h1>
                <button onClick={onIncrease}>+1</button>
                <button onClick={onDecrease}>-1</button>
            </div>
        )
}
export default Counter;

📍
useReducer사용

import React, {useReducer} from "react"; 

// reducer 생성
function reducer(state, action){
    switch (action.type){
        case 'INCREMENT':
            return state +1;
        case 'DECREMENT':
            return state -1;
        default:
            return state; // throw new Error('Unhandels action'); 해도됨.
            // return state하면, 준비되지 않은 action이 들어와도 별일 안생김.
            // Error를 발생시킨다면, console에서 에러발생했다는 것을 볼 수 있음.
    }
}

function Counter() { 
    // useReducer hook 사용
    const [number, dispatch] = useReducer(reducer, 0); // 시작할 때 값이 0이고, reducer함수의 action이 발생함에 따라서 바뀌게 됨.

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

    const onDecrease = () => {
        dispatch({
            type : 'DECREMENT'
        }) 
        }
        return ( 
            <div>
                <h1>{number}</h1>
                <button onClick={onIncrease}>+1</button> 
                <button onClick={onDecrease}>-1</button>
            </div>
        )    
}
export default Counter;

잘 작동한다~!

상태의 업데이트 로직이 컴포넌트 밖에 있는 것을 볼 수 있다.

function reducer(state, action){
    switch (action.type){
        case 'INCREMENT':
            return state +1;
        case 'DECREMENT':
            return state -1;
        default:
            return state; 
        	// throw new Error('Unhandels action'); 해도됨. (console에서 에러발생했다는 것을 볼 수 있음)
            // return state하면 -> 준비되지 않은 action이 들어와도 별일 안생김.
    }
}

useState 사용해서 구현한 코드를 useReducer 사용해서 구현한 코드로 전환하기~!

useState 사용해서 구현한 코드

App.js

import React,{useRef, useState, useMemo, useCallback} from 'react'; 
import CreateUser from './CreateUser';
import UserList from './UserList';

function countActiveUsers(users){
  console.log('활성 사용자 수를 세는중...');
  return users.filter(user=>user.active).length;
}

function App(){
  // 상태 설정
  const [inputs, setInputs] =useState({
    username:'',
    email:'',
  });

  const {username, email} = inputs; 

  const onChange = useCallback(e =>{
    const {name, value}=e.target;

    setInputs({
      ...inputs,
     [name]:value 
    });
  },[inputs]);

  const [users, setUsers] = useState([ 
    {
        id:1,
        username: 'gyomni',
        email: 'hi1@gmail.com',
        active:true,
    },
    {
        id:2,
        username: 'joy',
        email: 'hi2@gmail.com',
        active:false,
    },
    {
        id:3,
        username: 'zoe',
        email: 'hi3@gmail.com',
        active:false,
    }
    ]);
  
const nextId = useRef(4); 
const onCreate = useCallback(()=>{
  const user={
    id:nextId.current,
    username,
    email,
  };
 
  setInputs({
    username:'',
    email:''
  });
  nextId.current+=1; 
},[username,email]);

const onRemove = useCallback(id =>{
  setUsers(users=>users.filter(user=>user.id !==id)); 
}, []);

const onToggle = useCallback(id =>{ 
  setUsers(users=>users.map( 
    user=>user.id ===id
    ?{ ...user, active: !user.active} 
    : user
  ));
}, [])
  const count =useMemo(()=>countActiveUsers(users),[users]);
 
  return (  
  <>
  <CreateUser 
    username={username} 
    email={email} 
    onChange={onChange} 
    onCreate={onCreate} 
    />
  <UserList users={users} onRemove={onRemove} onToggle={onToggle}/>
  <div>활성 사용자 수 : {count}</div>
  </>
  )
  }

export default App;

useReducer 사용해서 구현한 코드

App.js

1) App컴포넌트에서 사용할 초기상태를 컴포넌트 바깥에 선언하고 나머지 로직 정리하고 내부 Props 설정을 지웠다.

import React,{useRef, useState, useMemo, useCallback} from 'react'; 
import CreateUser from './CreateUser';
import UserList from './UserList';

function countActiveUsers(users){
  console.log('활성 사용자 수를 세는중...');
  return users.filter(user=>user.active).length;
}

const initialState = {
  inputs: {
    username: '',
    email: ''
  },
  users: [
   {
        id:1,
        username: 'gyomni',
        email: 'hi1@gmail.com',
        active:true,
    },
    {
        id:2,
        username: 'joy',
        email: 'hi2@gmail.com',
        active:false,
    },
    {
        id:3,
        username: 'zoe',
        email: 'hi3@gmail.com',
        active:false,
    }
  ]
};

function App(){

  return (  
  <>
  <CreateUser/>
  <UserList users={[]} />
  <div>활성 사용자 수 : 0</div>
  </>
  )
  }

export default App;

2) useReducer를 불러 오고 reducer함수 틀 만들기

import React,{useRef, useReducer, useMemo, useCallback} from 'react'; 

.
.
.

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

function App(){

  const [state, dispatch] = useReducer(reducer, initialState);
  return (  
  <>
  <CreateUser 
    />
  <UserList users={[]} />
  <div>활성 사용자 수 : 0</div>
  </>
  )
  }

3)

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

-> 위 코드의 state안에 usersinputs이 들어 있다.
여기에 있는 값들을 비구조화할당을 통해서 추출해준 다음에 Props로 컴포넌트에게 전달해주기.

function App(){

  const [state, dispatch] = useReducer(reducer, initialState);
  const {users} = state;
  const {username, email} = state.inputs;
  return (  
  <>
  <CreateUser  username={username} email={email}
    />
  <UserList users={users} />
  <div>활성 사용자 수 : 0</div>
  </>
  )
  }

-> users, username, email을 추출해주고 넣어주기.


4) onChange 구현하고 CreateUser컴포넌트에게 전달.

 const onChange = useCallback( e => {
    const {name, value} = e.target;
    dispatch({
      type: 'CHANGE_INPUT',
      name,
      value
    })
  },[]);
  return (  
  <>
  <CreateUser username={username} email={email} onChange={onChange}
    />
  <UserList users={users} />
  <div>활성 사용자 수 : 0</div>
  </>
  )
  }

4) - 1 reducer 구현

  • action.type이 뭐냐에 따라 다른 작업 해준다.
  • 만약에 action.typeCHANGE_INPUT 이라면 현재 자신이 지니고 있는 상태에서 inputs안에 있는 특정 값을 바꿔주도록 구현.
function reducer(state, action) {
  switch (action.type) {
    case 'CHANGE_INPUT':
      return {
        ...state,
        inputs: {
          ...state.inputs,
         [action.name]: action.value
        }
      };
    default:
      return state;
  }
}

-> CHANGE_INPUT 이라는 액션 객체를 사용하여 inputs 상태를 업데이트 해주기.
-> reducer 함수에서 새로운 상태를 만들 때는 불변성을 지켜주어야 하기 때문에 spread 연산자 사용.
-> default값 넣어줘야함 .
( throw new Error('Unhandels action') 넣어주면 console창에 에러발생했다는 것을 볼 수 있음.)


5) onCreate 구현

const onCreate = useCallback(() => {
    dispatch({
      type: 'CREATE_USER',
      user: {
        id: nextId.current,
        username,
        email
      }
    });
    nextId.current += 1;
  }, [username, email]);

-> useCallback 사용

 const nextId = useRef(4); // 기존에 3개 등록 되있어서.

-> id값은 useRef로 관리해줘야함.

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

5) - 1 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: initialState.inputs,
        users: state.users.concat(action.user)
      };
    default:
      return state;
  }
}

-> 기존에 useState 사용했을 때는 inputs를 날리는 작업 따로하고, users배열 업데이트 하는 작업 따로 하고 했었는데, 이번에는 CREATE_USER라는 액션이 발생하면 두가지 작업 동시에 할 수 있다.


6) onToggle구현

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

-> [] 비어있음 : 함수 한번 만들고 재사용 가능.

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

6) - 1 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: initialState.inputs,
        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
        )
      };
}

7) onRemove구현

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

7) - 1 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: initialState.inputs,
      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;
}
}

8) 활성 사용자수 구하기

  • useMemo 사용
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>
  </>
  )
  }

-> 위에 만들어 놨던 countActiveUsers 사용
-> 활성 사용자수 0 에서 {count}로 변경.

useState VS. useReducer

언제 useState를 써야하고 언제 useReducer 를 써야할까?
-> 정해진 답은 없다 !

예를 들어, 컴포넌트에서 관리하는 값이 하나고, 그 값이 Number or String or boolean 값이라면 확실하게 useState로 관리하는 것이 편할 것이다.
그러나, 컴포넌트에서 관리하는 값이 여러개가 되어서 상태의 값이 복잡해진다면 useReducer 가 좀더 편할 수도 있다.

이것은 자주 사용해보고 마음에 드는 방식 사용하기!
간단한거다 싶으면 useState, 복잡하다 싶으면 useReducer 사용해보기 !

학습 : 벨로퍼트와 함께 하는 모던 리엑트

profile
Front-end developer 👩‍💻✍

0개의 댓글