본 글은 벨로퍼트의 모던 리액트를 참고했습니다.
원문보기
컴포넌트의 props가 바뀌지 않았다면, 리렌더링을 방지해 컴포넌트의 리렌더링 성능 최적화를 해줄수 있는 React.memo라는 함수에 대해서 알아보자.
이 함수를 사용한다면, 컴포넌트에서 리렌더링이 필요한 상황에서만 리렌더링을 하도록 설정해줄 수 있다.
사용법은 굉장히 간단하다. 그냥 감싸주면 된다.
import React from 'react';
const CreateUser = ({ username, email, onChange, onCreate }) => {
return (
<div>
<input
name="username"
placeholder="계정명"
onChange={onChange}
value={username}
/>
<input
name="email"
placeholder="이메일"
onchange={onChange}
value={email}
/>
<button onClick={onCreate}>등록</button>
</div>
);
};
export default React.memo(CreateUser);
UserList와 User 컴포넌트도 적용할 수 있다.
import React from 'react';
const User = react.memo(function User({user, onRemove, onToggle}) {
return (
<div>
<b
style={{
cursor: 'pointer',
color: user.active ? 'green' : 'black'
}}
onClick{() => onToggle(user.id)
>
{user.username}
</b>
 
<span>({user.emial})</span>
<button onClick={() => onRemove(user.id)}>삭제</button>
</div>
);
});
function UserList({ users, onRemove, onToggle }) {
return (
<div>
{users.map(user => (
<User
user={user}
key={user.id}
onRemove={onRemove}
onToggle={onToggle}
/>
))}
</div>
);
}
export default React.memo(UserList);
적용을 다 하고 나서, input 을 수정 할 때 하단의 UserList 가 리렌더링이 되지 않는것을 확인할 수 있다. 그러나, User 중 하나라도 수정하면 모든 User들이 리렌더링되고, CreateUser도 리렌더링 되고 있다.
이유는 users 배열이 바뀔때마다 onCreate도 새로 만들어지고, onToggle, onRemove도 새로 만들어지기 때문이다.
const onCreate = useCallback(() => {
const user = {
id: nextId.current,
username,
email
};
setUsers(users.concat(user));
setInputs({
username: '',
email: ''
});
nextId.current += 1;
}, [users, username, email]);
const onRemove = useCallback(
id => {
// user.id 가 파라미터로 일치하지 않는 원소만 추출해서 새로운 배열을 만듬
// = user.id 가 id 인 것을 제거함
setUsers(users.filter(user => user.id !== id));
},
[users]
);
const onToggle = useCallback(
id => {
setUsers(
users.map(user =>
user.id === id ? { ...user, active: !user.active } : user
)
);
},
[users]
);
deps
에 users
가 들어있기 때문에 배열이 바뀔때마다 함수가 새로 만들어지는건 당연하다. 이것을 최적화하기 위해선 deps에서 users
를 지우고, 함수들에서 현재 useState
로 관리하는 users
를 참조하지 않게 하는 것이다.
함수형 업데이트를 하게 되면, setUsers
에 들고하는 콜백함수의 파라미터에서 최신 users
를 참조할 수 있기 때문에 deps
에 users
를 넣지 않아도 된다.
import React, { useRef, useState, useMemo, useCallback } from 'react';
import UserList from './UserList';
import CreateUser from './CrateUser';
function countActiveUsers(users) {
console.log('활성 사용자 수를 세는 중 ... ');
return users.filter(user => user.active).length;
}
function App() {
const [input, 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: 'velopert',
email: 'public.velopert@gmail.com',
active: true
},
{
id: 2,
username: 'tester',
email: 'tester@example.com',
active: false
},
{
id: 3,
username: 'liz',
email: 'liz@example.com',
active: false
}
]);
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 => {
// user.id 가 파라미터로 일치하지 않는 원소만 추출해서 새로운 배열을 만듬
// = user.id 가 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 = 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;
추가적으로, 렌더링 최적화 하지 않을 컴포넌트에 React.memo를 사용하는 것은 불필요한 props비교만 하는 것이기 때문에 실제로 렌더링을 방지할 수 있는 상황이 있는 경우에만 사용하는 것이 좋다.
추가적으로 React.memo에서 두번째 파라미터에 propsAreEqual
이라는 함수를 사용해 특정값들만 비교를 하는 것도 가능하다.
export default React.memo(
UserList,
(prevPrps, nextProps) => prevProps.users === nextProps.users
);
true를 반환하면 리랜더를 방지하고, false를 반환하면 리랜더를 실시한다.