const Student = ({ name, age, address }) => {
return (
<div>
<h1>{ name }</h1>
<span>{ age }</span>
<span>{ address }</span>
</div>
)
}
const School = (props) => {
return (
<Student
name={"햅피빈"}
age={28}
address={"우리집"}
/>
);
};
React.memo에 어떤 컴포넌트를 넣어주면 UI적으로, 기능 적으로는 같지만 조금 더 최적화된 컴포넌트를 반환해줌
최적화된 컴포넌트는 렌더링이 되어야 할 상황에 놓일때마다 Prop Check를 통해서 자신이 받는 Props에 변화가 있는지 없는지 확인함
확인 후 Props의 변화가 있다면 -> 렌더링
확인 후 Props의 변화가 없다면 -> 기존에 렌더링 된 내용을 재사용함
React.memo의 memo는 Memoization을 의미
Memoization = 이미 계산해놓은 값을 메모리 상에 저장해놓고 필요할때마다 꺼내서 재사용하는 것
1) 컴포넌트가 같은 Props로 자주 렌더링 될 때
2) 컴포넌트가 렌더링 될때마다 복잡한 로직을 처리해야할 때
실제 코드 구현
ReactMemo.js
import React, { useState } from "react";
import Child from "../components/Child";
function ReactMemo() {
const [parentAge, setParentAge] = useState(0);
const [childAge, setChildAge] = useState(0);
const incrementParentAge = () => {
setParentAge(parentAge + 1);
};
// 자식 Age를 1씩 증가시켜주는 함수
const incrementChildAge = () => {
setChildAge(childAge + 1);
};
console.log("부모 컴포넌트가 렌더링 되었습니다");
return (
<div style={{ border: "2px solid navy", padding: "10px" }}>
<h1>부모</h1>
<p>age: {parentAge}</p>
<button onClick={incrementParentAge}>부모 나이 증가</button>
<button onClick={incrementChildAge}> 자녀 나이 증가</button>
<Child name={"홍길동"} age={childAge} />
</div>
);
}
export default ReactMemo;
Child.js
import React, { useState } from "react";
import Child from "../components/Child";
function ReactMemo() {
const [parentAge, setParentAge] = useState(0);
const [childAge, setChildAge] = useState(0);
const incrementParentAge = () => {
setParentAge(parentAge + 1);
};
// 자식 Age를 1씩 증가시켜주는 함수
const incrementChildAge = () => {
setChildAge(childAge + 1);
};
console.log("부모 컴포넌트가 렌더링 되었습니다");
return (
<div style={{ border: "2px solid navy", padding: "10px" }}>
<h1>부모</h1>
<p>age: {parentAge}</p>
<button onClick={incrementParentAge}>부모 나이 증가</button>
<button onClick={incrementChildAge}> 자녀 나이 증가</button>
<Child name={"홍길동"} age={childAge} />
</div>
);
}
export default ReactMemo;
그런데 부모 나이 증가 버튼을 누르면 부모의 나이만 증가하는데 콘솔에는 '자녀도 렌더링 되었습니다'라고 뜸
자녀 나이 증가 버튼을 눌러도 마찬가지로 자녀의 나이만 증가하는데 콘솔에는 '부모 컴포넌트가 렌더링 되었습니다'라고 뜸
React 컴포넌트는 자신의 State가 업데이트 될 때마다 다시 렌더링 되기 때문에 ReactMemo 컴포넌트의 State인 parentAge와 childAge가 업데이트 될 때마다 ReactMemo 컴포넌트가 다시 렌더링 됨
또한 컴포넌트가 가진 자식 컴포넌트도 다시 렌더링 됨
(여기에서는 Child 컴포넌트가 해당 됨)
따라서 '부모 나이 증가 버튼' 또는 '자녀 나이 증가 버튼'을 클릭하면 parentAge 또는 childAge State가 업데이트 되면서 ReactMemo 컴포넌트가 렌더링 되면서 위와 같은 내용이 콘솔에 출력이 됨
그런데 이렇게 불필요하게 자녀 컴포넌트까지 모두 렌더링이 된다면 비효율적임 -> 해결방법은? React.memo를 사용하자!
Child 컴포넌트의 전체 코드
React.memo는 React에서 제공하는 고차 컴포넌트 중 하나임
고차 컴포넌트는 하나의 함수임
함수는 컴포넌트를 인자를 받아서 또 다른 컴포넌트를 반환해줌
React.memo는 우리가 넣어준 Child 컴포넌트를 인자로 받아서 최적화된 Child 컴포넌트를 반환해줌
React.memo를 통해 최적화가 된 컴포넌트는 렌더링이 될 상황에 놓일때마다 prop check라는 것을 함
prop check는 컴포넌트가 받는 props에 변화가 있을때만 렌더링을 허락해주고, 만약 props의 변화가 없다면 렌더링을 하지 않고 이전에 이미 렌더링된 컴포넌트의 결과를 다시 재사용 함
ReactMemo.js
import React, { useState } from "react";
import Child from "../components/Child";
function ReactMemo() {
const [parentAge, setParentAge] = useState(0);
const [childAge, setChildAge] = useState(0);
const incrementParentAge = () => {
setParentAge(parentAge + 1);
};
console.log("부모 컴포넌트가 렌더링 되었습니다");
const name = {
lastName: "홍",
firstName: "길동",
};
return (
<div style={{ border: "2px solid navy", padding: "10px" }}>
<h1>부모</h1>
<p>age: {parentAge}</p>
<button onClick={incrementParentAge}>부모 나이 증가</button>
<Child name={name} />
</div>
);
}
export default ReactMemo;
Child.js
import React, { memo } from "react";
const Child = ({ name }) => {
console.log("자녀도 렌더링 되었습니다");
return (
<div style={{ border: "2px solid pink", padding: "10px" }}>
<h3>자녀</h3>
<p>name: {name.lastName}</p>
<p>name: {name.firstName}</p>
</div>
);
};
export default memo(Child);
이유는? -> name이 object(객체)형태이기 때문임
JavaScript에서의 object는 string, number타입과 같은 원시 타입과 다르게 변수 안에 그대로 저장되는게 아니라,
object가 저장되어있는 메모리의 주소가 여기에 저장되어 있음
ReactMemo 함수가 호출이 되면 함수 안의 모든 변수들이 다시 초기화가 되기 때문에 name도 초기화가 됨
-> ReactMemo 컴포넌트가 렌더링 될때마다 계속해서 새로운 오브젝트가 만들어짐
-> 만들어진 object는 각각 다른 메모리 주소에 저장이 됨
-> name이라는 props에 변화가 있다고 받아들이기 ㅐ문에 Child 컴포넌트도 렌더링 됨
- 해결방법 :
메모리 주소가 변하지 않도록
useMemo Hook을 사용해서 object를 Memoization 해주자!
- 결론 : useMemo와 React.memo 함께 사용하면 props으로 전달받는 값이 객체여도 Child 컴포넌트의 렌더링을 막아줄 수 있음
ReactMemo.js
import React, { useState } from "react";
import Child from "../components/Child";
function ReactMemo() {
const [parentAge, setParentAge] = useState(0);
const incrementParentAge = () => {
setParentAge(parentAge + 1);
};
console.log("부모 컴포넌트가 렌더링 되었습니다");
const tellMe = () => {
console.log("길동아 사랑해 💕");
};
return (
<div style={{ border: "2px solid navy", padding: "10px" }}>
<h1>부모</h1>
<p>age: {parentAge}</p>
<button onClick={incrementParentAge}>부모 나이 증가</button>
<Child name={"홍길동"} tellMe={tellMe} />
</div>
);
}
export default ReactMemo;
Child.js
import React, { memo } from "react";
const Child = ({ name, tellMe }) => {
console.log("자녀도 렌더링 되었습니다");
return (
<div style={{ border: "2px solid pink", padding: "10px" }}>
<h3>자녀</h3>
<p>이름: {name}</p>
<button onClick={tellMe}>엄마 나 사랑해?</button>
</div>
);
};
export default memo(Child);
'엄마 나 사랑해?' 버튼 클릭시 콘솔에 '길동아 사랑해'가 출력됨
'부모 나이 증가' 클릭시 콘솔에 부모 컴포넌트와 자식 컴포넌트 모두 렌더링 되었다고 출력됨
Child가 받는 name, tellMe props가 업데이트 되지 않았고 Child는 React.memo로 최적화가 되어있음에도 불구하고 렌더링이 됨
이유는? -> tellMe라는 prop이 함수를 전달받고 있기 때문임
JavaScrip에서 함수는 객체의 한 종류임
tellMe라는 변수 안에는 함수 객체가 들어있는 메모리 주소가 할당 되기 때문에 컴포넌트가 렌더링 될때마다 Child 컴포넌트의 TellMe prop으로 계속해서 다른 주소가 전달 됨
React.memo 입장에서는 메모리 주소가 변경되었으니 Child 컴포넌트를 다시 렌더링 시킴
- 해결방법: useCallback Hook을 사용하자!
- 결론 : useCallback과 React.memo 함께 사용하면 Child prop으로 함수를 전달해줘도 Child 컴포넌트의 렌더링을 제한해줄 수 있음
- React.memo는 꼭 필요할때만 사용해야 함
- React.mmo는 오직 Pops 변화에만 의존하는 최적화 방법임