useReducer
일기장 어플리케이션을 만들면서 부모 컴포넌트인 App 컴포넌트에 거의 모든 상태 변화 처리 함수가 몰려 있다.
거의 모든 함수나 값들이 const [data, setData] = useState([])를 가지고 state를 변화하는 로직들이기 때문이다.
아무래도 코드들이 길어지보니 업데이트 로직을 이해하거나 상태 값을 추적하기가 쉽지 않다.
이럴 때 useReducer로 상태 관리 로직을 보다 간결하고 구조적으로 정리할 수 있다.
원래 useState를 사용하면 이렇게 코드를 짜야 한다.
이게 useReducer를 만나면 이렇게 바뀐다.
크게 달라진 점은 useState 대신 useReducer가 생겼고, 이 안에는 reducer라는 함수와 초기값이 들어 있다.
useState처럼 구조 분해 할당으로 쓰는 count는 0번째 state고, 지금 초기값을 1로 세팅했다.
reducer 함수로 전달되는 1번째 파라미터는 현재 가장 최신 state다.
dispatch 함수는 reducer 함수의 action으로 전달되는 2번째 파라미터인데, 상태를 변화 시키는 액션을 발생 시킨다.
얘가 전달하는 타입에 따라 switch문을 읽고 state를 리턴한다. 이게 새로운 state가 된다.
dispatch 함수에 들어 있는 객체를 상태 변화 하는 객체, 즉 액션 객체라고 한다.
예시
먼저 reducer 함수는 컴포넌트의 바깥에 작성된다.
const [data, setData] = useState([])는 대체될 거라 모두 삭제해준다.
대신 const [data, dispatch] = useReducer(reducer, [])를 적어준다.
const reducer = (state, action) => {
switch (action.type) {
case "INIT": {
return action.data; // 이 값으로 새로운 state가 된다.
}
case "CREATE": {
const created_date = new Date().getTime();
const newItem = {
// action.data는 밑에서 dispatch에 담은 것들
...action.data,
created_date,
};
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.content } : it
);
}
default:
return state; // 저 경우에 해당 안되면 state 변화 없게
}
};
case를 4가지로 나눠줬다.
기존에 데이터를 받아 설정하는 getData, 일기를 작성하는 onCreate, 삭제하는 onRemove, 수정하는 onEdit이 있는데 이것들을 각각 "INIT", "CREATE", "REMOVE", "EDIT"으로 case를 나누어준다. 그리고 각각 케이스별로 리턴할 state를 작성해준다.
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); // -> dispatch({ type: "INIT", data: initData });
};
어떤 유형인지 type을 적어주고 setData에 밀어넣던 것을 dispatch에 data로 전달한다.
const reducer = (state, action) => {
switch (action.type) {
case "INIT": {
return action.data; // 이 값으로 새로운 state가 된다.
}
}
여기서 action이 dispatch의 객체고, 여기에 들어 있는 { data: initData }의 값을 return 하고 있는 중이다.
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]); // 함수형 업데이트
}, []);
onCreate도 원래 이렇게 있는데, 이걸 바꿔보았다.
case "CREATE": {
const created_date = new Date().getTime();
const newItem = {
// action.data는 밑에서 dispatch에 담은 것들
...action.data,
created_date,
};
return [newItem, ...state];
}
const onCreate = useCallback(({ author, content, emotion }) => {
dispatch({
type: "CREATE",
data: { author, content, emotion, id: dataId.current },
});
dataId.current += 1;
}, []);
dispatch 객체에는 타입과 데이터가 들어 있다.
그대로 위로 가져가 state, action을 받고 위에서 만든 created_data를 추가해 newItem을 만들었다.
조금 복잡하다... 🥲
// 기존
const onRemove = useCallback((targetId) => {
setData((data) => data.filter((it) => it.id !== targetId));
}, []);
case "REMOVE": {
return state.filter((it) => it.id !== action.targetId);
}
const onRemove = useCallback((targetId) => {
dispatch({ type: "REMOVE", targetId });
}, []);
dispatch 객체에는 type: "REMOVE"와 targetId가 들어 있다.
action 객체 안의 targetId를 꺼내 사용해주면 된다.
onEdit도 아래처럼 함수를 만들어줬는데 그렇게 하지 않고 액션 객체에 type과 여러 정보들을 넣어줬다.
// 기존
const onEdit = useCallback((targetId, newContent) => {
setData((data) =>
data.map((it) =>
it.id === targetId ? { ...it, content: newContent } : it
)
);
}, []);
// 현재
case "EDIT": {
return state.map((it) =>
it.id === action.targetId ? { ...it, content: action.content } : it
);
}
const onEdit = useCallback((targetId, newContent) => {
dispatch({ type: "EDIT", targetId, newContent });
}, []);
크게 다른 건 아닌데 객체로 생각하고 접근하려고 하니까 살짝 복잡하다.
처음 접해본 개념이니까 다시 한 번 복습해야겠다. 🥹
'⚛️ React > 💭 한 입 크기 React' 카테고리의 다른 글
[React] React-Router (0) | 2023.07.28 |
---|---|
[React] React.createContext로 Props Drilling 방지하기 (0) | 2023.07.26 |
[React] React.memo로 컴포넌트 재사용하기 (0) | 2023.07.26 |
[React] useMemo로 연산한 값 재사용하기 (0) | 2023.07.26 |
[React] 리액트의 생애주기 (Mount, Update, Unmount) (0) | 2023.07.25 |