React useReducer

떡ol·2023년 8월 28일

useReducer란

useState는 상태값을 저장하고 set을 이용하여 변수나 객체의 상태값에 변화를 줄수있게하는 React의 hook입니다. 해당 포스트에서도 설명하고 있습니다. 하지만 이러한 객체들이 많아지면 어떻게 될까요? 전부 한줄씩 작성해야할까요? 마찬가지로 onCreate, onDelete와 같은 함수식들도 한줄씩 계속 작성해야 할까요?

const [data1, setData1] = useState([]);
const [data2, setData2] = useState([]);
const [data3, setData3] = useState([]);
const [data4, setData4] = useState([]);
const [data5, setData5] = useState([]);
const [data6, setData6] = useState([]);
const [data7, setData7] = useState([]);
//생략...
const onA = () => {}
const onB = () => {}
const onC = () => {}
const onDo = () => {}
//생략

이러한 객체와 함수식을 한번에 묶어 사용할 수 있게 React는 Reducer를 제공합니다.

기본설명

다음과 같이 표기하여 사용하고있습니다.

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

여기서 reducer는 받아온 데이터를 가공해서 리턴해주는것을 도와주는 함수입니다.

function reducer(state, action) {
  // 새로운 상태를 만드는 로직
  // const nextState = ...
  return nextState;
}

여기서 action 은 업데이트를 위한 정보를 가지고 있습니다. 주로 type 값을 지닌 객체 형태로 사용하지만, 꼭 따라야 할 규칙은 따로 없습니다. state는 원본 데이터를 넘겨줍니다.

// 카운터에 1을 더하는 액션
{
  type: 'INCREMENT'
}
// 카운터에 1을 빼는 액션
{
  type: 'DECREMENT'
}
// input 값을 바꾸는 액션
{
  type: 'CHANGE_INPUT',
  key: 'email',
  value: 'tester@react.com'
}
// 새 할 일을 등록하는 액션
{
  type: 'ADD_TODO',
  todo: {
    id: 1,
    text: 'useReducer 배우기',
    done: false,
  }
}

다음과 같이 action의 내용들은 정해진 규칙이나 타입은 따로 없습니다. 원하시는데로 사용하시면 됩니다. (보통 type는 대문자로 하는것 같습니다.) 이렇게 만든 reduceruseReducer에 첫번째 파라미터에 등록하는 겁니다. initialState는 초기 값으로 useState와 동일하게 사용하시면 됩니다.

사용하기

다음은 예제에 사용해볼 함수와 객체입니다. 4가지 CRUD가 data를 바라보고 있네요.

const [data, setData] = useState([]);

const getData = async () => {
    const res = await fetch(
      "https://jsonplaceholder.typicode.com/comments"
    ).then((res) => res.json());

    const initData = res.slice(0, 20).map((it) => {
      return {
        author: it.email,
        content: it.body,
        emotion: Math.floor(Math.random() * 5) + 1,
        created_date: new Date().getTime(),
        id: dataId.current++
      };
    });

    setData(initData);
  };

const onCreate = useCallback((author, content, emotion) => {
    const created_date = new Date().getTime();
    const newItem = {
      author,
      content,
      emotion,
      created_date,
      id: dataId.current
    };

    dataId.current += 1;
    setData((data) => [newItem, ...data]);
  }, []);

  const onRemove = useCallback((targetId) => {
    setData((data) => data.filter((it) => it.id !== targetId));
  }, []);

  const onEdit = useCallback((targetId, newContent) => {
    setData((data) =>
      data.map((it) =>
        it.id === targetId ? { ...it, content: newContent } : it
      )
    );
  }, []);

먼저 함수로 사용할 reducer 를 만듭니다

// useState는 지워줍니다 안쓸겁니다.
//const [data, setData] = useState([]);
const [data, dispatch] = useReducer(reducer,[]);

// 그리고 관련 함수에서 이니셜을 따와서 case INIT, CREATE 등을 설정해줍니다.
//switch의 내용은 기존 함수와의 연관성을 생각해 그대로 옮겨 놓으시면 됩니다.
//setState()를 dispatch로 대체하기 위한 작업인 겁니다.
const reducer = (state, action) => {
    switch (action.type) {
        case "INIT": {
            return action.data;
        }
        case "CREATE": {
            const create_dt = new Date().getTime();
            const newItem = {
                ...action.data, // data라는 객체에 담아 보낼겁니다
                create_dt
            };
            return [newItem, ...state];
        }
        case "REMOVE":{
            return state.filter(it => it.id !== action.targetId )
        }
        case "EDIT":{
            return state.map(it =>
                it.id === action.targetId ? { ...it, content: action.newContent } : it )
        }
        default: return state; // 여긴 default로 기존 값을 가공없이 그대로 넘겨줍시다.
    }
}

이제는 함수에서 불러오는 setState부분을 전부 대체해주시면 됩니다.

    const getData = async () => {
        const res = await fetch("https://jsonplaceholder.typicode.com/comments")
            .then(res  => res.json());

        const initData = res.slice(0,20).map(it => {
            return {
                author: it.email,
                content: it.body,
                emotion: 5,
                create_dt: new Date().getTime() + 1,
                id: dataId.current++
            };
        });

        dispatch({type: "INIT", data: initData});
    }

    const onCreate = useCallback((author, content, emotion) => {
        dispatch({
            type: "CREATE",
            data: {author, content, emotion, id:dataId.current} 
            // 여기는 위에서 언급했듯이, data로 감싸서 객체에 담아 보냅니다.
        })
        dataId.current += 1;
    },[]);

    const onRemove = useCallback((targetId) => {
        dispatch({
            type: "REMOVE",
            targetId
        });
    }, []);

    const onEdit = useCallback((targetId, newContent) => {
        dispatch({
            type: "EDIT",
            targetId,
            newContent
        });
    }, []);
profile
하이

0개의 댓글