본문 바로가기
TIL

useState 와 useReducer

by 은지:) 2024. 4. 8.
728x90
반응형

useState

 

 

1.

이 버튼을 누르면 값이 이전 값과 같기 때문에 렌더링하지 않음 

...
<button onClick={()=>setCount(1)}>1</button>
...

 

 

객체는 참조형이기 때문에 주소값이 달라져 렌더링함

...
<button onClick={()=>setCount({count:1})}>1</button>
...



- 객체는 참조형이라서 값만 바뀌면 메모리 주소 안 바뀜
밑 예시는 렌더링 안 됨

...
<button onClick={ state.count = 1; setState(state) }>1</button>
...

 

 

 

 

2. 함수형 업데이트 쓰셈

 

...
<button onClick={()=>{setCount(count+1)}> {count} </button>
...

 

위 코드는 빠르게 두 번 누르면 한번만 카운팅됨

이건 상태 업데이트를 비동기적으로 하는 리액트 때문임 

(근데 이 설명보다는 한번에 많은 요청이 오면 성능 최적화를 위해 일괄적으로 처리하려는 리액트 때문에 그런 거 같음)


 


 

그래서 보통 함수형 업데이트 씀

c  => c  + 1가 호출되면서 이전 값을 기반으로 갱신함

 

...
<button onClick={()=>setCount((c)=>c+1)}>+</button>
...

 

 

 

 

useReducer

 

import React, { useReducer } from 'react';

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

function Counter() {
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  return (
    <div>
      Count: {state.count}
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </div>
  );
}

 

리액트 구조로 보면 그냥 view-action을 나눈 구조임

reducer 함수에서 action.type 으로 받아서 쓰고 있는데 그냥 원시 값 넣어도 됨

 

 

 

reducer의 세번째 매개 변수 : 지연 초기화 (Lazy initialization) : 초기값을 나중에 계산하거나 생성하는 것

 

function init(initialCount) {
  return { count: initialCount };
}

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

const initialCount = 0;
const [state, dispatch] = useReducer(reducer, initialCount, init);

 

 

지연 초기화 <= 번역 그대로 

 

두번째 자리랑 좀 헷갈리는데

두번째는 초기값이라면

세번째 자리는 초기값을 동적으로 생성할 수 있는 함수임

 

초기값 생성하는 로직을 따로 분리할 수 있고

초기값이 복잡하거나 계산이 필요할 때 유용함 (마운트 될 때 한번만 실행되니까)

 

 

예를 들어서

게임을 실행할때마다 캐릭터의 좌표를 랜덤으로 생성해야하는 경우일 때 ( init을 랜덤하게 바꿔야 함) 씀

 

 

밑은 예시

function init() {
  const initialPosition = { x: Math.random() * 100, y: Math.random() * 100 };
  const initialLives = 3;
  const initialScore = 0;
  return { position: initialPosition, lives: initialLives, score: initialScore };
}

function reducer(state, action) {
  switch (action.type) {
    case 'move':
      return { ...state, position: action.payload.newPosition };
    case 'incrementScore':
      return { ...state, score: state.score + 1 };
    case 'loseLife':
      return { ...state, lives: state.lives - 1 };
    default:
      throw new Error();
  }
}

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

 

 

 

 

3.  useState vs useReducer

 

둘다 16.8 버전때 나옴

뭐가 먼저라고 할 순 없지만

 

실제로 리액트 내부에서 useState는 useReducer로 구현되어 있음 

 

 

 

useReducer로 useState 구현한다면

const useState = (initialState) => {


  // 리듀서 함수, initState
  const [state, dispatch] = useReducer((prev,action)=>{
    typeof action === 'function' ? action(prev) : action, initialState
  })  
  return [state, dispatch]
}

 

const [state, setState] = useState(초기값) 

형태 만들기 가능

 

 

 

useState로  useReducer 구현한다면

const useReducer = (reducer, initalState) => {
   const [state, useState] = useState(initalState)
   const dispatch = (action) => setState(prev=>reducer(prev, action));
   
   return [state, dispatch]
}

 

 

아까 3번째 지연 초기화도 구현 가능함

const useReducer = (reducer, initArg, init) => {
   const [state, useState] = useState(init? () => init(initState) :initalArg)
   const dispatch = useCallback((action) => setState(prev=>reducer(prev, action)),[reducer])
   
   return [state, dispatch]
}

 

 

이렇게 쓰면 useReducer를 거의 완벽하게 대처함

 

 

-----------------------------------

 

책에서는 useReducer 사용을 지향하는 거 같았음

근데 선호도, 프로그래밍 스타일에 따라 선택하라는 거 보면 그냥 useState를 쓰되, action을 나눌 때가 있다면 그냥 useReducer를 쓰는게 좋을 거 같다

 

 

++

useReducer만 할 수 있는 거

 

 

const useScore = (bonus) => {
return useReducer((prev, delta)=> prev + delta + bonus, 0)
}

bonus 같은 외부변수에 의존 가능함

단, bonus와 delta 가 모두 갱신된 경우에만 올바르게 작동함
그렇기 떄문에 정말 필요한 경우가 아니라면 권장하진 않음

 

 

 

 

728x90
반응형

'TIL' 카테고리의 다른 글

리액트 지역 상태 전역 상태  (0) 2024.04.11
strategy template 패턴  (0) 2024.04.11
Iterator 패턴  (0) 2024.03.19
자바스크립트 접근 제한자  (0) 2024.03.09
리액트 - 순수 함수  (0) 2024.03.05

댓글