App.js
컴포넌트의 상태함수 관리하기React의 단방향성 데이터 흐름이라는 특징으로 인해, App 컴포넌트에서 useState
로 state를 정의하고, 각각의 컴포넌트에 데이터(state)를 흘려보내기 위해 stateful한 함수로 정의하여 props를 전달하는 형식으로 state를 끌어올리며 작업을 수행했습니다.
const App = () => {
const [data,setData] = useState([]);
return ..
}
물론 최적화까지 다 끝낸 코드기 때문에 작동상에는 문제는 없지만, App 컴포넌트에 많은 상태함수를 정의했기에 후에 코드 유지보수에 어려움을 겪을 수 있습니다.
useReducer
로 상태변화 함수를 컴포넌트에서 분리App 컴포넌트 파일에 여러 개의 상태변화 함수를 나열했던 부분을 컴포넌트에서 분리할 수 있는 useReducer()
리액트 훅을 통해 리팩토링을 수행할 수 있습니다.
useReducer Doc 에서도 첫번째 줄부터 명시되어 있듯이, useState()
를 대체할 수 있는 함수라고 명시 되어있습니다.
useReducer
사용법useReducer
사용하기const [count,dispatch] = useReducer(reducer,1);
count
: useReducer
함수의 2번째 매개변수가 count
의 초기값 dispatch
: state를 raise하여 App 컴포넌트 밖에 정의된 reducer
함수에 전달 reducer()
메소드 정의하기상태변화 함수를 App 컴포넌트 내에서 분리하기 위해, 해당 useReducer()
메소드는 전역적으로 정의합니다.
const reducer = (state,action) => {
switch(action.type){
case 1:{
return state + 1;
}
default:{
return state;
}
}
};
reducer
함수는 action에 기반하여 상태변화 로직을 처리합니다.
state
: 이전에 저장된 상태 action
:switch문에 해당하는 action 처리기존에setState()
로 정의된 부분을 dispatch
로 state를 raise하여 reducer
함수에 전달하는 로직으로 리팩토링 할 수 있습니다.
그럼 제 프로젝트에서 일기 생성과 삭제 부분에 대한 리팩토링을 한번 다뤄보도록 하겠습니다.
reducer()
함수 정의하기-App.js
에 useReducer()
선언하기
const [data,dispatch] = useReducer(reducer,[]);
data
는 초기값으로 빈배열로 setting하면서 state를 받고, dispatch
함수는 reducer
함수를 raise할 것입니다.
-App 컴포넌트 밖에다가 reducer()
함수 정의하기
생성과 삭제에 해당하는 action에 대해서 reducer함수를 mock-up 시 다음과 같습니다.
const reducer = (state,action) => {
switch(action.type) {
case 'CREATE':{ //생성
}
case 'REMOVE':{ //삭제
}
default:
return state;
}
};
그럼 이제 어느정도 1차 setting까지 마쳤습니다.
onCreate()
함수 Refactoring //일기 생성 기능
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]);
},[]);
위 코드에서는 setData()
가 useReducer()
에서 dispatch
에 해당하는 코드로 볼 수 있습니다. 따라서 위의 코드를 아래와 같이 리팩토링 할 수 있습니다.
const onCreate = useCallback(
(author,content,emotion) => {
dispatch({
type:'CREATE',
data:{author,content,emotion, id:dataId.current }
});
dataId.current += 1;
},[]);
dispatch()
함수는 state를 raise(=action)하여 reducer()
함수에 전달하는 역할을 합니다. 따라서, 매개변수를 객체 형태로 type
과 data
를 reducer()
함수에 전달합니다.
이 말은 곧, 구체적인 state handle은 reducer
함수의 case문 내에 정의한다는 것을 의미합니다. 그럼 다시 reducer()
함수로 되돌아가 정의해봅시다.
reducer()
함수에 CREATE action 정의하기const reducer = (state,action) => {
switch(action.type) {
case 'CREATE':{
const created_date = new Date().getTime();
const newItem = {
...action.data, //onCreate에서 setting한 값 spread
created_date
}
return [newItem,...state];
}
case 'REMOVE':{ //삭제
}
default:
return state;
}
};
dispatch함수에서 data
로 받아온 값들을 action
에 전달하여 spread하여 newItem
객체로 감싸고, 그리고 return 시에 배열에 담아서 최종적으로 리턴합니다.
onRemove()
함수 RefactoringonCreate()
와 동일한 방식으로 onRemove()
함수도 다음과 같이 dispatch 함수로 state를 raise하는 코드로 작성할 수 있습니다.
const onRemove = useCallback((targetId) => {
dispatch({type:'REMOVE',targetId});
},[]);
targetId를 기준으로 data를 삭제하기 때문에, data에 targetId를 담아서 state를 raise합니다.
reducer()
함수에 REMOVE action 정의하기const reducer = (state,action) => {
switch(action.type) {
case 'CREATE':{
const created_date = new Date().getTime();
const newItem = {
...action.data, //onCreate에서 setting한 값 spread
created_date
}
return [newItem,...state];
}
case 'REMOVE':{
return state.filter((it)=> it.id !== action.targetId);
}
default:
return state;
}
};
dispatch()
로 raise한 action에서의 targetId와 일치하지 않는것만 현재 state로 반환하게 코드를 구현할 수 있다.
이와 같이 리액트 훅 중 하나인 useReducer()
를 활용하여 단방향성의 데이터 흐름을 지닌 React를 위해 선언한 여러개의 상태관리 함수를 깔끔히 리팩토링 할 수 있었다.