함수형 컴포넌트(functional component)의 경우 Hooks를 통해서 다양한 작업을 할 수 있게 됩니다.
이 포스트에서 알아볼 Hook은 useMemo, useCallback입니다. 그리고 마지막으로 React.memo도 알아봅니다.
먼저, 컴포넌트가 리렌더링 되는 조건을 알아보면 다음과 같습니다.
- 부모 컴포넌트의 리렌더링
- Props의 변화
- State의 변화
- this.forceUpdate로 강제로 렌더링
다음과 같은 컴포넌트 코드가 있습니다.
아래 코드는 다음과 같이 진행됩니다.
1. input의 입력
2. state의 변경으로 인한 컴포넌트 리렌더링
3. onChange 함수의 선언 및 할당
즉 input이 입력될 때마다 리렌더링이 진행되고 그때마다 onChange의 함수를 다시 만들어줍니다.
const [form, setForm] = useState({ name: "", skill: "" });
const {name, skill} = form;
const onChange = (e) => {
const { name, value } = e.target;
setForm({ ...form, [name]: value });
};
return (
<>
<input name="name" value={name} onChange={onChange} />
<input name="skill" value={skill} onChange={onChange} />
</>
);
이 현상을 방지하기 위해 useCallback을 사용합니다.
기본 형태: useCallback(fn, deps)
useCallback의 첫 번째 파라미터에는 사용할 함수를 넣어줍니다.
두 번째 파라미터에는 첫 번째 파라미터에서 의존하는 변수를 넣어줍니다.
아래의 경우 첫 번째 파라미터로 넘겨주는 함수는 form변수에 의존적이기 때문에 두 번째 파라미터에 form 변수를 넣어줍니다.
만일, form 값이 바뀐다면 첫 번째 파라미터에 넘겨지는 함수가 다시 만들어 집니다.
지금 단계에서는 useCallback을 사용하는 것과 안 하는 것이 똑같지만 나중에 해당 컴포넌트가 form state와 관련 없이 리렌더링 될 때 함수를 다시 만들어주지 않을 것입니다.
const onChange = useCallback((e) => {
const { name, value } = e.target;
setForm({ ...form, [name]: value });
}, [form]);
여기서 한 번 더 최적화를 해줄 수 있는데
useState의 setter함수는 파라미터로 함수를 받기도 하는데 이것을 함수형 업데이트라고 합니다.
다음과 같이 사용하면 함수 안에서 의존성을 가지는 값이 없기 때문에 useCallback의 두 번째 파라미터에 빈 배열을 넘겨줄 수 있습니다.
이 경우 해당 컴포넌트가 생성되는 시점에만 onChange함수가 만들어집니다.
const onChange = useCallback((e) => {
const { name, value } = e.target;
setForm((prevForm) => ({ ...prevForm, [name]: value }));
}, []);
다음으로 useMemo를 알아봅니다.
useMemo(()=>fn, deps)와 같이 쓰입니다.
다음과 같이 컴포넌트 안에서 countOnlyReactUsers의 리턴 값을 활용하고 있습니다.
이 경우도 해당 컴포넌트가 리렌더링 될 때마다 함수가 실행됩니다.
근데 해당 함수는 users를 파라미터로 받게 됩니다. 즉 해당 값이 변하지 않는 한 실행이 될 필요 없는 함수가 됩니다.
const reactUser = countOnlyReactUsers(users);
따라서 다음과 같이 사용합니다.
의존적인 값(users)이 변하면 첫 번째 파라미터로 넘겨준 함수가 실행됩니다.
변하지 않으면 이전에 실행된 값을 넘겨줍니다.
const reactUser = useMemo(() => countOnlyReactUsers(users), [users]);
참고: ko.reactjs.org/docs/hooks-reference.html#usecallback
마지막으로 React.memo를 알아봅니다.
만일 React.memo 함수에 A라는 컴포넌트를 넣어주면 A 컴포넌트는 props가 바뀌어야 리렌더링이 실행됩니다.
즉 props에만 영향을 받습니다.
A 컴포넌트 안에서 useState 또는 useContext를 사용하면 state 또는 context가 변할 때 리렌더링됩니다.
아래 컴포넌트에서 li Element를 삭제할 때 일어나는 일은 다음과 같습니다.
1. li Element Click Event 발생
2. onRemove 실행되면서 setUsers 실행되어 state값 바뀜
3. UserList 컴포넌트 리렌더링
4. User 컴포넌트의 props가 바뀌지 않았으므로 리렌더링되지 않음
원래대로라면 부모 컴포넌트가 리렌더링 되면 자식 컴포넌트도 리렌더링 됩니다.
하지만, 자식 컴포넌트인 User 컴포넌트에 React.memo가 되어 있기 때문에 props의 변화로 리렌더링이 될지 안될지를 결정합니다.
만일 React.memo를 없애면 User 컴포넌트는 리렌더링 됩니다.
import React, { useState, useCallback } from "react";
export default function UserList() {
const [users, setUsers] = useState([
{ id: 1, name: "choi", skill: "react" },
{ id: 2, name: "Lee", skill: "swift" },
]);
const onRemove = useCallback((id) => {
setUsers((prevUsers) => prevUsers.filter((user) => user.id !== id));
}, []);
return (
<ul>
{users.map((user) => (
<User key={user.id} user={user} onRemove={onRemove} />
))}
</ul>
);
}
const User = React.memo(({ user, onRemove }) => {
console.log("render");
const { name, skill, id } = user;
return (
<li onClick={() => onRemove(id)}>
{name} / {skill}
</li>
);
});
참고: ko.reactjs.org/docs/react-api.html#reactmemo
지금까지 useCallback, useMemo, React.memo를 사용해봤습니다.
이것들은 메모이제이션을 사용한다는 걸 알아두시면 나중에 사용할 때 편할 것입니다.