일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- Kotlin
- React #API 호출 #async #await #fetch
- useMemo #React.memo #최적화 #re-rendering
- Common JS 모듈 시스템
- useReducer
- 패키지
- React #Context #props drilling #useMemo
- React #React 사용하는 이유
- Node.js #
- useCallback #최적화 #함수형 업데이트
- Today
- Total
세현's 개발로그
[React] useMemo(), React.memo 최적화(feat. 일기장) 본문
◈ Memoization
답을 기억하는 메모이제이션을 프로그래밍에도 사용할 수 있다.
◈ 최적화
최적화는 쉽게 말해 re-render 되는 횟수를 줄이는 것이다.
<re-rendering 되는 경우>
1. Props가 변경
2. State가 변경
3. 부모 컴포넌트가 re-render
4. Context value가 변경
메모이제이션을 이용하여 rendering 최적화를 해보자.
App.js
1) const getDiaryAnalysis = useMemo(() => {
if (data.length === 0) {
return { goodcount: 0, badCount: 0, goodRatio: 0 };
}
console.log("일기 분석 시작");
const goodCount = data.filter((it) => it.emotion >= 3).length;
const badCount = data.length - goodCount;
const goodRatio = (goodCount / data.length) * 100.0;
return { goodCount, badCount, goodRatio };
}, [data.length]);
useMemo함수는 첫번째 인자로 콜백함수를 받아서 콜백함수가 리턴하는 값을 연산에 최적화할 수 있도록 도와준다.
goodCount는 filter를 이용하여 감정 점수가 3이상인 일기의 개수를 센다.
badCount는 전체개수-goodCount
goodRatio는 전체 일기의 개수에서 감정 점수가 3이상인 일기의 비율을 계산해준다.
이 세 가지를 객체로 담아 return 한다.
이때 두 번째 인자로 [data.length]를 전달해준 것은, data의 길이가 바뀔 때(일기가 추가되거나 삭제될 때)만 re-rendering 되도록 하기 위함이다.
2) const { goodCount, badCount, goodRatio } = getDiaryAnalysis;
비구조화할당으로 값을 변수에 담아준다. 이때, 메모이제이션을 이용했으므로 getDiaryAnalysis는 함수를 호출하는 것이 아닌 값을 저장하고 있는 것이므로 getDiaryAnalysis()로 쓰지 않도록 주의해야 한다.
import { useEffect, useMemo, useRef, useState } from "react";
import "./App.css";
import DiaryEditor from "./DiaryEditor";
import DiaryList from "./DiaryList";
const App = () => {
const [data, setData] = useState([]);
const dataId = useRef(0);
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);
};
useEffect(() => {
setTimeout(() => {
getData();
}, 1500);
}, []);
const onCreate = (author, content, emotion) => {
const created_date = new Date().getTime();
const newItem = {
author,
content,
emotion,
created_date,
id: dataId.current
};
dataId.current += 1;
setData([newItem, ...data]);
};
const onRemove = (targetId) => {
const newDiaryList = data.filter((it) => it.id !== targetId);
setData(newDiaryList);
};
const onEdit = (targetId, newContent) => {
setData(
data.map((it) =>
it.id === targetId ? { ...it, content: newContent } : it
)
);
};
const getDiaryAnalysis = useMemo(() => {
if (data.length === 0) {
return { goodcount: 0, badCount: 0, goodRatio: 0 };
}
console.log("일기 분석 시작");
const goodCount = data.filter((it) => it.emotion >= 3).length;
const badCount = data.length - goodCount;
const goodRatio = (goodCount / data.length) * 100.0;
return { goodCount, badCount, goodRatio };
}, [data.length]);
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 onEdit={onEdit} onRemove={onRemove} diaryList={data} />
</div>
);
};
export default App;
useMemo는 deps가 변경되기 전까지 값을 기억하고, 실행 후 값을 보관하는 역할로도 사용한다. 복잡한 함수의 return 값을 기억한다는 점에서 useEffect와 다르다. useRef는 특정 값을 기억하는 경우, useMemo는 복잡한 함수의 return값을 기억하는 경우에 사용한다.
◈ React.memo란?
[React 공식 문서]
React.memo는 고차 컴포넌트(Higher Order Component)이다.
(=컴포넌트를 가져와 새 컴포넌트를 반환하는 함수)
즉, rendering 결과를 Memoizing 함으로써, 불필요한 re-rendering을 건너뛴다.
cont MyComponent = React.memo(function MyComponent(props) {
/* props를 사용하여 랜더링 */
});
혹은 export 할 때 감싸서 내보낸다.
export default React.memo(OptimizeTest);
- React.memo는 props에 변화가 일어날 때만 영향을 준다.
- React.memo(re-rendering이 일어나지 않았으면 하는 컴포넌트)를 입력하면 props가 변화하지 않으면 반환되지 않는 고차 컴포넌트(강화된 컴포넌트)를 돌려준다. 물론 자기 자신의 state가 바뀌면 re-rendering이 일어난다.
OptimizeTest.js 라는 테스트용 컴포넌트를 생성하여 count와 obj(객체)를 자식컴포넌트로 두어 re-rendering이 어떻게 일어나는지 확인하자.
OptimizeTest.js
1) const CounterA = React.memo(({ count }) => {
useEffect(() => {
console.log(`CountA Update - count : ${count}`);
});
return <div>{count}</div>;
});
const CounterB = ({ obj }) => {
useEffect(() => {
console.log(`CountB Update - count : ${obj.count}`);
});
return <div>{obj.count}</div>;
};
count와 odj 상태를 받아서 prop으로 받아서 활용할 두 개의 자식컴포넌트를 만들어준다.
odj는 객체이기 때문에 점표기법으로 count를 꺼내서 쓴다.
2) const areEqual = (prevProps, nextProps) => {
if (prevProps.obj.count === nextProps.obj.count) {
return true;
}
return false;
};
const MemoizedCounterB = React.memo(CounterB, areEqual);
areEqual 함수를 만들어서 true반환을 하면 리랜더링을 일으키지 않고 false라면 리랜더링을 일으킨다.
그리고나서 MemoizedCounterB 컴포넌트를 새로 만들어서 React.memo에 첫 번째 인자로 CounterB를 넣고 두 번째 인자로는 areEqual 함수를 넣어주었다. areEqual의 상태에 따라 리랜더링 여부를 결정하게 된다.
3) const OptimizeTest = () => {
const [count, setCount] = useState(1);
const [obj, setObj] = useState({
count: 1
});
odj useState 안에는 객체로 count:1이라는 프로퍼티를 넣어둔다.
4) <div>
<h2>Counter A</h2>
<CounterA count={count} />
<button onClick={() => setCount(count)}>A Button</button>
</div>
CounterA는 setCount에 count를 전달한다. 그러면 setCount로 상태변화를 일으켰지만 바뀌는 값은 없으므로 리랜더링이 되지 않는다.
5) <div>
<h2>Counter B</h2>
<MemoizedCounterB obj={obj} />
<button onClick={() => setObj({ count: 1 })}>B Button</button>
</div>
CounterB는 setObj로 상태변화를 시킬건데 마찬가지로 값이 바뀌지 않으므로 리랜더링이 되지 않는다.
import React, { useEffect, useState } from "react";
const CounterA = React.memo(({ count }) => {
useEffect(() => {
console.log(`CountA Update - count : ${count}`);
});
return <div>{count}</div>;
});
const CounterB = ({ obj }) => {
useEffect(() => {
console.log(`CountB Update - count : ${obj.count}`);
});
return <div>{obj.count}</div>;
};
const areEqual = (prevProps, nextProps) => {
if (prevProps.obj.count === nextProps.obj.count) {
return true;
}
return false;
};
const MemoizedCounterB = React.memo(CounterB, areEqual);
const OptimizeTest = () => {
const [count, setCount] = useState(1);
const [obj, setObj] = useState({
count: 1
});
return (
<div style={{ padding: 50 }}>
<div>
<h2>Counter A</h2>
<CounterA count={count} />
<button onClick={() => setCount(count)}>A Button</button>
</div>
<div>
<h2>Counter B</h2>
<MemoizedCounterB obj={obj} />
<button onClick={() => setObj({ count: 1 })}>B Button</button>
</div>
</div>
);
};
export default OptimizeTest;
'React' 카테고리의 다른 글
[React] 복잡한 상태 관리 로직 분리하기 - useReducer (feat. 일기장) (0) | 2023.05.07 |
---|---|
[React] useCallback() 최적화 (feat. 일기장) (0) | 2023.05.07 |
[React] React에서 API 호출하기 (0) | 2023.05.06 |
[React] React Lifecycle 제어하기 - useEffect (0) | 2023.04.30 |
[React] React에서 배열 사용하기 (feat.일기장) (0) | 2023.04.30 |