원문 : https://beta.reactjs.org/learn/extracting-state-logic-into-a-reducer
Reducer
reducer의 명칭은 배열의 reduce(누적) 연산에서 비롯되었습니다.
reduce는 전체 배열을 다른 값으로 누적(reduce)합니다.
- 우리의 리듀서는 지금까지 변화해 온 상태와 액션을 취하고 다음 상태를 반환합니다.
- 우리의 리듀서는 시간이 지남에 따라 액션을 상태로 누적합니다.
리듀서는 상태 관리 로직을 추출하기 위해 사용합니다.
useReducer를 사용하면 업데이트 로직과 이벤트 핸들러에서 발생한 액션을 명확하게 구분할 수 있습니다.
즉, 이벤트 핸들러의 변경을 예방합니다.
- useState를 useReducer로 리팩토링하는 방법
- useReducer의 사용사례
- useReducer를 잘 쓰는 법
Reducer는 상태를 처리하는 다른 방법입니다.
다음 세 단계로 useState에서 useReducer로 마이그레이션할 수 있습니다.
- state setter > action dispatcher
- reducer function 작성
- 컴포넌트에서 reducer 사용
Step 1: Move from setting state to dispatching actions
아래와 같이 이벤트 핸들러를 전부 setter로 작성하였습니다.
function handleAddTask(text) {
setTasks([...tasks, {
id: nextId++,
text: text,
done: false
}]);
}
function handleChangeTask(task) {
setTasks(tasks.map(t => {
if (t.id === task.id) {
return task;
} else {
return t;
}
}));
}
function handleDeleteTask(taskId) {
setTasks(
tasks.filter(t => t.id !== taskId)
);
}
이벤트 핸들러를 통해 "테스크 데이터를 set하라는 메세지" 대신
"작업 추가/변경/삭제" 액션을 전달합니다. 이것은 사용자의 의도를 더 잘 설명합니다.
function handleAddTask(text) {
dispatch({
type: 'added',
id: nextId++,
text: text,
});
}
function handleChangeTask(task) {
dispatch({
type: 'changed',
task: task
});
}
function handleDeleteTask(taskId) {
dispatch({
type: 'deleted',
id: taskId
});
}
function handleDeleteTask(taskId) {
dispatch(
// "action" object:
{
type: 'deleted',
id: taskId
}
);
}
그냥 JavaScript 객체입니다.
일반적으로 발생한 일에 대한 최소한의 정보를 포함해야 합니다.
컨벤션
액션 객체는 모든 모양을 가질 수 있습니다.
관례에 따라 발생한 일을 설명하는 문자열 타입을 설정하고 다른 필드에 추가 정보를 전달하는 것이 일반적입니다.
타입은 컴포넌트에 따라 다르므로 이 예에서는 'added' 또는 'added_task'가 적합합니다.
어떤 일이 발생했는지를 설명하는 이름을 지정합니다!
dispatch({
// specific to component
type: 'what_happened',
// other fields go here
});
Step 2: Write a reducer function
function yourReducer(state, action) {
// return next state for React to set
}
// 이전 상태 > 액션 > 다음 상태
function tasksReducer(tasks, action) {
if (action.type === 'added') {
return [...tasks, {
id: action.id,
text: action.text,
done: false
}];
} else if (action.type === 'changed') {
return tasks.map(t => {
if (t.id === action.task.id) {
return action.task;
} else {
return t;
}
});
} else if (action.type === 'deleted') {
return tasks.filter(t => t.id !== action.id);
} else {
throw Error('Unknown action: ' + action.type);
}
}
리듀서 함수는 상태를 인수로 취하기 때문에 컴포넌트 외부에서 선언할 수 있습니다.
이렇게 하면 들여쓰기 수준이 줄어들고 코드를 더 쉽게 읽을 수 있습니다.
리듀서는 심지어 다른 파일로 옮길 수도 있습니다.
컨벤션
function tasksReducer(tasks, action) {
switch (action.type) {
case 'added': {
return [...tasks, {
id: action.id,
text: action.text,
done: false
}];
}
case 'changed': {
return tasks.map(t => {
if (t.id === action.task.id) {
return action.task;
} else {
return t;
}
});
}
case 'deleted': {
return tasks.filter(t => t.id !== action.id);
}
default: {
throw Error('Unknown action: ' + action.type);
}
}
}
Step 3: Use the reducer from your component
마지막으로 useState를 useReducer로 변경합니다.
const [tasks, setTasks] = useState(initialTasks);
const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);
- reducer함수
- 초기 상태
그리고 다음을 반환합니다.
- 상태
- 디스패치 함수(액션을 리듀서에 디스패치(전파) 하는 데 사용)
Comparing useState and useReducer
코드 크기
- 일반적으로 useState를 사용하면 초기에 적은 코드를 작성합니다.
- useReducer를 사용하면 리듀서 함수와 디스패치 작업을 모두 작성해야 합니다.
- 그러나 useReducer는 이벤트 핸들 코드를 줄이는 데 도움이 될 수 있습니다.
가독성
- useState는 상태 업데이트가 단순할 때 매우 읽기 쉽습니다..
- useReducer를 사용하면 업데이트 로직과 이벤트 핸들러에서 발생한 일을 명확하게 구분할 수 있습니다.
디버깅
테스팅
Writing reducers well
- 리듀서는 순수해야 합니다.
- 각 작업은 단일 사용자 인터랙션을 설명합니다.
요약
- 이벤트 핸들러에서 액션을 디스패치합니다.
- 주어진 상태와 동작에 대해 다음 상태를 반환하는 리듀서 함수를 작성하십시오.
- useState를 useReducer로 바꿉니다.
- 리듀서는 순수해야 합니다.
- alert 같은거 배치하지 마세요
- 개발 환경에서 경고 2번 실행하는 이유입니다.
- 순수하기 때문에 의존성 없이 쉽게 테스트 할 수 있습니다.
- 각 액션은 단일 사용자 인터랙션을 설명합니다.
- 뮤테이션 스타일로 리듀서를 작성하려면 Immer를 사용하십시오.
- 리듀서가 없으면 상태를 업데이트하는 모든 이벤트 핸들러를 변경해야 합니다.
챌린지 요약
alert 하고싶으면 리듀서에 넣지 말고 얼럿 후 디스패치
액션 타입은 "상태를 변경하려는 방법"보다 "사용자가 수행한 작업"을 이상적으로 설명해야 합니다.
이렇게 하면 나중에 더 많은 기능을 더 쉽게 추가할 수 있습니다.
<button onClick={() => {
alert(`Sending "${message}" to ${contact.email}`);
dispatch({
type: 'sent_message',
});
}}>Send to {contact.email}</button>
setState로 간단하게 리듀서 만들기
export function useReducer(reducer, initialState) {
const [state, setState] = useState(initialState);
function dispatch(action) {
const nextState = reducer(state, action);
setState(nextState);
}
// 디스패치된 작업은 업데이터 함수과 유사하게 다음 렌더까지 큐에서 대기합니다.
function dispatch(action) {
setState(s => reducer(s, action));
}
return [state, dispatch];
}
'FrontEnd' 카테고리의 다른 글
새로운 리액트 공식문서로 배우는 Context API 2편 : 리듀서와 컨텍스트로 애플리케이션 확장하기 (0) | 2022.07.06 |
---|---|
새로운 리액트 공식문서로 배우는 Context API 1편 : 프롭 드릴링 해결하기 (0) | 2022.07.05 |
몽고db 문법으로 불변성을 관리하는 immutability-helper (0) | 2022.07.05 |
[짤막글] styled-components는 어떻게 스타일을 적용하는가 (0) | 2022.07.03 |
디자인 시스템 컴포넌트를 만들 때 고려할 사항들 (0) | 2022.07.02 |