React의 컴포넌트가 리랜더링 되는 경우는 다음과 같다.
1. 컴포넌트 자신이 가진 state에 변화가 생겼을 경우
2. 부모 컴포넌트에 리랜더링이 일어날 경우
3. 자신이 받은 prop이 변경되는 경우
컴포넌트가 꼭 필요한 경우에만 리랜더링 될 수 있도록 React에는 컴포넌트를 최적화할 수 있는 다양한 방법이 있다.
import React, {useMemo} from 'react';
React의 기능이기 때문에 useMemo를 import 해야함.
const getDiaryAnalysis = useMemo(
() => {
console.log("일기 분석 시작");
const goodCount = data.filter((it)=> it.emotion >= 3).length;
const badCount = data.length - goodCount;
const goodRatio = (goodCount / data.length) * 100;
return{goodCount, badCount, goodRatio};
}, [data.length]
);
const {goodCount, badCount, goodRatio} = getDiaryAnalysis; // getDiaryAnalysis을 함수가 아닌 값으로 사용
return (
<div className="App">
<div>전체 일기 : {data.length}</div>
<div>기분 좋은 일기 개수 : {goodCount}</div>
<div>기분 나쁜 일기 개수 : {badCount}</div>
<div>기분 좋은 일기 비율 : {goodRatio}</div>
</div>
);
기본공식 : useMemo(콜백함수,[]);
useMemo(콜백함수,[data.length]); => data.length 즉, 데이터의 길이가 변할때만 작동함
📌 중요!! useMemo를 사용하면 콜백함수가 리턴하는 값을 반환하기 때문에 getDiaryAnalysis 은 더이상 함수가 아니므로 값으로 사용.
useMemo를 사용해서 뒤의 배열에 어떤 값이 변화할 때 연산을 다시 수행할지 명시하면 그 함수를 값처럼 사용하여 연산 최적화를 할 수 있다.
이미 계산해 본 연산 결과를 기억해두었다가 동일한 계산을 시키면
다시 연산하지 않고 기억해두었던 데이터를 반환시키게 하는 방법
함수형 컴포넌트에게 업데이트 조건 걸기.
import React,{ useState, useEffect } from 'react';
const TextView = React.memo(({text}) => {
useEffect(()=>{
console.log(`Update :: Text : ${text}`)
});
return <div>{text}</div>;
});
const CountView = React.memo(({count}) => {
useEffect(()=>{
console.log(`Update :: Count : ${count}`)
});
return <div>{count}</div>;
});
const OptimizeTest = ()=>{
const [count, setCount] = useState(1);
const [text, setText] = useState("");
return (
<div style={{padding:50}}>
<div>
<h2>count</h2>
<CountView count={count}/>
<button onClick={()=>setCount(count+1)}>+</button>
</div>
<div>
<h2>text</h2>
<TextView text={text}/>
<input value={text} onChange={(e)=> setText(e.target.value)} />
</div>
</div>
)
};
export default OptimizeTest;
OptimizeTest와 내부 컴포넌트 TextView, CountView가 있다.
내부 컴포넌트가 받고 있는 prop인 text나 count 중 하나만 바뀌어도 두개의 컴포넌트가 모두 리랜더 된다.
이러한 낭비를 막기 위하여 컴포넌트가 자신이 받고있는 prop이 변화할때만 리랜더될 수 있도록 조건을 걸어줄 수 있는것이
React.memo
이다.
React.memo 를 사용하여 컴포넌트를 감싸주면 그 컴포넌트는 prop이 변화할 때만 리랜더된다.
하나의 컴포넌트 파일을 통채로 React.memo로 감싸기 위해서는 컴포넌트 마지막 줄 export에 아래와 같이 사용하면 된다.
export default React.memo(DiaryEditor);
React.memo는 props가 변화할 때 리랜더된다. 그러나 props는 객체에 대하여 얕은 비교만 하기 때문에 같은 객체의 값을 가지더라도 컴포넌트는 계속 리랜더가 되는 상황이 발생한다.
그러한 상황에서 성능 최적화를 위한 areEqual
함수를 사용할 수 있다.
nextProps가 prevProps와 같은 값을 가지면 true, 그렇지 않으면 false 반환함.
const areEqual = (prevProps, nextProps)=>{
return true // 이전 props와 현재 props가 같다 = 리랜더링 X
return false // 이전 props와 현재 props가 다르다 = 리랜더링 O
}
자식 컴포넌트 CounterB는 객체인 obj를 props로 받고 있기 때문에 최적화를 위하여
React.memo가 아닌 areEqual 함수를 사용한다. CounterB에 React.memo를 사용하여 감싸면 컴포넌트는 버튼을 누를 때마다 리랜더가 되는것을 useEffect를 통해 확인할 수 있었다.
prevProps과 nextProps을 비교하여 리랜더링 여부를 결정하는 areEqual 함수를 사용하고 새로운 컴포넌트 MemoizedCounterB를 만들어서 React.memo에 (최적화할 컴포넌트, areEqual) 를 넣어준다.
const CounterB = ({obj}) => {
useEffect(()=>{
console.log(`CounterB Update - count : ${obj.count}`)
})
return <div>{obj.count}</div>
}
const areEqual = (prevProps, nextProps)=>{ // areEqual 함수사용
if(prevProps.obj.count === nextProps.obj.count){
return true;
}
return false;
// return prevProps.obj.count === nextProps.obj.count;
// 위의 if문을 이렇게 쓸 수도 있음.
}
const MemoizedCounterB = React.memo(CounterB, areEqual);
// CounterB는 areEqual 함수의 판단에 따라서 리랜더링을 여부를 결정하는 메모화된 컴포넌트가 된다.
부모 컴포넌트에서는 CounterB가 아닌 메모화된 새로운 컴포넌트 MemoizedCounterB를 불러온다.
const OptimizeTest = ()=>{
const [obj, setObj] = useState({
count:1
})
return (
<div>
<h2>Counter B</h2>
<MemoizedCounterB obj={obj}/>
<button onClick={()=>setObj({
count: obj.count
})}>B button</button>
</div>
)
};
최적화할 컴포넌트 DiaryEditor 파일에서 React.memo를 사용하여 감싸주고,
해당 컴포넌트에서 prop으로 받고 있는 oncreate 함수가 포함되어 있는 부모 컴포넌트에 useCallback 을 사용한다.
기본공식 : useCallback(함수,[]);
export default React.memo(DiaryEditor);
리액트의 기능이므로 useCallback import 해준다.
import React, {useCallback} from 'react';
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]);
},[]);
자식 컴포넌트에 prop으로 넘겨주는 함수인 oncreate에 useCallback을 사용한다.
여기서 useCallback(기존 함수,[]); 형식으로 사용하게 되는데, 이 때 뒤의 배열을 비워두면 해당 함수가 실행될 때 기존의 데이터가 사라지고 새로운 데이터만이 남게된다. 마운트 될 때, 초기값인 뒤의 배열이 비어있기 때문인데 이 문제를 보완하기 위해 함수형 업데이트를 사용할 수 있다.
함수형 업데이트란 상태변화 함수인 setState 함수에 함수를 전달하는 것이다.
현재 함수 내부에 있는 상태변화 함수인 setData에 데이터를 전달하는 콜백함수를 넣어주면 해당 함수와 컴포넌트는 최적화 되어 정상적으로 작동하게 된다.
유익한 글이었습니다.