이것 역시 전 게시글과 마찬가지로 useMemo와 비슷하다.
const MyComponent = React.memo(function MyComponent(props) {
/* props를 사용하여 렌더링 */
});
리액트 공식문서에 따르면 다음과 같다.
React.memo는 Memoization(메모이제이션) 기법으로 동작하며, 고차 컴포넌트(Higher Order Component, HOC)이다.
컴포넌트가 props로 동일한 결과를 렌더링하면, React.memo를 호출하고 결과를 메모이징(Memoizaing) 하도록 래핑하여 경우에 따라 성능 향상을 할 수 있다.
즉, React는 컴포넌트를 재렌더링하지 않고 마지막으로 렌더링된 결과를 재사용한다.
React.memo는 props 변화에만 영향을 준다.
즉, 함수 컴포넌트 안에서 구현한 state나 context가 변할 때는 재렌더링된다.
props가 갖는 복잡한 객체에 대하여 얕은 비교만을 수행하는 것이 기본 동작이다.
다른 비교 동작을 원한다면, 두번째 인자로 별도의 비교 함수를 제공하면 된다.
이렇게 적혀있는데 처음 읽는다면 어려울 수 있다.
하지만 걱정할 필요가 없다. 이것은 useMemo와 같다고 보면 된다.
- props가 이전과 동일한 값이면 재렌더링하지 않고, 다른 값이면 재렌더링하여 컴포넌트를 다시 만들어 반환한다.
- React.memo에 쓰인 컴포넌트 안에서 구현한 state가 변경되면 컴포넌트는 재렌더링이 된다.
이렇게 적어도 감이 안오는 경우가 있으므로 실습을 만들어보자.
🧐 어떻게 동작하는지 실습해보기
import React, { useEffect, useMemo, useState } from 'react';
const TextView = ({ text }) => {
useEffect(() => {
console.log('text 변경됨');
});
return <div>{text}</div>;
};
const CountView = ({ count }) => {
useEffect(() => {
console.log('count가 변경됨');
});
return <div>{count}</div>;
};
function App() {
const [count, setCount] = useState(0);
const [text, setText] = useState('a');
return (
<div className="App">
<TextView text={text} />
<input value={text} onChange={(e) => setText(e.target.value)} />
<br />
<CountView count={count} />
<button onClick={() => setCount(count + 1)}>count 1 증가</button>
</div>
);
}
export default App;
이번에도 App.js 파일에서 간단하게 실습해보자.
잠깐 얘기하자면 TextView 컴포넌트와 CountView 컴포넌트를 만들었다.
그 안에서는 useEffect()가 있는데 2번째 인자에 값이 없으므로 그 컴포넌트가 업데이트가 될때마다 실행이된다.
즉, 컴포넌트가 재렌더링될때마다 콘솔창에 출력을 하는 것이다.
이렇게 작성하여 실행해보면 다음과 같다.
여기서 입력칸에 한글자와 count 1증가 버튼을 1번 눌러보면 전부 재렌더링되어 콘솔창에 전부 합해서 4개가 출력된다.
자 그러면 text가 변경되었는데 count가 재렌더링될 필요는 없고, 그 반대도 마찬가지이니 React.memo로 랜더링을 막아보자.
const TextView = React.memo(({ text }) => {
useEffect(() => {
console.log('text 변경됨');
});
return <div>{text}</div>;
});
const CountView = React.memo(({ count }) => {
useEffect(() => {
console.log('count가 변경됨');
});
return <div>{count}</div>;
});
각 컴포넌트에 React.memo만 추가하여 다시 실행해보자.
count 1증가 버튼만 11번 클릭했는데 CountView 컴포넌트만 재렌더링이 11번 된 것을 볼 수 있다.
계획대로 TextView 컴포넌트는 업데이트 되지 않았다.
이게바로 React.memo이다.
props로 들어오는 값(text)이 동일하여 TextView는 이전 렌더링 결과값을 그대로 반환하였고,
props로 들어오는 값(count)이 이전과 동일하지 않아 CountView는 재렌더링하여 결과값을 반환하였다.
그럼 입력칸에도 동일하게 입력하여 되는지 확인해보자.
동일하게 TextView 컴포넌트만 재렌더링이 된 것을 볼 수 있다.
여기까지만 해도 감이 왔을 것이다.
그렇다면 공식문서에 적힌 객체는 얕은비교를 한다는건 무슨 말일까?
import React, { useEffect, useState } from 'react';
const NumView = React.memo(({ num }) => {
useEffect(() => {
console.log('num 변경됨');
});
return <div>{num}</div>;
});
const CountView = React.memo(({ count }) => {
useEffect(() => {
console.log('count가 변경됨');
});
return <div>{count.count}</div>;
});
function App() {
const [count, setCount] = useState({
count: 1,
});
const [num, setNum] = useState(1);
return (
<div className="App">
<NumView num={num} />
<button onClick={() => setNum(1)}>num값을 1로 변경</button>
<br />
<CountView count={count} />
<button onClick={() => setCount({ count: 1 })}>
count : 1 객체 생성
</button>
</div>
);
}
export default App;
이번에도 실습 파일을 만들어보자.
아까와 다르게 NumView 컴포넌트와 CountView 컴포넌트가 있다.
num의 값은 1로 초기화되어있고, 버튼도 num값을 1로 변경하는 것을 볼 수 있다.
count 값은 객체로 { count : 1 } 로 초기화되어있고, 버튼은 { count : 1 } 객체로 변경해주고 있다.
이번에는 바로 실행하여 num 값을 먼저 10번 눌러보자.
처음에 나온 출력문을 제외하고, 버튼을 10번 눌렸을때 아무런 렌더링을 하지 않는다.
왜냐하면 props로 들어가는 num의 값이 이전과 동일하기 때문에 재렌더링하지 않는 것을 볼 수 있다.
그렇다면 count 객체는 어떨까?
CountView 컴포넌트가 버튼을 누른 횟수만큼 재렌더링이 된 것을 볼 수 있다.
이것은 props로 들어간 객체가 기본 동작으로 얕은 비교를 해서이다.
얕은 비교란 객체의 값이 아닌 객체의 주소값을 비교한다.
따라서 다음과 코드와 같다.
let a = { count : 1 };
let b = { count : 1 };
console.log(a === b); // false가 나온다!!!
// ===============
let a = { count : 1 };
let b = a;
console.log(a === b); // true가 나온다!!!
이렇게 처음에는 서로 다른 주소값을 가졌기에 false가 나오고, 두번째는 값은 주소값을 가지기 true가 나온다.
이제 얕은 비교가 무엇인지 알게되었으니 우리가 직접 비교 함수를 만들어보자.
const CountView = ({ count }) => {
useEffect(() => {
console.log('count가 변경됨');
});
return <div>{count.count}</div>;
};
const areEqual = (prevProps, nextProps) => {
return prevProps.count.count === nextProps.count.count;
};
// 고착 컴포넌트
const MemoizedCountView = React.memo(CountView, areEqual);
function App() {
const [count, setCount] = useState({
count: 1,
});
const [num, setNum] = useState(1);
return (
<div className="App">
<NumView num={num} />
<button onClick={() => setNum(1)}>num값을 1로 변경</button>
<br />
<MemoizedCountView count={count} />
<button onClick={() => setCount({ count: 1 })}>
count : 1 객체 생성
</button>
</div>
);
areEqual() 라는 비교함수를 만들었다.
이전 props와 현재 props 객체 안의 값인 count가 동일한지 비교해준다.
가독성을 위해 CountView에 바로 React.memo를 적은것을 없애고 따로 만들었다.
따라서 MemoizedCountView 컴포넌트를 만들었다.
여기에 React.memo(컴포넌트, 비교함수) 를 넣어서 동작하게 만들었다.
이렇게 실행해보면 버튼 두개다 동일한 값이 계속 들어간것으로 판단하여 재렌더링을 하지 않는 것을 볼 수 있다.
여기까지가 React.memo에 대한 공식문서를 참고한 내용이다.
결론
- useMemo처럼 Memoization 기법으로 동작
- 인자로 받는 props가 변화할 때 재렌더링되고, 이전과 동일하면 예전 결과값을 반환
- React.memo로 감싸진 함수 컴포넌트 안에서 구현한 state가 변화하면 재렌더링 된다.
- React.memo에서 props가 객체로 받아진다면 얕은 비교로 기본동작한다.(비원시타입 동일)
- React.memo에서 다른 비교 동작을 원한다면 두번째 인자에 별도의 비교함수를 만들어야 한다.
'프로그래밍 > React.js' 카테고리의 다른 글
[React] useMemo란? (0) | 2022.05.28 |
---|---|
[크롬 확장프로그램]React Developer Tools 설치 (0) | 2022.05.27 |
[React]리액트에서 Redux 써보기 (0) | 2022.05.24 |