useMemo는 바로 전 작성글에서 다룬 내용으로 Reack Hook의 한 종류이다.
컴포넌트내의 어떤 함수가 값을 리턴하는데 하나의 변화에도 값을 리턴하는데 많은 시간을 소요한다면 이 컴포넌트가 리렌더링 될 때마다 함수가 호출되면서 많은 시간을 소요하게 될 것이고, 그 함수가 return 되는 값이 자식 컴포넌트에도 props로 사용이 된다면, 그 자식 컴포넌트도 함수가 호출 될 때마다 새로운 값을 받아 리렌더링 된다.
const average = useMemo(() => {
console.log("calculate average. It takes long time !!");
return users.reduce((acc, cur) => {
return acc + cur.score / users.length;
}, 0);
}, [users]);
그럴때 사용 할 수 있는 것이 useMemo
사용법으로는
useMemo(() => {}, [deps])
useMemo는 종속 변수들이 변하지 않으면 함수를 굳이 다시 호출하지 않고 이전에 반환한 참조값을 재사용 한다.
즉, 함수 호출 시간도 세이브할 수 있고 같은 값을 props로 받는 하위 컴포넌트의 리렌더링도 방지할 수 있다.
useCallback도 앞서 전 작성글에서 다뤘던 내용으로 useMemo와 비슷하면서 다른 차이점은 useMemo는 리턴되는 값을 memorize, useCallback은 함수를 memorize 하는데 사용된다.
개발을 하다보면 props의 값으로 객체를 넘겨주는 경우가 많다. 이때 props로 전달하는 형태에 주의 하여야 한다.
// 생성자 함수
<Component prop={new Obj("x")} />
// 객체 리터럴
<Component prop={{property: "x"}} />
위와 같은 경우 생성된 객체가 props로 들어가므로 컴포넌트가 리렌더링 될 때마다 새로운 객체가 생성되어 자식 컴포넌트로 전달된다.
props로 전달한 객체가 동일한 값이어도 새로 생성된 객체는 이전 객체와 다른 참조 주소를 가진 객체이기 때문에 자식 컴포넌트는 메모제이션이 되지 않는다.
// UserList.jsx
function UserList() {
{...}
const getResult = useCallback((score) => {
if (score <= 70) {
return { grade: "D" };
} else if (score <= 80) {
return { grade: "C" };
} else if (score <= 90) {
return { grade: "B" };
} else {
return { grade: "A" };
}
}, []);
return(
<div>
{users.map((user) => {
return (
<Item key={user.id} user={user} result={getResult(user.score)} />
);
})}
</div>
)
export default memo(UserList);
// Item.jsx
function Item({ user, result }) {
console.log("Item component render");
return (
<div className="item">
<div>이름: {user.name}</div>
<div>나이: {user.age}</div>
<div>점수: {user.score}</div>
<div>등급: {result.grade}</div>
</div>
);
}
export default Item;
// UserList.jsx
function UserList() {
{...}
return(
<div>
{users.map((user) => {
return (
<Item key={user.id} user={user} />
);
})}
</div>
)
export default memo(UserList);
// Item.jsx
function Item({ user }) {
console.log("Item component render");
const getResult = useCallback((score) => {
if (score <= 70) {
return { grade: "D" };
}
if (score <= 80) {
return { grade: "C" };
}
if (score <= 90) {
return { grade: "B" };
} else {
return { grade: "A" };
}
}, []);
const { grade } = getResult(user.score);
return (
<div className="item">
<div>이름: {user.name}</div>
<div>나이: {user.age}</div>
<div>점수: {user.score}</div>
<div>등급: {grade}</div>
</div>
);
}
export default memo(Item);
아직 완벽히 이해가 되지 않는 부분으로 참조한 블로그의 예시코드를 가져왔다.
이 부분은 다시 다뤄볼 예정이다.
사람들이 많이 하는 실수 중에 하나가 바로 컴포넌트를 매핑할 때 key값에 index 값을 넣어준다.
리액트에서 매핍을 할떄 반드시 고유 key를 부여하도록 강제하고 있는데, 이렇게 index값으로 key값을 부여하면 좋지 않다.
왜냐하면, 어떤 배열에 중간에 어떤 요소가 삽입될때 그 중간 이후에 위치한 요소들은 전부 index가 변경된다.
ex)
{items.map((el,index) => {
<Component key={index}
}
이런 상황에선 items라는 배열에 값이 추가되거나 수정이될때마다 index가 변경이되어 이로 인해 key값이 변경되어 React는 key가 동일 할 경우, 동일한 DOM Element를 보여주기 때문에 예상치 못한 문제가 발생합니다. 또한, 데이터가 key와 매치가 안되어 서로 꼬이는 부작용도 발생한다.
배열의 요소 가 필터링, 삭제, 추가 등이 들어간다면 문제가 생길 수 있으나 다음과 같은 경우에서는 index로 사용해도 무방하다.(그래도 사용안하는걸 권장)
기존 useState를 사용하며, 대부분 setState시에 새로운 상태를 파라미터로 넣어주었다. setState를 사용할 때 새로운 상태를 파라미터로 넣는대신에 상대 업데이트를 어떻게 할지 정의해주는 함수를 넣을수도 있다.
이렇게 하면 useCallback을 사용할 때 두번째 파라미터로 넣는 배열에 값을 넣어주지 않아도 된다.
// 예시) 함수형 업데이트
const onRemove = useCallback(id => {
setTodos(todos => todos.filter(todo => todo.id !== id));
}, []);
다른 최적화 방법도 여러가지가 더 있겠지만 일단은 내가 이해할 수 있는 내용을 정리하여봤다.