반응형
250x250
Notice
Recent Posts
Recent Comments
Link
관리 메뉴

짧은코딩

최적화 하기 본문

인프런, 유데미/한입 크기로 잘라 먹는 리액트

최적화 하기

5_hyun 2022. 6. 27. 22:59

웹 최적화 방법

1. 코드들을 하나하나 보면서 찾는 방법, 정적
2. 프로젝트를 키고 프로그램에 의해 동적으로 분석한다.

 

=> 우선은 정적 분석을 할 만큼 실력이 뛰어나지 않기 때문에 정적으로 분석한다.

 

그렇기에 개발자 도구의 Components의 설정에서 Highlight updates when components render를 체크하고 분석하면 된다.

 

최적화 하기

1. 달력을 바꿨을 때, 버튼들은 리렌더 될 필요가 없다.

-Home.js

  // 오른쪽 버튼 누르면 1달씩 증가
  const increaseMonth = () => {
    setCurDate(
      new Date(curDate.getFullYear(), curDate.getMonth() + 1, curDate.getDate())
    );
  };
  // 왼쪽 버튼 누르면 1달씩 감소
  const decreaseMonth = () => {
    setCurDate(
      new Date(curDate.getFullYear(), curDate.getMonth() - 1, curDate.getDate())
    );
  };

  return (
    <div>
      <MyHeader
        headText={headText}
        leftchild={<MyButton text={"<"} onClick={decreaseMonth} />}
        rightchild={<MyButton text={">"} onClick={increaseMonth} />}
      />
      <DiaryList diaryList={data} />
    </div>
  );
};

Home.js에서 달력을 이동 시키는 버튼을 누르면 Home 컴포넌트의 state가 바뀌게 되어 리렌더링 되게 된다. 그리고 DiaryList.js에 버튼이 있어서 Home.js가 리렌더되면 DiaryList.js도 리렌더된다.

 

-DiaryList.js

그리고 DiaryList.js 안에 정렬해주는 기능인 ControlMenu 컴포넌트도 Home.js가 렌더링되면 리렌더된다.

=> 따라서 문제는 ControlMenu가 다시 렌더링되고 있는 것이 문제이다.

 

해결법

-DiaryList.js

const ControlMenu = React.memo(({ value, onChange, optionList }) => {
  return (
    <select
      className="ControlMenu"
      value={value}
      onChange={(e) => onChange(e.target.value)}
    >
      {optionList.map((it, idx) => (
        <option key={idx} value={it.value}>
          {it.name}
        </option>
      ))}
    </select>
  );
});

따라서 ControlMenu를 React.memo로 감싸줬다. React.memo에 인자를 전달 하면 강화된 컴포넌트를 돌려주는 고차 컴포넌트이다. 고차 컴포넌트는 인자를 받아서 강화된 컴포넌트를 돌려주는 것이다. 그리고 ControlMenu 안에 useEffect를 이용해서 콘솔을 찍어보면 잘 해결되었는지 알 수 있다.

근데 ControlMenu의 onChange은 useCallback 같은 기능으로 함수를 재사용하게 만들지 않으면 부모 컴포넌트가 리렌더링 되면서 변경이 되어 결론적으로 리엑트 memo가 정상적으로 동작하지 않는다고 배웠다. 하지만 지금은 onChange 함수가 별도의 useCallback를 하지 않고도 리렌더링이 발생하지 않는다.

 // 정렬 기준을 정할 state이고 초기값은 최신으로 할거라서 lastest
  const [sortType, setSortType] = useState("latest");
  // 감정 숫자 기준으로 출력
  const [filter, setFilter] = useState("all");

onChange 함수는 state가 반환하는 상태 변환 함수를 prop로 받는다. state는 렌더링이 일어나도 동일한 id를 보장 받아서 렌더링이 안된다. 따라서 이런 것은 기본적으로 useCallback되어 나온다고 보면 된다.

 

하지만 예전에 배운 함수는 위 사진처럼 state 함수가 아니였다. 이런식으로 중간에 핸들링 할 함수를 만들고 지금 이 함수를 prop로 전달하면 날짜를 옮길 때마다 렌더링이 일어난다. 왜냐하면 이런 함수들은 컴포넌트가 리렌더링 될 때 다시 생성이 된다. 즉, 리액트 memo가 비교했을 때 다른 prop라고 비교하게 된다.

 

  • 결론

굳이 중간에 핸들러 함수를 만들 수 없는 구간에서는 useCallback를 사용하지 말고 상태 변화 함수 그 자체를 내려주면 좀 더 편하게 사용 가능하다.


2. 최신순/오래된 순 + 좋은 감정/안좋은 감정 바꿀 때, 일기 내용의 위치만 바꿨으면 되는데 다 리렌더링이 일어난다.

DiaryItem.js는 DiaryList.js의 자식이다. DiaryList.js에서 필터 값을 변경해주게 되면 DiaryList.js가 관리하는 state의 값이 바뀌게 되어서 업데이트가 발생한다. 그렇기 때문에 자식인 DiaryItem.js 컴포넌트들도 리렌더된다. 이런 이미지, 동영상 같은 크기가 큰 아이템들이 리렌더링 되면 엄청 위험하다. 따라서 DiaryItem에 memo를 적용해야한다.

 

-DiaryItem.js

export default React.memo(DiaryItem);

이렇게 마지막만 memo로 감싸도 된다. 그러고 필터를 바꾸면 아이템 자체의 렌더링은 일어나지 않는다.


상세 페이지에는 상태를 변화시키는 액션이 없기 때문에 수정할게 없다.


3. 수정 페이지에서 내용을 입력하면 감정 이미지도 렌더링이 일어난다.

-DiaryEditor.js

 // 오늘의 일기 state
  const [content, setContent] = useState("");

수정하기 페이지에서 글을 수정하면 content state가 계속 변화하게 된다.

 

  {/* 오늘의 감정 */}
        <section>
          <h4>오늘의 감정</h4>
          <div className="input_box emotion_list_wrapper">
            {emotionList.map((it) => (
              <EmotionItem
                key={it.emotion_id}
                {...it}
                onClick={handleClickEmote}
                // 자신이 선택된 감정인지 아닌지 알기 위한 prop
                // 선택된 emotion의 값과 같다면 true 전달
                isSelected={it.emotion_id === emotion}
              />
            ))}
          </div>
        </section>

위에서 content state가 계속 변화하게 되면 자식 요소인 EmotionItem도 변화하게 된다. 그렇기에 EmotionItem.js로 이동해서 재생성되지 않도록 해줘야 한다.

 

-EmotionItem.js

import React from "react";

const EmotionItem = ({
  emotion_id,
  emotion_img,
  emotion_descript,
  onClick,
  isSelected,
}) => {
  return (
    <div
      onClick={() => onClick(emotion_id)}
      className={[
        "EmotionItem",
        isSelected ? `EmotionItem_on_${emotion_id}` : `EmotionItem_off`,
      ].join(" ")}
    >
      <img src={emotion_img} />
      <span>{emotion_descript}</span>
    </div>
  );
};

export default React.memo(EmotionItem);

이렇게 memo를 적용해도 렌더링이 계속 일어난다. 이유는 1번에서 핸들링 예시로 들은 onClick가 있기 때문이다. 따라서 DiaryEditor 컴포넌트로 가서 onClick 함수 마저도 useCallback으로 재사용할 수 있도록 한다.

 

-DiaryEditor.js

  //감정 클릭시 발생하는 기능
  const handleClickEmote = useCallback((emotion) => {
    setEmotion(emotion);
  }, []);

이렇게 useCallback으로 감싸주면 감정 이미지들도 렌더링이 일어나지 않는다.

728x90
반응형

'인프런, 유데미 > 한입 크기로 잘라 먹는 리액트' 카테고리의 다른 글

Firebase로 프로젝트 배포하기  (1) 2022.06.28
배포 준비  (0) 2022.06.27
LocalStorage를 일기 DB로 사용하기  (0) 2022.06.24
흔한 버그 수정하기  (0) 2022.06.24
DAIRY 구현  (0) 2022.06.23
Comments