React 난 이렇게 배웠다[useReducer]

진건희·2024년 4월 17일
1
post-thumbnail
post-custom-banner

개념

React의 내장 hook 중 하나이다.
useState 처럼 상태 관리 할 때 사용됨.
주로 useState 보다 복잡한 로직에 사용한다.

  • state
    ↳ 상태는 컴포넌트 내부에서 관리되는 데이터 useState와 똑같이 현재 상태를 말함(상태)
  • action
    ↳ 액션은 상태를 변경하는데 필요한 정보를 포함한 객체.(변경할 정보)
  • dispatch
    ↳ 디스패치는 액션을 작동하는 역할(상태 변경 스위치)
  • reducer
    ↳ 리듀서는 상태를 변경하는 함수(상태 변경 기능)
  • initnalState
    ↳ 초기 상태는 상태의 초기값을 나타냄(초기값)

즉 useReducer는
state로 상태를 만들고, initnalState로 초기값을 state에 넣어줌.
그 다음 action으로 필요 정보를 state에 넣고
dispatch로 action을 작동시킴, 그 다음 reducer로 state를 렌더링 시켜서
최신 것으로 바꿈

이런식의 작동이 이루어진다.

useState와 useReducer의 차이

복잡한 상태 로직 관리

useState: 단순한 상태 값만을 다루는데 유용합니다. 간단한 상태 관리에 적합하다.
useReducer: 복잡한 상태 로직이나 여러 상태 값 간의 의존성이 있는 경우에 유용하다.
↳ state 같은 변수가 많을 때 좋음

액션 처리

useState: 상태를 직접 업데이트하는 setState 함수를 사용합니다. 액션에 대한 처리를 직접 작성해야 한다.
useReducer: 액션 객체를 사용하여 상태를 변경하며, 이를 처리하는 리듀서 함수를 통해 상태를 업데이트함.
액션에 대한 처리 로직이 리듀서 함수 내에 캡슐화되어 있습니다.
↳ 좀 더 코드 보기 편함(가독성 ↑), 유지보수 간편

컴포넌트 내부 코드의 가독성

useState: 상태 업데이트 로직이 컴포넌트 내부에 직접 작성되므로, 간단한 상태 관리에 적합하지만, 로직이 복잡해질수록 가독성이 떨어짐.
useReducer: 상태 업데이트 로직이 별도의 리듀서 함수로 분리되어 있기 때문에, 컴포넌트 내부 코드가 간결해짐.
특히 상태 업데이트 로직이 복잡한 경우에 전보다 보기 편함.

성능

useStateuseReducer는 모두 상태 관리에 사용될 수 있지만, 성능 측면에서 큰 차이는 없다.
하지만 리듀서 함수를 사용하면 상태 업데이트 로직이 분리되므로, 특히 컴포넌트의 규모가 크거나 복잡한 경우에 유지보수성이 더 좋아짐.

실전

미리 예시 코드를 만들어 왔다
이번에는 useReducer에서 나오는 복잡한 로직 중 무엇을 쓸 지 고민하다.
생각한 Todolist를 가볍게 만들어 볼 것이다.
(고퀄 X)

전체 코드

import React, { useReducer } from "react";
import styled from 'styled-components';

const Box = styled.div`
  width: 50vw;
  height: 10vh;
  background-color: white;
  display: block;
  margin-left: auto;
  margin-right: auto;
  border: 1px solid black;
  border-radius: 20px;
`

const Titlebox = styled.div`
  width: 30vw;
  height: 5vh;
  background-color: white;
  margin-left: 1vw;
  margin-top: 1vh;
`

const Title = styled.span`
  text-align: center;
  vertical-align: middle;
  font-size: 27px;
  font-family: bold;
`

const Text = styled.span`
  text-align: center;
  vertical-align: middle;
  font-family: bold;
`

const Nulldiv = styled.div`
  height: 15px;
`

const Button = styled.div`
  display: block;
  margin-top: 2vh;
  margin-left: 90vw;
`

function reducer(state, action){
  switch(action.type) {
    case "add":
      return { count: state.count + 1 };
    case "delete":
      return { count: state.count - 1 };
    default:
      throw new Error("unsupported action type: ", action.type);
  }
}

function App(){
  return(
    <div>
      <Nulldiv />
      <Box>
        <Titlebox>
          <Title><b>Todo list 만들기</b></Title>
          <br />
          <Text>Reducer 하나 배우려고 Todolist 만듭니다</Text>
        </Titlebox>
      </Box>
      <Nulldiv />
    </div>
  );
}

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

  const increaseCount = () => {
    dispatch({ type: "add" });
  };

  const decreaseCount = () => {
    dispatch({ type: "delete" });
  };

  const renderApps = () => {
    const apps = [];
    for (let i = 0; i < state.count; i++) {
      apps.push(<App key={i} />);
    }
    return apps;
  };

  return (
    <div>
    <Button>
      <button onClick={increaseCount}>증가</button>
      <button onClick={decreaseCount}>감소</button>
    </Button>
    {renderApps()}
    </div>
  );
}

export default Todolist;

일단 코드를 3가지로 나눈다.
※ styled components를 사용하였음(styled component를 따로)

style

const Box = styled.div`
  width: 50vw;
  height: 10vh;
  background-color: white;
  display: block;
  margin-left: auto;
  margin-right: auto;
  border: 1px solid black;
  border-radius: 20px;
`

const Titlebox = styled.div`
  width: 30vw;
  height: 5vh;
  background-color: white;
  margin-left: 1vw;
  margin-top: 1vh;
`

const Title = styled.span`
  text-align: center;
  vertical-align: middle;
  font-size: 27px;
  font-family: bold;
`

const Text = styled.span`
  text-align: center;
  vertical-align: middle;
  font-family: bold;
`

const Nulldiv = styled.div`
  height: 15px;
`

const Button = styled.div`
  display: block;
  margin-top: 2vh;
  margin-left: 90vw;
`

App

App은 Todolist 컴포넌트이다.
어떻게 생겼는지 만들어진 것

function App(){
  return(
    <div>
      <Nulldiv />
      <Box>
        <Titlebox>
          <Title><b>Todo list 만들기</b></Title>
          <br />
          <Text>Reducer 하나 배우려고 Todolist 만듭니다</Text>
        </Titlebox>
      </Box>
      <Nulldiv />
    </div>
  );
}

그냥 그대로 따라 치거나, 복붙 하면 된다.

reducer

자 reducer이다.

일단 useReducer를 사용하려면
import React, { useReducer } from "react";로 선언을 해줘야 한다.

그리고 다음으로
파라미터안에 state, action을 선언해준다.
※ 파라미터 = ()안에 있는 것들

action은 필요 정보를 넣는 곳이라 했는데
useReducer를 사용할 때 switch문을 사용하는데
case에 결과가 달라진다(switch는)

그런 것 처럼 switch는 파라미터가 필수 인데
어떤 case인지 묻는 것이다.
우리는 action.type(액션의 종류)로 해준다.

그리고 todolist를 추가, 제거 기능을 넣기 위해
adddelete case를 추가하고
마지막으로 둘 다 아닐 때는 오류가 나오게 return 해준다.

add는 state.count(지금 현 상황의 count를 의미) +1 시켜서
list가 하나 더 생긴 것으로 의미한다.

delete는 반대로 -1 시켜 하나 사라진 것을 의미한다.

function reducer(state, action){
  switch(action.type) {
    case "add":
      return { count: state.count + 1 };
    case "delete":
      return { count: state.count - 1 };
    default:
      throw new Error("unsupported action type: ", action.type);
  }
}

Todolist

이번에는 Todolist 쪽이다.
여기서는 const [state, dispatch] = useReducer(reducer, { count: 0 });
를 선언한다.

해석하자면
const [state, dispatch] = useReducer(reducer, { count: 0 });
const [state, dispatch] = state와 dispatch 선언

useReducer(reducer, { count: 0});
reducer 함수 소환(dispatch한 것을 새로고침으로 표시)
count: 0 = initnalState(초기값)을 0으로 잡는다
count라는 함수의 초기값을 0으로 잡음

일단 코드에서 increaseCount, decreaseCount 라는 변수를 선언하고
dispatch로 각각 add와 delete를 실행한다.

그리고 renderApps라는 변수는 for문을 이용해 state.count의 값만큼
Todolist = App 컴포넌트를 생성 및 제거한다.

그리고 return으로 버튼과 renderApps를 표시한다.

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

  const increaseCount = () => {
    dispatch({ type: "add" });
  };

  const decreaseCount = () => {
    dispatch({ type: "delete" });
  };

  const renderApps = () => {
    const apps = [];
    for (let i = 0; i < state.count; i++) {
      apps.push(<App key={i} />);
    }
    return apps;
  };

  return (
    <div>
    <Button>
      <button onClick={increaseCount}>증가</button>
      <button onClick={decreaseCount}>감소</button>
    </Button>
    {renderApps()}
    </div>
  );
}

결과

초깁값이 0이라 list가 없다.

그렇다면 증가를 눌러보자

증가를 누르자 App 컴포넌트가 나온다.

증가를 누를 수록 state.count의 수가 커지고
그 수만큼 컴포넌트가 늘어난다

마찬가지로 감소를 누르면 누른 횟수만큼 state.count가 줄어
컴포넌트의 개수가 줄어든다.

profile
프론트엔드 공부 중인 GSM 학생입니다.
post-custom-banner

0개의 댓글