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.js
export default React.memo(CreateUser);
컴포넌트를 내보낼 때
React.memo( )
로 감싸주면 된다.
-> 이제props
가 바뀌었을 때만 리렌더링 해준다.
📍
UserList.js
export 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.js
return ( <> <CreateUser username={username} email={email} onChange={onChange} onCreate={onCreate} /> <UserList users={users} onRemove={onRemove} onToggle={onToggle}/> <div>활성 사용자 수 : {count}</div> </> )
UserList.js
return( <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
-onCreate
const 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
-onRemove
const onRemove = useCallback(id =>{ setUsers(users=>users.filter(user=>user.id !==id)); }, []);
onRemove
함수는 컴포넌트가 처음렌더링 될 때 한번만 만들어지고 그 다음부터는 재사용된다.
📍
App.js
-onToggle
const 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
는 컴포넌트의 성능을 실제로 개선할수있는 상황에서만 사용하기! 💫
학습 : 벨로퍼트와 함께 하는 모던 리엑트