useMemo
개념
- 반복적으로 연산하는 값의 결과를 저장해두고 재사용하게 도와주는 Hook
- 해당 연산과 관련된 값(의존성 배열에 넣은 값)이 변경될 때만 연산되며, 이외는 이미 연산된 결과를 재사용
user의 변화에 따라 활성 사용자 수를 세야하는데 input 값이 바뀔 때에도 컴포넌트가 리렌더링이 되면서
불필요한 호출이 발생(리소스 낭비)
여기서 Input 값의 state는 App에서 관리하고 있고, 해당 App에서 활성 사용자 수를 세는 연산도 있기 때문에
리렌더링 발생 때마다, 계속 연산되고 있는 상황
이러한 상황에서 useMemo를 사용하여 연산한 결과를 저장해두고, 저장해둔 값을 사용하여 리소스 낭비를 막아줌
dp의 메모이제이션과 동일
사용
// 적용 전
// const count = countActiveUsers(users);
// useMemo 적용 후
const count = useMemo(() => countActiveUsers(users), [users]);
useMemo의 1번째 인자에는 메모할 함수(연산) 2번째 인자에는 의존성 배열을 둠
이를 통해서, users 변화 이외에의 리렌더링이 발생했을 때 해당 연산이 반복되지 않음
useCallback
개념
- 반복 사용하는 특정 함수를 저장해두고 재사용하게 도와주는 Hook
- 일반적으로 컴포넌트가 리렌더링 될 때마다 컴포넌트 내에 기술한 함수들은 새로 만들어짐
- 나중에 컴포넌트에서 props가 바뀌지 않았으면 Virtual DOM에서 새로 렌더링 하는 것 조차 하지 않고,
이전에 생성했던 함수를 재사용하여 최적화 작업을 진행하기 위해!
- useCallback 내에서 사용하는 상태 or props(변수 or 함수)가 있을 경우, 의존성 배열 내에 필수적으로 포함해야 함(해당 값들을 참조할 때 최신 상태로 동기화 하기 위해)
- useCallback은 useMemo를 기반으로 만들어진 Hook
React.memo
- 컴포넌트의 리렌더링을 방지하여 성능 최적화를 해주는 기능
- props의 변화가 없는데, 상위 컴포넌트 업데이트로 인해 리렌더링이 발생하는 것을 방지해줌
// 해당 컴포넌트를 React.memo()로 감싸주면 된다.
export default React.memo(CreateUser);
React.memo를 하여 일부 컴포넌트로 인해 리렌더링이 되는 것을 막았지만,
UserList 내의 User 컴포넌트 중 하나가 변할때마다, UserList에 있는 User 컴포넌트가 전부 리렌더링이 되는 현상이 발생했음
이유는 users 배열이 바뀔마다 onToggle 등의 관련 함수가 새로 만들어지기 때문이었음
(onToggle 함수의 경우 useCallback을 사용했고 의존성 배열에 users를 넣어논 상황!)
의존성 배열에서 users를 제거해주고 리렌더링을 방지하기 위해서는 함수형 업데이트를 해야 함
함수형 업데이트
import { useRef, useState, useMemo, useCallback } from "react";
import UserList from "./UserList";
import CreateUser from "./CreateUser";
function countActiveUsers(users) {
console.log("활성 사용자 수 세는중");
return users.filter((user) => user.active).length;
}
function App() {
const [inputs, setInputs] = useState({
username: "",
email: "",
});
const { username, email } = inputs;
const onChange = useCallback((e) => {
const { name, value } = e.target;
setInputs((inputs) => ({
...inputs,
[name]: value,
}));
}, []);
const [users, setUsers] = useState([
{
id: 1,
username: "kim",
email: "kimkirin@naver.com",
isActive: true,
},
{
id: 2,
username: "aaa",
email: "aaa@naver.com",
isActive: true,
},
{
id: 3,
username: "bbb",
email: "bbb@google.com",
isActive: true,
},
]);
const nextId = useRef(4);
const onCreate = useCallback(() => {
const user = {
id: nextId.current,
username,
email,
};
setUsers((users) => users.concat(user));
setInputs({
username: "",
email: "",
});
nextId.current += 1;
}, [username, email]);
const onRemove = useCallback((id) => {
setUsers((users) => users.filter((user) => user.id !== id));
}, []);
const onToggle = useCallback((id) => {
setUsers((users) =>
users.map((user) =>
user.id === id ? { ...user, active: !user.active } : user
)
);
}, []);
// 적용 전
// const count = countActiveUsers(users);
// useMemo 적용 후
const count = useMemo(() => countActiveUsers(users), [users]);
return (
<>
<CreateUser
username={username}
email={email}
onChange={onChange}
onCreate={onCreate}
/>
<UserList users={users} onRemove={onRemove} onToggle={onToggle} />
<div>활성사용자 수 : {count}</div>
</>
);
}
export default App;
참고
벨로퍼트님의 모던 리액트