React - useReducer

BigbrotherShin·2020년 2월 5일
0

Frontend

목록 보기
12/31
post-thumbnail

useReducer

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

useState의 대체 함수입니다. (state, action) => newState의 형태인 reducer를 첫 번째 인자로 받고 dispatch 메서드와 짝의 형태로 현재 state를 반환합니다. (만약 Redux 에 익숙하다면 이것이 어떻게 동작하는지 여러분은 이미 알고 있을 것입니다.)

다수의 하윗값을 포함하는 복잡한 정적 로직을 만드는 경우다음 state가 이전 state에 의존적인 경우에 보통 useState보다 useReducer를 선호합니다. 또한 useReducer는 자세한 업데이트를 트리거 하는 컴포넌트의 성능을 최적화할 수 있게 하는데, 이것은 콜백 대신 dispatch를 전달 할 수 있기 때문입니다.

useState에 있는 카운터 예제.

function Counter({initialCount}) {
  const [count, setCount] = useState(initialCount);
  const [text, setText] = useState('');
  const toggleSubButton = () => {
    setCount(prevCount => prevCount - 1);
    setText("-");
  };
  const toggleAddButton = () => {
    setCount(prevCount => prevCount + 1);
    setText("+");
  };
  return (
    <>
      Count: {count}
      <button onClick={toggleSubButton}>-</button>
      <button onClick={toggleAddButton}>+</button>
      <div>{text}를 눌렀습니다.</div>
    </>
  );
}

useState를 사용하면 각각의 state마다 useState를 사용하여 상태를 변경해주어야 합니다.

useReducer 예제

위의 useState 예제를 reducer를 사용해서 다시 작성하면 아래 코드와 같습니다.

const initialState = {count: 0, text: ''};

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

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
      <div>{text}를 눌렀습니다.</div>
    </>
  );
}

이렇게 useReducer를 사용하면 useState와 달리 여러 상태를 한 번에 처리할 수 있기 때문에 관리해야할 상태가 많은 경우 useReducer가 선호됩니다.

주의
React는 dispatch 함수의 동일성이 안정적이고 리렌더링 시에도 변경되지 않으리라는 것을 보장합니다. 이것이 useEffect나 useCallback 의존성 목록에 이 함수를 포함하지 않아도 괜찮은 이유입니다.

useReducer 비동기 처리 문제

작동하지 않는 예제.

import React from 'react'

const ProfileContext = React.createContext()

const initialState = {
  data: false
}

let reducer = async (state, action) => {
  switch (action.type) {
    case 'unload':
      return initialState
    case 'reload':
      return { data: reloadProfile() } //how to do it???
  }
}


const reloadProfile = async () => {
  try {
    let profileData = await fetch('/profile')
    profileData = await profileData.json()

    return profileData
  } catch (error) {
    console.log(error)
  }
}

function ProfileContextProvider(props) {
  let [profile, profileR] = React.useReducer(reducer, initialState)

  return (
    <ProfileContext.Provider value={{ profile, profileR }}>
      {props.children}
    </ProfileContext.Provider>
  )
}

export { ProfileContext, ProfileContextProvider }

useReducer hook은 redux와는 달리 비동기적으로 dispatch를 하기 때문에 componentDidMount 또는 useEffect hook을 사용해야 한다.

위의 예제의 컴포넌트 함수 일부분을 수정한 예제.

function ProfileContextProvider(props) {
  let [profile, profileR] = React.useReducer(reducer, initialState);

  useEffect(() => {
    reloadProfile().then((profileData) => {
      profileR({
        type: "profileReady",
        payload: profileData
      });
    });
  }, []); // The empty array causes this effect to only run on mount

  return (
    <ProfileContext.Provider value={{ profile, profileR }}>
      {props.children}
    </ProfileContext.Provider>
  );
}

출처: Hooks API Reference – React
reactjs - React useReducer async data fetch - Stack Overflow

profile
JavaScript, Node.js 그리고 React를 배우는

2개의 댓글

comment-user-thumbnail
2021년 2월 18일

안녕하세요 질문이 있습니다. 왜 useReducer의 reducer 함수에서는 switch case문에 break를 안해줘도 그 케이스에 해당하는 실행문만이 실행되는거죠? 원래는 case 마다 break를 써줘야 그 case에 해당하는 실행문만이 실행되잖아요.

1개의 답글