이 장에서는 상태 변화의 개념과 useReducer에 대해 알아보고, useRuducer를 이용하여 이전에 만들었던 [할 일 관리] 앱을 업그레이드하여 본다.
리액트 훅 useReducer를 이용하여 상태 변화 코드를 분리한다. 상태 변화 코드 분리라는 개념을 이해하기 전에 useReducer에 대해 알아보자.
[ 실습 준비하기 ]
이전 실습에서 작성한 [할 일 관리] 앱을 열고 component 폴더에 TestComp.js 이름으로 임시 컴포넌트를 만든다.
import { useState } from "react";
function TestComp() {
const [count, setCount] = useState(0);
const onIncrease = () => {
setCount(count + 1);
};
const onDecrease = () => {
setCount(count - 1);
};
return (
<div>
<h4>테스트 컴포넌트</h4>
<div>
<bold>{count}</bold>
</div>
<div>
<button onClick={onIncrease}>+</button>
<button onClick={onDecrease}>-</button>
</div>
</div>
);
}
export default TestComp;
이 TestComp 컴포넌트는 카운트와 +, - 버튼 2개로 구성된다. 다음은 App 컴포넌트의 자식으로 배치 후 페이지에 렌더링한다.
import TestComp from './component/TestComp'; // TestComp import
return(
<div className='App'>
<TestComp /> //자식으로 설정
(...)
</div>
);
}
export default App;
[ 상태 변화 코드의 개념 ]
상태 변환 코드란 State 값을 변경하는 코드이다. TestComp 컴포넌트의 경우, onIncrease 함수와 onDecrease 함수는 State 변수 count의 값을 증가시키거나 감소시키므로 상태 변화 코드이다.
그렇다면 상태 변화 코드를 컴포넌트에서 분리한다는 것은 무슨 뜻인가? 앞서 작성한 것처럼 컴포넌트 내부에 작성했던 상태 변화 코드를 외부에 작성한다는 뜻이다. 지금처럼 useState를 이용하여 State를 생성하면 상태 변화 코드는 컴포넌트 내부에 작성해야 한다. 그러나 useReducer를 사용하면 상태 변화 코드를 컴포넌트 외부로 분리하는 것이 가능하다.
[ useReducer 기본 사용법 ]
useReducer는 useState처럼 State를 관리하는 리액트 훅이다. 하지만 useState와는 달리 State 관리를 컴포넌트 외부에서 할 수 있게 하므로 상태 변화 코드를 컴포넌트에서 분리할 수 있다.
먼저 TestComp에서 useState를 이용한 기능을 모두 삭제한다.
function TestComp() {
return (
<div>
<h4>테스트 컴포넌트</h4>
<div>
<bold>0</bold>
</div>
<div>
<button>+</button>
<button>-</button>
</div>
</div>
);
}
export default TestComp;
그 다음에는 useState로 만든 기능을 useReducer로 대체하도록 TempComp 컴포넌트를 수정한다.
import { useReducer } from "react"; // react 라이브러에서 import
function reducer() {} //새로운 함수 reducer를 컴포넌트 밖으로 만듬
function TestComp() {
const [count, dispatch] = useReducer(reducer, 0); //useReducer 함수 호출
return (
(...)
);
}
export default TestComp;
5행에서 count는 State 변수이고, dispatch는 상태 변화 촉발 함수로 상태 변화 촉발 함수 reducer와 State 초깃값 0을 인수로 받는다.
다음으로는 count를 페이지에 렌더링한다.
import { useReducer } from "react";
function reducer() {}
function TestComp() {
const [count, dispatch] = useReducer(reducer, 0);
return (
<div>
<h4>테스트 컴포넌트</h4>
<div>
<bold>{count}</bold> //count 렌더링
</div>
<div>
<button> + </button>
<button> - </button>
</div>
</div>
);
}
export default TestComp;
버튼을 클릭하면 count를 증가시키거나 감소시키기 위해 함수 dispatch를 호출하는 이벤트 핸들러 onClick을 추가한다.
import { useReducer } from "react";
function reducer() {}
function TestComp() {
const [count, dispatch] = useReducer(reducer, 0);
return (
<div>
<h4>테스트 컴포넌트</h4>
<div>
<bold>{count}</bold> //count 렌더링
</div>
<div>
<button onClick={() => dispatch({type: "INCREASE", data: 1})}>
// <+>버튼을 누르면 함수 dispatch를 호출하고 인수로 객체를 전달한다.
+
</button>
<button onClick={() => dispatch({type: "DECREASE", data: 1})}>
// <->버튼을 누르면 함수 dispatch를 호출하고 인수로 객체를 전달한다.
-
</button>
</div>
</div>
);
}
export default TestComp;
dispatch 함수는 <+> 버튼이 클릭되었을 때 호출되는 함수로 상태 변화를 촉발하는 역할을 한다. 이때 State 정보를 가진action 객체를 인수로 전달한다.
<+> 버튼 클릭 시 :
dispatch는 두 프로퍼티를 가진 action 객체를 전달한다.
type 프로퍼티는 상황을 나타내며, <+> 버튼 클릭 시 값은 "INCREASE"다.
data 프로퍼티는 상태 변화에 필요한 값이며, <+> 버튼 클릭 시 값은 1이다.
<-> 버튼 클릭 시 :
이 때에도 dispatch를 호출하며, action 객체의 type은 "DECREASE"이고 data는 -1이다.
그러나 버튼을 클릭해도 실제 상태 변화는 reducer 함수에서 일어난다.
dispatch를 호출하면 reducer 함수가 실행되고, 반환값이 새로운 state가 된다.
정리하면, dispatch를 호출하면 reducer가 실행되고, 이 함수가 반환하는 값으로 state가 업데이트된다.
다음은 TestComp에서 함수 reducer를 작성하도록 한다.
function reducer(state, action) {//state : 현재 State 값이 저장, action : dispatch의 action객체 전달
switch(action.type){
case "INCREASE":
return state + action.data;//기존 State에 action객체의 data 값을 더해 반환
case "DECREASE":
return state - action.data;//기존 State에 action객체의 data 값을 빼서 반환
default:
return state;
}
}
function TestComp() {
(...)
}
export default TestComp;
이제 <+>,<-> 버튼으로 count를 증가시키거나 감소시킬 수 있다. <0으로 초기화> 등 새로운 상태 변화가 필요한 경우 함수 reducer에서 새로운 case를 추가하고 버튼을 새로 만드는 등 자유롭게 추가하면 된다!
앞서 배운 useReducer를 활용하여 상태 변화 코드를 컴포넌트와 분리할 수 있었으니, 이제 [할 일 관리] 앱에 적용하여 보자.
[ useState를 useReducer로 변경 ]
State를 관리하는 역할을 useState에서 useReducer로 바꾼다.
import {useRef, useReducer} from "react";
(...)
function reducer(state, action) {
//상태 변화 코드
return state;
}
function App() {
(...)
const[todo, dispatch] = useReducer(reducer, mockTodo); //useState->useReducer로 변경
return(
(...)
);
}
export default App;
이제 앞으로는 상태 변화가 필요할 때 set 함수 대신 dispatch 함수를 호출하므로, setTodo 함수 관련 코드를 삭제한다.
const onCreate = (content) => { //setTodo 함수 전면 삭제
idRef.current += 1;
};
const onUpdate = (targetId) => {
};
const onDelete = (targetId) => {
};
[ Create: 할 일 아이템 추가하기 ]
아이템 추가 기능을 구현하기 위해 useReducer를 이용하자. 먼저 함수 onCreate에서 함수 dispatch를 호출하고 인수로 할 일 정보를 담은 action 객체를 전달하도록 App 컴포넌트를 수정한다.
const onCreate = (content) => {
dispatch({ //할 일 아이템 생성
type: "CREATE", //추가
newItem: { //추가할 할 일 데이터 설정
id: idRef.current,
content,
isDone: false,
createdDate: new Date().getTime(),
},
});
idRef.current += 1;
};
이제 함수 reducer에서 action 객체의 type이 CREATE일 때, 새 아이템을 추가하는 상태 변화 코드를 작성한다.
function reducer(state, action) {
switch(action.type){ //type별로 상태 변화 코드 할당
case "CREATE": //추가 시 동작
return [action.newItem, ...state]; //기존 아이템에 action 객체의 새 아이템 추가된 새 배열 반환
default:
return state;
}
}
함수 dispatch를 호출해 인수로 action 객체를 전달하면, State가 업데이트되어 할 일 아이템에 새로 추가된다.
[ Update: 할 일 아이템 수정하기 ]
이번에는 할 일 아이템을 수정하는 함수를 다시 작성한다.
(...)
function App(){
(...)
const onUpdate = (targetId) => {
dispatch({
type: "UPDATE", //type 프로퍼티:update로 설정
targetId, //targetId 프로퍼티:수정할 아이템의 id 설정
});
};
(...)
}
다음으로 action 객체의 type 프로퍼티가 update일 때 할 일을 수정하는 상태 변화 코드를 추가한다.
(...)
function reducer(state, action) {
switch(action.type){
case "CREATE":
return [action.newItem, ...state];
case "UPDATE" : {
return state.map((it) => //map 메서드로 순회+매개변수 state에 저장된 아이템 배열에서
it.id === action.targetId //각 id 값 비교 후 일치하면
?{
...it,
isDone: !it.isDone, // isDone을 토글한 새 배열 반환
}
: it
);
}
default:
return state;
}
}
이제 할 일 아이템들을 체크박스로 체크할 수 있다.
[ Delete: 할 일 삭제 구현 ]
이제 할 일을 삭제하는 함수 onDelete를 수정한다.
(...)
function App(){
(...)
const onDelete = (targetId) => {
dispatch({
type: "DELETE",
targetId, //targetId 프로퍼티->삭제할 아이템 id 설정
});
};
(...)
}
다음으로 action 객체의 프로퍼티 type이 DELETE일 때 할 일을 삭제하도록 상태 변화 코드를 추가한다.
function reducer(state, action) {
switch(action.type){
(...)
case "DELETE": { //type 프로퍼티가 DELETE일 때
return state.filter((it) => it.id !== action.targetId); //filter 메서드->id, targetId가 일치하는 것 제외 할 일 배열 반환
}
default:
return state;
}
}
이를 통해 State 변수를 관리하는 useState를 useReducer로 대체하여 상태 변화 코드를 컴포넌트 밖으로 분리하고, 이를 적용하여 [할 일 관리 앱]을 한층 업그레이드할 수 있었다.
[ 최적화란? ]
웹 서비스의 성능을 개선하여 불필요한 연산을 줄이고 렌더링 속도를 높이는 기술이다. 리액트는 성능이 우수하지만, 프로그래머의 실수로 인한 성능 저하를 방지할 수는 없어 직접 최적화하는 노력이 필요하다. 리액트 앱에서 연산 최적화는 대부분 ‘메모이제이션(Memoization)’ 기법을 이용한다.
📌메모이제이션 (Memoization)이란?
말 그대로 '메모하는 방법'이다. 메모이제이션은 특정 입력에 대한 결과를 계산해 메모리 어딘가에 저장했다가, 동일한 요청이 들어 오면 저장한 결괏값을 제공해 빠르게 응답하는 기술로 불필요한 연산을 줄여 주어 프로그램의 실행 속도를 빠르게 만든다. (cf.알고리즘을 공부하는 사람들은 이 기능을 동적 계획법(Dynamic Programming, 줄여서 DP) 이라고 한다.)
[할 일 분석 기능 추가하기]
불필요한 함수 호출이 언제 발생하는지 살펴보기 위해 앞서 만든 [할 일 관리] 앱에 추가한 할 일 아이템이 모두 몇 개인지, 또 완료 아이템과 미완료 아이템은 각각 몇 개인지 검색해 페이지에 렌더링하는 기능을 TodoList 컴포넌트에 추가한다.
//할 일 분석 함수 analyzeTodo 추가하기
const analyzeTodo = () => {
const totalCount = todo.length;
const doneCount = todo.filter((it) => it.isDone).length;
const notDoneCount = totalCount - doneCount;
return {
totalCount,
doneCount,
notDoneCount,
};
};
//analyzeTodo 호출하고 반환값을 페이지에 렌더링하기
return (
<div className="TodoList">
<h4>Todo List</h4>
<div>
<div>총개수: {totalCount}</div>
<div>완료된 할 일: {doneCount}</div>
<div>아직 완료하지 못한 할 일: {notDoneCount}</div>
</div>
(...)
</div>
);
./src/component/TodoList.js
TodoList의 검색 폼 위에 분석 결과를 렌더링해보면 페이지 하단에 있는 할 일 아이템의 체크박스를 클릭해, 분석 결과의 완료 또는 미완료 카운트가 잘 변경되는 지 확인할 수 있다.
[❗문제점 파악하기 ]
analyzeTodo는 todo에 저장한 아이템 개수에 비례해 수행할 연산량이 증가한다. 만약 todo에 저장한 아이템 개수가 많아지면 성능상의 문제를 일으킬 수 있다. 함수에 대한 불필요한 호출이 있는지 확인하기 위해 함수 analyzeTodo를 호출할 때마다 콘솔에 메시지를 출력하도록 수정한다.
const analyzeTodo = ()= > {
console.log("analyzeTodo 함수 호출");
(...)
};
수정 후 함수 analyzeTodo가 얼마나 빈번히 호출되는지 확인하기 위해 TodoList 컴포넌트의 검색 폼에서 검색어 ‘react’를 입력해 본다.
TodoList 컴포넌트를 처음 마운트할 때 1번, 검색 폼에서 react 다섯 글자를 입력할 때마다 TodoList가 리렌더되어 총 5번 더 출력되는 것을 확인할 수 있다. 컴포넌트 내부에서 선언한 함수는 렌더링할 때마다 실행되기 때문이다.
✅ useMemo의 기본 사용법
useMemo를 사용하면 특정 함수를 호출했을 때 그 함수의 반환값을 기억하고 같은 함수를 다시 호출하면 기억해 두었던 값을 반환한다. useMemo를 이용하면 함수의 반환값을 다시 구하는 불필요한 연산을 수행하지 않아 성능을 최적화할 수 있다.
함수 useMemo를 호출하고 2개의 인수로 콜백 함수와 의존성 배열(deps)을 전달한다. 호출된 useMemo는 의존성 배열에 담긴 값이 바뀌면 콜백 함수를 다시 실행하고 결괏값을 반환한다.
[함수 analyzeTodo 재호출 방지하기]
useMemo를 이용해 [할 일 관리] 앱에 추가한 함수 analyzeTodo를 불필요하게 다시 호출하지 않도록 최적화한다.
const analyzeTodo = useMemo(() => {
console.log("analyzeTodo 함수 호출");
const totalCount = todo.length;
const doneCount = todo.filter((it) => it.isDone).length;
const notDoneCount = totalCount - doneCount;
return {
totalCount,
doneCount,
notDoneCount,
};
},[todo]);
const { totalCount, doneCount, notDoneCount } = analyzeTodo;
이제 최적화가 잘 이루어졌는지 확인해보기 위해 전과 같이 검색폼에 react를 입력해보고 콘솔창에 메세지가 몇 번 출력되는 지 확인해 본다.
검색어를 입력해도 todo 값은 변하지 않았기 때문에 analyzeTodo함수를 다시 호출하지 않는 모습을 확인할 수 있다. 새 할 일 아이템으로 독서하기 를 추가해보면, todo 값이 업데이트 되므로 useMemo는 다시 연산을 수행해 analyzeTodo가 다시 호출되었다.
📌 고차 컴포넌트
고차 컴포넌트는 HOC라고도 하는데 이는 Higher Order Component의 약자이다. 컴포넌트의 기능을 다시 사용하기 위한 리액트의 고급 기술로 인수로 전달된 컴포넌트를 새로운 컴포넌트로 반환하는 함수이다. 고차 컴포넌트는 전달된 컴포넌트를 그대로 반환하는 게 아니라 어떤 기능을 추가해 반환하는 것이 특징이다.
기능을 추가해 반환한 컴포넌트를 강화된 컴포넌트라고 한다. 위 그림은 고차 컴포넌트인 withFunc를 이용해 '기능 A'라는 컴포넌트를 감싼 다음 새 기능이 추가된 강화된 컴포넌트(EnhancedComp)를 반환하는 예시이다. 코드로 작성하면 아래와 같다.
cosnt EnhancedComp = withFunc(Comp);
📌횡단 관심사 (Cross-Cutting Concerns)
횡단 관심사란 크로스 커팅 관심사(Cross-Cutting Concerns) 라고도 하는데, 프로그래밍에서 비즈니스 로직과 구분되는 공통 기능을 지칭할 때 사용하는 용어이다. 반면 비즈니스 로직은 해당 컴포넌트가 존재하는 핵심 기능을 표현할 때 사용한다. 프로그래밍에서 횡단 관심사는 주로 로깅, 데이터베이스 접속, 인가 등 여러 곳에서 호출해 사용하는 코드들을 말한다.
컴포넌트의 핵심 기능(비즈니스 로직)을 세로로 배치한다고 했을 때, 여러 컴포넌트 에서 공통으로 사용하는 기능은 가 로로 배치하게 되어 공통 기능들이 핵심 컴포넌트들을 마치 ‘횡단’하는 모습이다.
모든 컴포넌트가 마운트와 동시에 콘솔에 특정 메시지를 출력하는 기능은 컴포넌트의 핵심 로직은 아니다. 여러 컴포넌트에서 횡단 관심사 코드를 작성하는 일은 중복 코드를 만드는 주요 요인 중 하나다. ❗ 이때 고차 컴포넌트를 사용하면 횡단 관심사 코드를 분리할 수 있다.
function withLifecycleLogging(WrappedComponent) {
return (props) => {
// Mount와 Unmount 로그
useEffect(() => {
console.log("Mount!");
return () => console.log("Unmount!");
}, []);
// Update 로그
useEffect(() => {
console.log("Update!");
});
// 래핑된 컴포넌트 렌더링
return <WrappedComponent {...props} />;
};
}
함수 withLifecycleLogging은 인수로 컴포넌트를 받는다. 그리고 해당 컴포넌트가 마운트, 업데이트, 언마운트할 때마다 콘솔에 로그를 출력하도록 기능을 추가한 ‘강화된 컴포넌트’를 반환한다.
(cf. 보통 고차 컴포넌트에 인수로 전달된 컴포넌트를 ‘래핑된 컴포넌트’라고 하고, 고차 컴포넌트가 반환하는 컴포넌트를 ‘강화된 컴포넌트’라고 한다.)
✅ React.memo의 기본 사용법
React.memo는 인수로 전달한 컴포넌트를 메모이제이션된 컴포넌트로 만들어 반환한다. Props가 메모이제이션의 기준이 되고 React.memo가 반환하는 컴포넌트는 부모 컴포넌트에서 전달된 Props가 변경되지 않는 한 리렌더되지 않는다.
사용법은 강화하고 싶은(메모이제이션을 적용하고 싶은) 컴포넌트를 React.memo로 감싸면 된다.
[Header 컴포넌트의 리렌더 방지하기]
[ 할 일 관리 ] 앱의 Header 컴포넌트는 부모 컴포넌트인 App에서 아무런 Props도 받지 않고 단지 오늘 날짜를 표시하는 아주 단순한 기능만 하기 때문에 이 컴포넌트는 어떠한 경우에서도 리렌더할 필요가 없다. 문제점을 확인해보기 위해 콘솔창을 이용해 리렌더가 발생하는 지 확인해 본다.
const Header = () => {
console.log("Header 업데이트"); //Header 컴포넌트 호출, 리렌더 될 때마다 콘솔에 출력
(...)
};
export default Header;
부모 컴포넌트인 App 컴포넌트가 리렌더되었기 때문에 자식 컴포넌트인 Header 컴포넌트도 불필요하게 리렌더 된다는 문제를 확인할 수 있다. 이 문제를 해결하기 위해서 React.memo를 사용해 해결한다.
import React from "react";
(...)
export default React.memo(Header);
Header 컴포넌트를 내보낼 때 React.memo로 감쌌더니 불필요한 리렌더가 제거된 모습이다.
[TodoItem 컴포넌트의 리렌더 방지하기]
마찬가지로 어떤 상황에서 불필요한 렌더링이 일어나는지 알아보기 위해 Todoltem을 렌더링할 때마다 해당 아이템의 id를 포함하는 문자열을 콘솔에 출력해 본다. 새 할 일 아이템으로 독서하기 를 추가한 뒤 콘솔창을 확인한다.
처음 마운드 시점에서 3개의 Todoltem을 렌더링하면, 콘솔에는 아이템당 한 번 씩 총 3번의 메시지가 출력되고 새 아이템을 추가하면, 3번 아이템을 마운트하면서 한 번, 나머지 0, 1, 2번 아이템을 리렌더하면서 각각 한 번씩 총 3번의 메시지를 출력한다. 결과적으로 새 아이템을 추가하면 총 4번의 메시지가 추가로 콘솔에 출력된다.
할 일 아이템인 Todoltem 컴포넌트는 개별 아이템 체크박스에서 완료/미완료를 토글할 때가 아니면 리렌더할 필요가 없기 때문에 현재 Todoltem의 렌더링은 불필요한 리렌더이다.
import React from "react";
const TodoItem = ({ id, content, isDone, createdDate, onUpdate, onDelete }) => {
(...)
};
export default React.memo(TodoItem);
하지만, Todoltem은 Props로 id, content, isDone, createdDate와 같이 원시 자료형에 해당하는 값뿐만 아니라, onUpdate, onDelete와 같이 객체 자료형에 해당하는 함수도 받는다. 이 함수들은 App 컴포넌트에서 생성되어 Props로 전달된다.
즉, App 컴포넌트를 리렌더 하면 함수 onUpdate, onDelete가 다시 만들어지는데, 이때 함수는 새롭게 선언한 것과 마찬가지로 참좃값이 변경된다. 따라서 이 함수를 Props로 받는 컴포넌트는 React.memo를 적용했다고하더라도 다시 렌더링되는 문제가 발생한다. 이런 문제 때문에 컴포넌트를 리렌더해도 함수를 다시 생성하지 않도록 만들어 주는 리액트 훅 useCallback을 사용할 수 있다.
✅ useCallback의 기본 사용법
useCallback은 useMemo처럼 2개의 인수를 제공한다. 첫 번째 인수로는 메모이제이션하려는 콜백 함수를 전달하고, 두 번째 인수로는 의존성 배열을 전달하고 그 결과로 useCallback은 메모이제이션된 함수를 반환한다.
useCallback은 의존성 배열에 담긴 값이 바뀌면 첫 번째 인수로 전달한 콜백 함수를 다시 만들어 반환한다. 만약 첫 번째 인수로 전달한 콜백 함수를 어떤 경우에도 다시 생성되지 않게 하려면 의존성 배열을 빈 배열로 전달하면 된다.
[useCallback과 함수형 업데이트]
const onCreate = useCallback(()=>{
setState( [newltem, ...state]);
}, [])
위와 같이 의존성 배열에 빈 배열을 전달하면 함수 onCreate는 처음 생성된 후 컴포넌트가 리렌더 된다고 해도 다시 생성되지 않기 때문에 useCallback에서 전달한 콜백 함수에서 State 변수에 접근하면 컴포넌트를 마운트할 때의 값, 즉 State의 초깃값이 반환된다.
useCallback으로 래핑된 함수 onCreate는 State의 변화를 추적하지 못하므로 자칫 의도치 않은 동작을 야기할 수 있다. 그렇다고 의존성 배열에 State 변수를 전달하면, 결국 이를 업데이트할 때마다 함수 onCreate를 계속 재생성하므로 useCallback을 적용한 의미가 사라진다.
❗ 이때는 setstate의 인수로 콜백 함수를 전달하는 리액트의 ‘함수형 업데이트’ 기능 을 이용하면 된다.
const onCreate = useCallback(() => {
setState((state) => [newltem, ...state]);
}, []);
이 함수는 항상 최신 State 값을 매개변수로 저장하고 콜백 함수가 반환한 값은 새로운 State 값이 되어 업데이트된다. 따라서 useCallback을 사용하면서 setState로 최신 State 값을 추적하려면 함수형 업데이트 기능을 이용해야 한다.
App 컴포넌트의 함수 onUpdate와 onDelete를 useCallback으로 메모이제이션해 이 함수들을 다시 생성하지 않도록 수정한다.
const onUpdate = useCallback((targetId) => {
dispatch({
type: "UPDATE",
targetId,
});
},[]);
const onDelete = useCallback((targetId) => {
dispatch({
type: "DELETE",
targetId,
});
},[]);
새 할 일 아이템을 추가해도 마운트 시점에만 한 번 콘솔에 메시지를 출력하는 것을 통해 최적화가 되었음을 알 수 있다.
[최적화할 때 유의할 점]
✔ 최적화는 항상 마지막에 하기
✔ 모든 것을 최적화할 필요는 없음
✔ 컴포넌트 구조를 잘 설계했는지 점검하기
✔ 최적화는 위 방법이 전부가 아님
**코드 작성 문제**
const onCreate = (content) => {
dispatch({
type: "CREATE",
newItem: {
//______________,
content,
isDone: false,
createdDate: new Date().getTime(),
},
});
//____________________
};
2. 다음은 할 일 분석을 위해 만든 함수인데 컴포넌트 렌더링시 불필요하게 매번 호출되는 문제가 있다. 적절한 리액트 훅을 사용해 불필요한 재호출을 방지하시오.
const analyzeTodo = () => {
const totalCount = todo.length;
const doneCount = todo.filter((it) => it.isDone).length;
const notDoneCount = totalCount - doneCount;
return {
totalCount,
doneCount,
notDoneCount,
};
};
const { totalCount, doneCount, notDoneCount } = analyzeTodo();
1. action 객체 / 2. State / 3. useState / 4. 컴포넌트 밖으로 분리 / 5. 고차 컴포넌트 / 6. 메모이제이션 / 7. 횡단 관심사
코드 작성 문제
1.
const onCreate = (content) => {
dispatch({
type: "CREATE",
newItem: {
id: idRef.current, // 현재 ID를 사용
content,
isDone: false,
createdDate: new Date().getTime(),
},
});
idRef.current += 2; // ID를 2씩 증가
};
2.
const analyzeTodo = useMemo(() => {
console.log("analyzeTodo 함수 호출");
const totalCount = todo.length;
const doneCount = todo.filter((it) => it.isDone).length;
const notDoneCount = totalCount - doneCount;
return {
totalCount,
doneCount,
notDoneCount,
};
},[todo]);
const { totalCount, doneCount, notDoneCount } = analyzeTodo;
출처: 이정환, 『한 입 크기로 잘라먹는 리액트』, 프로그래밍인사이트(2023),
https://reactjs.winterlood.com/.
Corner React.js 2
Editor: chacha, ahyohyo
[React.js 2팀] project 3 [감정 일기장] 만들기 (0) | 2025.01.24 |
---|---|
[React.js 2팀] 9장. 컴포넌트 트리에 데이터 공급하기 (0) | 2025.01.17 |
[React.js 2팀] project 2 [할 일 관리] 앱 만들기 2 (Read: 할 일 리스트 렌더링하기 ~ Delete: 할 일 삭제하기) (1) | 2025.01.03 |
[React.js 2팀] project 2 [할 일 관리] 앱 만들기 1 (프로젝트 준비하기 ~ Create: 할 일 추가하기) (1) | 2024.12.27 |
[React.js 2팀] 8장. Hooks (0) | 2024.11.29 |