컴포넌트의 props 가 바뀌지 않았다면, 리렌더링을 방지하여 컴포넌트의 리렌더링 성능 최적화를 해줄 수 있는 React.memo 라는 함수에 대해서 알아보겠습니다.
CreateUser.js
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);
JavaScript
복사
[ deps에 users가 들어있으면 리렌더링이 된다. 이를 해결하기위해선? ]
바로 deps 에서 users 를 지우고, 함수들에서 현재 useState 로 관리하는 users 를 참조하지 않게 하는것입니다. 그건 또 어떻게 할까요?
정답은 바로, 함수형 업데이트입니다.
함수형 업데이트를 하게 되면, setUsers 에 등록하는 콜백함수의 파라미터에서 최신 users 를 참조 할 수 있기 때문에 deps 에 users 를 넣지 않아도 된답니다.
App.js
import React, {useRef, useState, useMemo, useCallback}from 'react';
// import React from 'react';
import './App.css'
// import InputSample2 from './InputSample2';
import UserList from './UserList';
import CreateUser from './CreateUser';
//useMemo를 사용하여 연산한 값 재사용
//App 컴포넌트에서 다음과 같이 countActiveUsers 라는 함수를 만들어서, active 값이 true 인 사용자의 수를 세어서 화면에 렌더링을 해보세요.
function countActiveUsers(users) {
console.log('활성 사용자 수를 세는중 ...');
return users.filter(user => user.active).length;
}
function App() {
const [Inputs, setInputs] = useState({
username : '',
email : '',
id : ''
});
const {username, email, id} = 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
},
]);
//다음값 id지정
const nextId = useRef(4);
//새롭게 등록할 때
const onCreate = useCallback(() => {
const user = {
id : nextId.current,
username : username,
email : email
}
setUsers([...users, user]);
setInputs({
username : '',
email : ''
})
nextId.current += 1;
},[users, username, email]);
//삭제하기위해서는 각 id를 알아야한다
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 onUpdate = useCallback(() => {
setUsers (
users.map(user => user.id === id ? {...user, username: username, email : email} : user)
);
setInputs({
username : '',
email : '',
id : ''
})
},[users,username,email,id])
// 활성 사용자 수를 세는건, users 에 변화가 있을때만 세야되는건데,
//input 값이 바뀔 때에도 컴포넌트가 리렌더링 되므로 이렇게 불필요할때에도 호출하여서 자원이 낭비되고 있습니다.
// 이러한 상황에는 useMemo 라는 Hook 함수를 사용하면 성능을 최적화 할 수 있습니다.
// Memo 는 "memoized" 를 의미하는데, 이는, 이전에 계산 한 값을 재사용한다는 의미를 가지고 있습니다.
// 한번 사용해볼까요?
const count = useMemo(() => countActiveUsers(users),[users]);
const onModify = (user) => {
setInputs({
username : user.username,
email : user.email,
id : user.id
})
};
return (
<>
<CreateUser
username = {username}
email = {email}
onChange = {onChange}
onCreate = {onCreate}
onUpdate = {onUpdate}
/>
<UserList users={users} onRemove={onRemove} onToggle={onToggle} onModify={onModify}/>
<div>활성 사용자 수 : {count}</div>
</>
);
// return (
// <InputSample2 />
// )
}
export default App;
JavaScript
복사
이렇게 해주면, 특정 항목을 수정하게 될 때, 해당 항목만 리렌더링 될거예요.
리액트 개발자 도구의 버그인지, CreateUser 도 렌더링 되는것처럼 보이는데 실제로 console.log 찍어보시면 렌더링이 안되고 있는 것을 확인 할 수 있습니다.
그럼 최적화가 끝난겁니다!
리액트 개발을 하실 때, useCallback, useMemo, React.memo 는 컴포넌트의 성능을 실제로 개선할수있는 상황에서만 하세요.
예를 들어서, User 컴포넌트에 b 와 button 에 onClick 으로 설정해준 함수들은, 해당 함수들을 useCallback 으로 재사용한다고 해서 리렌더링을 막을 수 있는것은 아니므로, 굳이 그렇게 할 필요 없습니다.
추가적으로, 렌더링 최적화 하지 않을 컴포넌트에 React.memo 를 사용하는것은, 불필요한 props 비교만 하는 것이기 때문에 실제로 렌더링을 방지할수있는 상황이 있는 경우에만 사용하시길바랍니다.
추가적으로, React.memo 에서 두번째 파라미터에 propsAreEqual 이라는 함수를 사용하여 특정 값들만 비교를 하는 것도 가능합니다.
export default React.memo(
UserList,
(prevProps, nextProps) => prevProps.users === nextProps.users
);
Plain Text
복사
하지만, 이걸 잘못사용한다면 오히려 의도치 않은 버그들이 발생하기 쉽습니다. 예를 들어서, 함수형 업데이트로 전환을 안했는데 이렇게 users 만 비교를 하게 된다면, onToggle 과 onRemove 에서 최신 users 배열을 참조하지 않으므로 심각한 오류가 발생 할 수 있습니다.