:useReducer를 사용하면, 컴포넌트에서 상태변화 로직을 분리 시킬 수 있다!
useState처럼 컴포넌트의 상태를 관리해주는 React hooks
(useState를 대체할 수 있는 훌륭한 hook입니다.)
useState와 같이 비구조화 할당을 통해 사용합니다.
import React, {useReducer} from "react";
// 중략
const [count, dispatch] = useReducer(reducer, 1);
count는 state입니다. dispatch는 상태를 변화시키는 액션을 발생시키는 함수입니다.
우항에 useReducer를 호출할 때에는 꼭 인자로 reducer라는 함수를 전달해야 합니다.
dispatch는 상태변화를 일으키는 역할을 하는데 이 일어난 상태변화를 처리해주는 것이 reducer함수입니다. 2번째로 전달하는 인자는 count state의 초기값입니다.
ReducerEx.js
import React, { useReducer } from "react";
const reducer = (state, action) => {
switch (action.type) {
case 1:
return state + 1;
case 10:
return state + 10;
case 100:
return state + 100;
case 1000:
return state + 1000;
default:
//기존값 리턴
return state;
}
};
function ReducerEx() {
const [count, dispatch] = useReducer(reducer, 1);
return (
<div>
{count}
<button onClick={() => dispatch({ type: 1 })}>add 1</button>
<button onClick={() => dispatch({ type: 10 })}>add 10</button>
<button onClick={() => dispatch({ type: 100 })}>add 100</button>
<button onClick={() => dispatch({ type: 1000 })}>add 1000</button>
</div>
);
}
export default DispatchEx;
예시 코드를 보면, dispatch에게 객체를 전달하고 있는데 이 객체를 action 객체라고 부릅니다. action이 곧 상태변화! 라고 생각하면 됩니다.
action = 상태변화를 설명할 객체 //"reducer야 너가 처리해야할 상태변화 이런 내용임 내가 들고옴~"
dispatch를 호출하면 상태변화가 일어나야 하고 그 상태변화에 대한 처리는 reducer에서 수행합니다.
reduce의 인자로는 state와 action이 있는 걸 확인했습니다.
reducer함수는 switch case문을 통해서 액션 타입에 따른 다른 state값을 return해주고 있습니다. return으로 반환하는 것은 새로운 state입니다.
key point 👾 dispatch 함수를 useCallback 내부에서 사용할 때, useCallback의 deps값을 신경쓰지 않아도 된다. reducer가 알아서 현재의 state값을 기억하고 참조하기 때문임
App.js컴포넌트에서 분리된 상태로직 결과물
import React, {
useRef,
useEffect,
useMemo,
useCallback,
useReducer,
} from "react";
import "./App.css";
import DiaryList from "./components/DiaryList";
import DiaryEditor from "./DiaryEditor";
const reducer = (state, action) => {
switch (action.type) {
case "INIT": {
return action.data;
}
case "CREATE": {
const created_date = new Date().getTime();
const newItem = {
...action.data,
created_date,
};
return [newItem, ...state];
}
case "REMOVE": {
return state.filter((item) => item.id !== action.targetId);
}
case "EDIT": {
return state.map((item) =>
//기존 state의 item의 아이디가 수정하려는 item의 아이디(targetId) 와 같으면
//해당 객체의 기존 프로퍼티는 그대로 두고, 콘텐츠의 내용만 새로운 아이로 업데이트
//주의점: contents가 뒤로 가게 전개 연산자와 같이 사용
//수정하려는 item이 아니라면 원래 item 그대로 return
item.id === action.targetId
? { ...item, contents: action.newContents }
: item
);
}
default:
return state;
}
};
function App() {
//useReducer를 통해 상태관리를 할 것이라서 useState는 주석처리
// const [data, setData] = useState([]);
//useReducer의 2번째 인자는 항상 dispatch로
const [data, dispatch] = useReducer(reducer, []);
const getData = async () => {
const res = await fetch(
"https://jsonplaceholder.typicode.com/comments"
).then((res) => res.json());
console.log(res);
const initData = res.slice(0, 20).map((item) => {
return {
author: item.email,
contents: item.body,
emotion: Math.floor(Math.random() * 5) + 1,
created_date: new Date().getTime(),
id: dataId.current++,
};
});
//action 객체에 타입을 적어주고 어떤 데이터로 state를 변경할지도 넘겨줘야 하기 때문에
//setData로 전달해준 새로운 state를 data라는 이름으로 액션객체에 같이 실어줍니다.
dispatch({ type: "INIT", data: initData });
// setData(initData);
};
useEffect(() => {
getData();
}, []);
//useRef를 사용하여 고유 아이디를 만들 수 있다.
const dataId = useRef(0);
//리스트 아이템 추가 기능
const onCreate = useCallback((author, contents, emotion) => {
dispatch({
type: "CREATE",
data: { author, contents, emotion, id: dataId.current },
});
dataId.current += 1;
}, []);
//리스트 아이템 삭제 기능
const onRemove = useCallback((targetId) => {
dispatch({ type: "REMOVE", targetId });
}, []);
//리스트 아이템 수정 기능
const onEdit = useCallback((targetId, newContents) => {
dispatch({ type: "EDIT", targetId, newContents });
}, []);
//리액트에서는 return을 가지고 있는 함수를 메모이제이션할 수 있다.
//메모이제이션하고 싶은 함수를 useMemo를 통해 감싸주면 된다.
//useMemo()의 첫번째 인자로 콜백함수를 받고 이 콜백함수가 리턴하는 값(연산)을 최적화할 수 있도록 도와준다.
//useMemo(콜백함수, [deps])-> 콜백함수를 유즈메모로 감싸주면 리턴해주는 아이는 더 이상 함수가 아니다.
//그렇기 때문에 호출할 때에 ()는 없애줘야 한다. deps는 useEffect의 의존성 배열과 같은 역할을 한다.
//해당 값에 어떤 값이 변했을 때에만 콜백 연산을 다시 수행할 지 알려준다.
const getDiaryAnalysis = useMemo(() => {
const goodCount = data.filter((item) => item.emotion >= 3).length;
const badCount = data.length - goodCount;
const goodRatio = Math.floor((goodCount / data.length) * 100);
return { goodCount, badCount, goodRatio };
}, [data.length]);
//useMemo를 함수에 씌워주었으니
// const { goodCount, badCount, goodRatio } = getDiaryAnalysis();
const { goodCount, badCount, goodRatio } = getDiaryAnalysis;
return (
<div className="App">
<DiaryEditor onCreate={onCreate} />
<div>전체 일기: {data.length}</div>
<div>기분 좋은 일기 개수 : {goodCount}</div>
<div>기분 안좋은 일기 개수: {badCount}</div>
<div>기분 좋은 일기 퍼센트: {goodRatio}%</div>
<DiaryList diaryList={data} onRemove={onRemove} onEdit={onEdit} />
</div>
);
}