React.memo 함수 사용하면 컴포넌트의 props 가 바뀌지 않았을 때 컴포넌트의 리렌더링 성능 최적화를 해줄 수 있다.CreateUser.js
function 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.js
import React, { useEffect } from "react";
const User = React.memo(function User({user, onRemove,onToggle}){
const {username, email, id, active} =user;
useEffect(()=>{
console.log('user값이 설정됨');
console.log(user);
return()=>{
console.log('user 값이 바뀌기전');
console.log(user);
}
},[user]);
return(
<div>
<b
style={{
color: active ?'green':'black',
cursor:'pointer',
}}
onClick={()=>onToggle(id)}
>
{username}
</b>
<span>({email})</span>
<button onClick={()=>onRemove(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);
📍
CreateUser.jsexport default React.memo(CreateUser);컴포넌트를 내보낼 때
React.memo( )로 감싸주면 된다.
-> 이제props가 바뀌었을 때만 리렌더링 해준다.
📍
UserList.jsexport default React.memo(UserList);
user컴포넌트는 함수 전체를React.memo( )로 감싸준다.const User = React.memo(function User({user, onRemove,onToggle}){ const {username, email, id, active} =user; useEffect(()=>{ console.log('user값이 설정됨'); console.log(user); return()=>{ console.log('user 값이 바뀌기전'); console.log(user); } },[user]); return( <div> <b style={{ color: active ?'green':'black', cursor:'pointer', }} onClick={()=>onToggle(id)} {username} </b> <span>({email})</span> <button onClick={()=>onRemove(id)}>삭제</button> </div> ); });
📍
React.memo적용 전
input을 바꿀 때 하단의 컴포넌트들도 리렌더링 된다.
📍
React.memo적용 후
input을 바꿀 때 아래는 영향을 받지 않는다.
input작성 후 , 등록버튼을 누르면 모든 컴포넌트들이 리렌더링 된다.
- 계정명을 클릭(활성 사용자)하면 모두 리렌더링 된다.
일단, 계정명을 클릭할 때, 클릭하지 않은 계정명들이 리렌더링 되는 과정을 보자면!
1)
App.js에서<UserList/>컴포넌트에는onRemove,onToggle을 전달해 주고 있다.return ( <> <CreateUser username={username} email={email} onChange={onChange} onCreate={onCreate} /> <UserList users={users} onRemove={onRemove} onToggle={onToggle}/> <div>활성 사용자 수 : {count}</div> </> )
2) 그리고
UserList.js에서는User컴포넌트,UserList컴포넌트에React.memo를 넣어줘서 만약에props가 바뀌지 않았더라면 리렌더링을 방지하도록 설정을 했다.
User컴포넌트const User = React.memo(function User({user, onRemove,onToggle}){ const {username, email, id, active} =user; useEffect(()=>{ console.log('user값이 설정됨'); console.log(user); return()=>{ console.log('user 값이 바뀌기전'); console.log(user); } },[user]); return( <div> <b style={{ color: active ?'green':'black', cursor:'pointer', }} onClick={()=>onToggle(id)} {username} </b> <span>({email})</span> <button onClick={()=>onRemove(id)}>삭제</button> </div> ); })
UserList컴포넌트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);
3) 그런데,
App.js에서onRemove,onToggle를 보면,users가[ ]에 있다.const onRemove = useCallback(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])->
users배열이 바뀌면onRemove,onToggle도 새로 바뀐다는 뜻.
4)
<userList/>입장에서는onRemove,onToggle바뀌니까UserList컴포넌트 내부에 있는 것 다 리렌더링 해야한다.
App.jsreturn ( <> <CreateUser username={username} email={email} onChange={onChange} onCreate={onCreate} /> <UserList users={users} onRemove={onRemove} onToggle={onToggle}/> <div>활성 사용자 수 : {count}</div> </> )
UserList.jsreturn( <div> { users.map( (user)=>( <User user={user} key ={user.id} onRemove={onRemove} onToggle={onToggle} />) ) } </div> );
5)
User컴포넌트 입장에서도onRemove,onToggle바꼈으니까 리렌더링을 해야한다.
User컴포넌트const User = React.memo(function User({user, onRemove,onToggle}){ const {username, email, id, active} =user; useEffect(()=>{ console.log('user값이 설정됨'); console.log(user); return()=>{ console.log('user 값이 바뀌기전'); console.log(user); } },[user]) return( <div> <b style={{ color: active ?'green':'black', cursor:'pointer', }} onClick={()=>onToggle(id)} {username} </b> <span>({email})</span> <button onClick={()=>onRemove(id)}>삭제</button> </div> ); })
onRemove, onToggle, onCreate 함수들이 기존 users를 참조하면 안된다. users를 안넣어도 된다.📍
App.js-onCreateconst onCreate = useCallback(()=>{ const user={ id:nextId.current, username, email, }; setUsers(users=>[...users, user]); setInputs({ username:'', email:'' }); nextId.current+=1; },[username,email]);
Deps에
users을 지우고setUsers(users=>[...users, user]);해줌.
setUsers에 등록한 콜백함수 (users=>[...users, user])의 파라미터에서 최신users를 조회하기 때문에, 굳이 Deps에users를 넣지 않아도 됨.결국,
onCreate함수는username,
📍
App.js-onRemoveconst onRemove = useCallback(id =>{ setUsers(users=>users.filter(user=>user.id !==id)); }, []);
onRemove함수는 컴포넌트가 처음렌더링 될 때 한번만 만들어지고 그 다음부터는 재사용된다.
📍
App.js-onToggleconst onToggle = useCallback(id =>{ setUsers(users=>users.map( user=>user.id ===id ?{ ...user, active: !user.active} : user )); }, [])
onToggle함수도 컴포넌트가 처음 만들어 질 때 한번만 만들어지고 그 이후로는 재사용된다.
성능 최적화가 잘 되었다!

현재 위의 gif에서 CreateUser컴포넌트가 계속 리렌더링 되는것 처럼 보이는데,
CreateUser컴포넌트에 console.log('CreateUser');를 작성하여 확인해보면 CreateUser컴포넌트 이외의 것이 바뀔때는 콘솔창에 CreateUser이 작성되지 않는다!
즉, 리렌더링 되지 않는 것을 확인할 수 있다!
React.memo 사용시 두번째 파라미터 PropsAreEqual prevProps, nextProps( 전, 후 Props )를 가져와서 비교를 해줌.T를 반환하면 리렌더링을 방지. F를 반환하면 리렌더링하게함.export default React.memo(
UserList,
(prevProps, nextProps) => prevProps.users === nextProps.users
);
PropsAreEqual 를 넣어주는 경우에는 나머지 Props가 정말로 고정적이여서 비교를 할 필요가 없는지 꼭 확인해 줘야 한다. 심각한 오류 발생할 수 있다!
💫 리액트 개발 시에 useCallback, useMemo, React.memo 는 컴포넌트의 성능을 실제로 개선할수있는 상황에서만 사용하기! 💫
학습 : 벨로퍼트와 함께 하는 모던 리엑트