React 랜더링 최적화 7가지 방법 (Hooks)

김형석·2022년 6월 28일
0

컴포넌트의 리렌더링 조건

  • 부모에게서 전달받은 props가 변경될 때
  • 부모 컴포넌트가 리렌더링 될 때
  • 자신의 state가 변경될 때

1. useMemo

useMemo 공식문서

어떠한 값에 변화가 있을때만 새로운 값을 할당해주고 아닐경우 memoized된 값 (이전 값) 을 불러오는 Hooks
성능 최적화하는데 쓰인다

const average = useMemo(()=>{
	return userList.reduce((acc,cur)=>{ return acc + cur.score /userList.length 	},0 )
},[userList]);

2. React.memo 컴포넌트 메모이제이션

React.memo 공식문서

함수형 컴포넌트에서는 shouldComponentUpdate를 사용 할 수 없는데, 리액트 공식 문서에서는 그 대안으로 React.memo를 제시하고 있다.

단, 공식문서를 인용하면 아래와 같은 주의점이 있으니 잘 알아보고 사용하는 것이 좋겠다.

React.memo는 props 변화에만 영향을 줍니다. React.memo로 감싸진 함수 컴포넌트 구현에 useState, useReducer 또는 useContext 훅을 사용한다면, 여전히 state나 context가 변할 때 다시 렌더링됩니다.

3.useCallback

useCallback 공식문서

useMemo와 비슷하다, useMemo는 리턴되는 값을 memoize했다면 useCallback은 함수 선언을 memoize 하는데 사용한다.

4.자식 컴포넌트의 props로 객체를 넘겨줄 경우 변형하지말고 넘겨주기

// 생성자 함수
<Component prop={new Obj("x")} />
// 객체 리터럴
<Component prop={{property: "x"}} />

이런 경우 새로 생성된 객체가 props로 들어가므로 컴포넌트가 리렌더링 될 때마다 새로운 객체가 생성되어 자식 컴포넌트로 전달된다.
props로 전달한 객체가 동일한 값이어도 새로 생성된 객체는 이전 객체와 다른 참조 주소를 가진 객체이기 때문에 자식 컴포넌트는 메모이제이션이 되지않는다.

🙅‍♂️ 나쁜 예시

const getResult = useCallback((score) => {
    if (score <= 70) {
      return { grade: "D" };
    } else if (score <= 80) {
      return { grade: "C" };
    } else if (score <= 90) {
      return { grade: "B" };
    } else {
      return { grade: "A" };
    }
  }, []);

return(
  <div>
  	{users.map((user) => {
    return (
      <Item key={user.id} user={user} result={getResult(user.score)} />
        );
      })}  
  </div>
)

export default memo(UserList);


// Item.jsx  
function Item({ user, result }) {
  console.log("Item component render");

  return (
    <div className="item">
      <div>이름: {user.name}</div>
      <div>나이: {user.age}</div>
      <div>점수: {user.score}</div>
      <div>등급: {result.grade}</div>
    </div>
  );
}

export default Item;

🙆‍♂️ 올바른 예

// UserList.jsx  
function UserList() {
{...}

return(
 <div>
 {users.map((user) => {
    return (
      <Item key={user.id} user={user} />
        );
      })}
 </div> 
  
)
export default memo(UserList);



// Item.jsx  
function Item({ user }) {
  console.log("Item component render");

  const getResult = useCallback((score) => {
    if (score <= 70) {
      return { grade: "D" };
    }
    if (score <= 80) {
      return { grade: "C" };
    }
    if (score <= 90) {
      return { grade: "B" };
    } else {
      return { grade: "A" };
    }
  }, []);

  const { grade } = getResult(user.score);

  return (
    <div className="item">
      <div>이름: {user.name}</div>
      <div>나이: {user.age}</div>
      <div>점수: {user.score}</div>
      <div>등급: {grade}</div>
    </div>
  );
}

export default memo(Item);

5.컴포넌트 매핑시 key값으로 index를 사용하지 말 것

key값으로는 unique한 값이 들어와야하는데 index의 경우에는 중간에 다른 요소가 삽입되거나 할 경우
모든 index가 바뀌게 되고 그로 인해 key값이 변경되어 동일한 DOM Element를 보여주게 되어 예상치 못한 문제가 발생하게 된다
또한, 데이터가 key와 매치가 안되어 서로 꼬이는 부작용도 발생한다.

6.useState의 함수형 업데이트

// 예시) 삭제 함수 
const onRemove = useCallback(
  id => {
    setTodos(todos.filter(todo => todo.id !== id));
  },
  [todos],
);

// 예시) 함수형 업데이트 후
const onRemove = useCallback(id => {
  setTodos(todos => todos.filter(todo => todo.id !== id));
}, []);

7.input onChange 최적화

// 예시) 최적화 전(X)
//UserList.jsx
function UserList() {
 {...}
  return (
      <div>
       <input
         type="text"
         value={text}
         placeholder="아무 내용이나 입력하세요."
         onChange={(event) => setText(event.target.value)}
        />
   {...}
      </div>
  );
}

export default UserList;


// 예시) 최적화 후(O)
//UserList.jsx
function UserList() {
 {...}
  return (
      <div>
       <input
          ref={searchRef}
          type="text"
          placeholder="아무 내용이나 입력하세요."
          onKeyUp={() => {
            let searchQuery = searchRef.current.value.toLowerCase();
            setTimeout(() => {
              if (searchQuery === searchRef.current.value.toLowerCase()) {
                setText(searchQuery);
              }
            }, 400);
          }}
        />
   {...}
      </div>
  );
}

export default UserList;

🌙 마치며

리액트를 좀 더 dep하게 파볼수록 알아야하고 아는게 더 많이 늘어가겠지만.

리액트는 단방향 하향식 데이터 흐름을 가지고 있어서, 부모 컴포넌트에서 자식 컴포넌트 방향으로 데이터(props,state)가 흘러간다.

이 데이터들의 변화는 컴포넌트를 리렌더링 시키는데, state는 그것이 선언된 컴포넌트 내에서 사용되고,
props는 부모 컴포넌트로부터 받은 데이터이다.

이 기본 구조를 숙지하면 최적화를 어떻게 해야할지 쉽게 알 수 있다.

profile
코드로 소통하기 위해 힘쓰는 프론트엔드 개발자 입니다.

0개의 댓글