React.memo

gyomni·2022년 2월 25일
0

React

목록 보기
6/9
post-thumbnail

컴포넌트 리렌더링 방지

  • 컴포넌트에서 리렌더링이 불필요할 때 이전에 렌더링 했던 결과를 재사용 할 수 있게 하는 방법 ?
    -> 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>
            &nbsp;
            <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>
            &nbsp;
            <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>
            &nbsp;
            <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>
            &nbsp;
            <span>({email})</span>
            <button onClick={()=>onRemove(id)}>삭제</button>
        </div>
    );
})


이런 상황을 해결하기 위해서는 ?-?

  • onRemove, onToggle, onCreate 함수들이 기존 users를 참조하면 안된다.
  • 대신, useState의 함수형 업데이트를 하면 된다!
  • 함수형 업데이트를 하게 되면, Deps에 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, email이 바뀔 때만 새로 만들어 진다.


📍 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 는 컴포넌트의 성능을 실제로 개선할수있는 상황에서만 사용하기! 💫

학습 : 벨로퍼트와 함께 하는 모던 리엑트

profile
Front-end developer 👩‍💻✍

0개의 댓글