✅ 성능 최적화 전에 알면 좋은 것


❓ 브라우저 주소창

  • 결국은 api get을 이용한다.

  • 비교

    • 브라우져
      ⇒ 가지고온 데이터(HTML)를 그림으로 바꿔주는 것이다.

    • 포스트맨
      ⇒ 요청 했을 때 데이터만 받아오는 것이다.

    • axios
      ⇒ 프로그램 상에서 데이터를 요청하고 받아올 때 사용하는 것이다.

    • curl
      => 터미널 cmd의 curl로 주소를 요청할 수있음.

❓ 브라우저의 정보를 가져오는 방법

  • 방식의 차이
    • 스크래핑 브라우저의 정보를 1번 가져오기
      ⇒ Cheerio

    • 크롤링 브라우저의 정보를 계속 가져오기
      ⇒ Puppeteer

🤔 고려사항

  • cors(cross-origin-resource-sharing)
    • sop(same-origin-polic) 정책으로 인해서 생기는 에러이다.

    • proxy server 만들면 접근이 가능해서 백엔드에서 작업을 많이 한다.

    • 프론트엔드 서버에서도 webpack의 설정을 건드리면 프록시 역할을 하게 할 수 있다.

❓ 오픈그래프 (og)

  • og값의 세팅

    • SEO 최적화를 위한 방법 중 하나이다.
      => 검색엔진 최적화, 즉 검색엔진에서 찾기 쉽도록 사이트를 개선하는 프로세스이다. 검색엔진 최적화 작업을 하는 사람의 직책을 의미하기도 한다.
  • Facebook에서 시작한 개발자들끼리의 약속

🤔 오픈그래프 사용방법

  • ❓ 개발자관점 코드예시

      const onClickEnter = async (): Promise<void> => {
        // 1. 채팅데이터에 주소가 있는지 찾기 (ex, http~로 시작하는 것)
        // 2. 해당 주소로 스크래핑 하기
        const result = await axios.get(
          "http://localhost:3000/section32/32-01-opengraph-provider"
        ); // CORS : https://www.naver.com
        console.log("result::", result.data);
        // 3. 메타태그에서 오픈그래프(og:) 찾기
        console.log(result.data.split("<meta"));
        console.log(
          "og:",
          result.data.split("<meta").filter((el: string) => el.includes("og:"))
        );
      };
  • ❓ 제공자관점 코드예시

      <Head>
            <meta property="og:title" content="중고마켓" />
            <meta
              property="og:description"
              content="중고마켓에 오신것을 환영합니다!"
            />
            <meta property="og:image" content="http://~~~" />
          </Head>
          <div>중고마켓에 오신 것을 환영합니다!(여기는 Body입니다.)</div>
        </>

🤔 리렌더링

  • ❓ React의 re-rendering
    UI와 state를 연동시키기 위함인데,리액트에서는 setState로 방법을 제한 시키고, 이 함수가 호출될 때마다 리렌더링이 되도록 설계되어 있다.

  • ❓ 렌더링 될 때 일어나는 것
    • 기존 컴포넌트의 UI를 재사용할 지 확인한다.

    • 함수 컴포넌트: 컴포넌트 함수를 호출한다 / Class 컴포넌트: render 메소드를 호출한다.

    • 호출한 결과를 통해서 새로운 VirtualDOM을 생성한다.

    • 이전의 VirtualDOM과 새로운 VirtualDOM을 비교해서 실제 변경된 부분만 DOM에 적용한다.
  • 자세한 설명
    • 리액트에서는 UI의 변화가 발생하면 변화에 필요한 DOM조작들을 매번 바로 실제 DOM에 적용하는 것이 아니라, VirtualDOM이란 리액트가 관리하고 있는 DOM과 유사한 객체형태로 만들어낸다.

    • 그리고 이전의 VirtualDOM과 새로운 VirtualDOM을 비교해서 실제로 변화가 필요한 DOM요소들을 찾아낸다.

    • 그 다음에 한 번에 해당 DOM요소들을 조작한다.

✅ 본격적인 성능 최적화

  • 🤔 성능 점검
    • page-speed-insight
      => 도메인 주소를 입력하면 해당 주소의 웹페이지의 성능을 점검하여 개선사항을 확인 할 수 있다.


    • React Develope Tools
      • component
        =>리액트 파일 구조 확인 가능
      • profiler
        => 녹화를 해서 렌더링 되는 컴포넌트들을 확인 할 수 있다.
      • Highlight updates when components render.
        => profiler의 설정에서 선택하면, 리렌더가 되는 대상을 알려준다.

    • performance
      => 녹화를 하면 Call Tree(요청트리)에서 작업된 내용을 확인 가능하다.

  • 🤔 이미지 미리보기

    • 기존방식

      • 클라우드 스토리지에 저장 후 그 이미지를 미리보기로 제공하는 방식
        => 미리보기 속도가 너무 느리다.
        => 최종 등록하기를 누르고 않으면, 이미지 찌꺼기가 남는다.
    • 해결 방안 (임시 URL 생성)

      • createObjectURL
        호환이 안 되는 곳이 있으니 확인 후 사용.
          if (file === undefined) return;
          // 1. 임시 URL 생성 => (가짜 URL - 내 브라우저에서만 접근가능)
          // 자바스크립트 내장 객체
          //blob까지 같이 복사해서 넣어야 나옴
          const result = URL.createObjectURL(file);
          console.log(result);
      • FileReader
            FileReader => 사진 자체를 글자로 표현 이것을 저장하면 안된다.
              // 2. 임시 URL 생성 => (진짜 URL - 다른 브라우저에서도 접근 가능)
          const fileReader = new FileReader();
          fileReader.readAsDataURL(file);
          fileReader.onload = (event) => {
            console.log(event.target?.result); // 게시판에서 event.target.id를 쓰면 eslint가 잡았던 이유: event.target 태그만을 가르키지 않음
            if (typeof event.target?.result === "string")
              setImageUrl(event.target?.result);
          };
  • 🤔 이미지 업로드 방법

    • 기존 방식

      for문과 await를 같이 쓰는 것은 안티패턴으로 하나의 이미지를 올릴 때 다른 이미지는 기다려야 하는 문제점이 있다.

    • 해결방안

      👍 Promise.all

        const results = await Promise.all(
          files.map(async (el) => await uploadFile({ variables: { file: el } }))
        );
        console.log(results); // [resultFile0, resultFile1, resultFile2]
        const resultUrls = results.map((el) => el.data?.uploadFile.url);

      => 동시에 처리가 가능하고, 모두 완료 되었을 경우 results에 값이 담긴다.

    • 이미지 종류 설정

      validation으로 따로 설정 할 수도 있지만, input 자체에 accept라는 기능이 있어 입력자체를 막을 수있다.


  • 🤔 메모이제이션 (memoization)
    • 내용

      Memoization은 특정한 값을 저장해뒀다가, 이후에 해당 값이 필요할 때 새롭게 계산해서 사용하는게 아니라 저장해둔 값을 활용하는 테크닉을 의미한다.

    • useMemo

      => 리액트에서 값을 memoization 할 수 있도록 해주는 함수이다.

      // const aaa = Math.random();
      const aaa = useMemo(() => Math.random(), []);
      console.log(aaa);
      => 계산이 복잡한 값을 변수로 기억해 놓고 싶을 때 사용한다.
    • useCallback

      => useMemo를 조금 더 편리하게 사용할 수 있도록 만든 버전으로 함수를 기억한다.

      let countLet = 0;
      // const onClickCountLet = (): void => {
      //   console.log(countLet + 1);
      //   countLet += 1;
      // };
      const onClickCountLet = useCallback((): void => {
        console.log(countLet + 1);
        countLet += 1;
      }, []);
    • memo

      => 하위 컴포넌트의 경우에는 props가 변화하지 않았다면 리렌더링을 하지 않도록 설정해 주는 것이다.

      const MyComponent = React.memo(function MyComponent(props) {
      /* render using props */
      });
      • props를 비교하는 방식
        React.memo는 기본적으로 props의 변화를 이전 prop와 새로운 prop를 각각 shallow compare 해서 판단한다.

      • areEqual
        function areEqual(prevProps, nextProps) {
          /*
            true를 return할 경우 이전 결과를 재사용
            false를 return할 경우 리렌더링을 수행
          */
        }
        export default React.memo(MyComponent, areEqual);
  • 🔥 주의사항

    변하는 값이 넘어가면 리렌더가 되기 때문에 불필요하게 된다. 모든 컴포넌트에 memo를 넣으면 오히려 memo에 메모리를 낭비 할 수 있다.


🤔 reflow와 repaint

  • 배경지식

    CRP(Critical Rendering Path)는 브라우저가 HTML, CSS, Javascript를 화면에 픽셀로 변화하는 일련의 단계를 말하며 이를 최적화하는 것은 렌더링 성능을 향상시킨다.
    CRP 내용 확인

  • CRP 과정

    • 다운로드

    • HTML준비 => DOM 저장 (DOM API 사용 가능)

    • CSS준비 => CSSOM 저장

    • DOM과 CSSOM 합친 것을 렌더트리(Render Tree)라고한다.

    • 위치그리기 => layout (reflow)

    • 색칠하기 => paint (repaint)
      repaint 변경 내용 확인 사이트 => 리페인트

    • 컴포지트 => Composite
      이미 페인트된 다양한 레이어를 화면에 조합하여 최종 출력을 생성한다.
  • 코드로 확인하는 reflow / repaint

    <!DOCTYPE html>
    <html lang="ko">
      <head>
        <title>리플로우 리페인트</title>
        <style>
          .myreflow {
            width: 100px;
            height: 100px;
            background-color: skyblue;
          }
          .myreflow:hover {
            width: 110px;
            height: 110px;
          }
          .myrepaint {
            width: 100px;
            height: 100px;
            background-color: yellow;
          }
          .myrepaint:hover {
            background-color: red;
          }
        </style>
      </head>
      <body>
        <div class="myreflow">리플로우 연습!</div>
        <div class="myrepaint">리페인트 연습!</div>
      </body>
    </html>
  • 고려사항

    • reflow 위치가 바뀌면 색도 바뀌어 refaint도 바뀌기 때문에 이부분을 조심해야 한다.

    • http 버전마다 다르지만, 우리가 일반적으로 사용하는 1.1 버전은 6개씩 다운로드를 받고, 다음 6개를 받는 식으로 진행한다.

      <!-- 
            << 리소스 다운로드 순서 >>
            1. HTML 파일의 다운로드 주소를 읽어들인 후, 백그라운드에서 6개씩 다운로드 요청 시도
            2. 위에서부터 읽기 시작
            3. <script /> 태그의 "정지!!" 라는 구문에서 화면은 멈췄지만, 이미 다운로드 요청이 들어간 상태
      -->
      <!DOCTYPE html>
      <html lang="ko">
      <head>
        <title>리소스 다운로드 순서</title>
      
        <script>
          alert("정지!");
        </script>
      
        <link rel="stylesheet" href="./index.css" />
      </head>
      <body>
        <script src="./index.js"></script>
      </body>
      </html>


  • 🤔 프리페치 (prefetch)

    • 내용

      다음페이지의 데이터 다음에 보여질 페이지에서 사용될 데이터를 미리 다운로드 하는 것으로, 마우스를 올렸을 때 프리페치를 사용하는 방식으로 사용 가능하다.

    • 프리패치

      <!DOCTYPE html>
      <html lang="ko">
        <head>
          <title>프리페치</title>
          <!-- 프리페치: 다음페이지를 미리 다운로드 받으므로, 버튼 클릭시 다운로드 안받고 이동하므로 빠름 -->
          <link rel="prefetch" href="./board.html" />
        </head>
        <body>
          <!-- 라이브서버에서 Disavle-cache 해제하고 테스트 -->
          <a href="./board.html">게시판으로 이동하기</a>
        </body>
      </html>
    • 데이터의 프리패치

      onMouseOver={wrapAsync(prefetchBoard(el._id))}
        const prefetchBoard = (boardId: string) => async () => {
        await client.query({
          query: FETCH_BOARD,
          variables: {
            boardId,
          },
        });
      };
    • 프리패치 확인

      • 프리패치는 캐쉬 영역에 다운로드 되어 확인을 하려면, disable cache 부분을 해제하고 확인해야 한다.
      • Apollo Client Devtools 에서 확인할 수 있음.
  • 🤔 프리로드 (preload)

    • 내용

      현재페이지의 다운로드 순서를 조작. 용량이 큰 이미지 먼저 다운로드하여 전체 다운로드 시간 축소한다.

    • 코드확인
      <!DOCTYPE html>
      <html lang="ko">
        <head>
          <title>프리로드</title>
      
          <!-- 1. 프리로드란? 한 번에 6개씩 받아오므로, body태그의 이미지는 가장 마지막에 다운로드 -->
          <!-- 눈에 보이는 이미지를 먼저 다운로드 받아서 보여주고, 클릭하면 실행되는 JS는 나중에 받아오기 -->
          <!-- 전체 모든 파일 총 다운로드 시간은 더 짧아짐 -->
      
          <!-- 2. 만약, 여기서 프리페치를 한다면? 프리페치는 다음을 위해서 받는 것이므로 마지막에 받음(img로 1번 받고, prefetch로 1번 총 2번 받음) -->
          <link rel="preload" as="image" href="./taewan.jpg" />
          <!-- Disable-cache 체크하고 테스트(매번새로) -->
          <link rel="stylesheet" href="./index.css" />
          <script src="./index1.js"></script>
          <script src="./index2.js"></script>
          <script src="./index3.js"></script>
          <script src="./index4.js"></script>
          <script src="./index5.js"></script>
          <script src="./index6.js"></script>
        </head>
        <body>
          <img src="./taewan.jpg" alt="" />
        </body>
      </html>

  • 🤔 브라우져에서의 최적화

    • 레이아웃 쉬프트 (Layout Shift)

      화면이 렌더링 되면서 화면이 뚝떨어지는 현상
      여기 저기서 일어나게 되면, 사용자가 보기가 안 좋다.
      network에서 속도를 조절하여 확인 하자.

    • 해결방안

      => 값이 없을 경우에는 더미 데이터를 넣자. 이렇게 더미 데이터를 넣어두면, 미리 영역을 설정하여 레이아웃 쉬프트 현상을 없애자.

          <div>
            {(data?.fetchBoards ?? new Array(10).fill(1)).map((el) => (
              <div key={el._id} style={{ height: "30px" }}>
                <span>
                  <input type="checkbox" />
                </span>
                {/* 원래 변수를 생성하여 넣어야 하는데 바로 넣어서 {{}} 두 번 넣어줘야함 */}
                <span style={{ margin: "10px" }}>{el._id}</span>
                <span style={{ margin: "10px" }}>{el.title}</span>
                <span style={{ margin: "10px" }}>{el.writer}</span>
              </div>
            ))}
            {/* fill(1) 1로 채워줌 */}
            {new Array(10).fill("철수").map((_, index) => (
              <span key={index + 1} id={String(index + 1)} onClick={onClickPage}>
                {index + 1}
              </span>
            ))}
          </div>

  • 🤔 다이나믹 임포트 (Dynamic Import) - 코드 스플리팅 (Code Splitting)

    • 내용

      필요한 시점에 import하여, 초기 로딩 속도를 줄일 수 있다.

    • 코드확인

           useEffect(() => {
             // 필요 시점에 다운로드 받자!
             async function aaa(): Promise<void> {
               const { Modal } = await import("antd");   code-splitting(코드스플릿팅)
             }
             void aaa();
           }, []);
    • 응용

        // 메모리 누수가 생길 수 있음.
      const qqq = [];
      
      export default function ImagePreloadPage(): JSX.Element {
        const router = useRouter();
      
        useEffect(() => {
          // 이미지 태그가 만들어짐
          const img = new Image();
          img.src =
            "https://upload.wikimedia.org/wikipedia/commons/2/22/The-Last-Supper-Restored-Da-Vinci_32x16.jpg";
          img.onload = () => {
            qqq.push(img);
          };
        }, []);
      
        const onClickMove = (): void => {
          void router.push("/section31/31-09-image-preload-moved");
        };
    • 추가사항

      • Next에서는 - next/dynamic, React 에서는 react/lazy를 사용하여 원할 때 동적으로 import하는 기능을 제공한다.

  • 🤔 옵티미스틱 ui (Optimistic-UI)

    • 내용

      • 캐시수정 과정에서 주로 사용하는데, 낙관적인 UI라는 뜻과 같이 특정 API가 설공할 것을 가정하고 그 결과를 먼저 보여주는 것이다.

      • 덜 중요하면서, 실패해도 문제가 안되고, 성공 확률도 99%인 곳에서 주요 사용된다.
    • 예시코드

       const onClickLike = (): void => {
        void likeBoard({
          variables: {
            boardId: "64c38cdf5d6eaa0029f77ca3",
          },
          //   refetchQueries: [{}],
          //   optimisticResponse: {
          //     likeBoard: (data?.fetchBoard.likeCount ?? 0) + 1,
          //   },
          update: (cache, { data }) => {
            cache.writeQuery({
              query: FETCH_BOARD,
              variables: { boardId: "64c38cdf5d6eaa0029f77ca3" },
              data: {
                fetchBoard: {
                  _id: "64c38cdf5d6eaa0029f77ca3",
                  __typename: "Board", //리턴타입
                  likeCount: data?.likeBoard, // 좋아요 갯수(6)
                },
              },
            });
          },
        });
      };
profile
로건의 개발이야기

0개의 댓글