JIGGAG

1월 한달동안 로그

2025년 2월 26일

Fix the slow render before you fix the re-render

  • 리렌더링이 나쁜것일까???
    • 핸드폰이 아우성을 치는 것을 보면 나쁘다
    • 리액트는 DOM 을 비교하여 변경된 부분만 업데이트 할 수 있도록 한다
    • 이때 DOM을 비교하기 위해 createElement 를 호출하여 DOM 을 그린다 → render
    • DOM element 비교한다 → reconciliation
    • 변경된 부분들만 실제 DOM에 반영한다 → commit
    • state change -> render > reconciliation > commit
  • render 자체가 느리다면 성능 문제가 발생한다
    • 무조건 리렌더 되었다고 DOM 이 업데이트 되는 것은 아니다
    • 그렇다면 렌더 자체 비용이 크다면?
    • 3의 렌더 비용을 가지는 리렌더 10번 vs 10의 렌더 비용을 가지는 리렌더 3번
    • 총 연산 비용은 같지만 아무래도 한번에 10을 연산하는 비용은 문제가 될 수 있다
  • 리렌더를 최소화 하는 것 vs 비용이 큰 연산을 막는 것
    • 사용처에서 계산 하도록 시점을 늦춰둔 로직이 많다
    • 미리 연산해둘 필요가 없다는 점에서 선택한 방향인데
    • 리렌더가 많은 것이 문제가 아니라 비용이 큰 렌더가 문제라면
    • 이 연산을 미리 해두어서 렌더 비용이 작아지도록 해야하지 않을까

AI에게 시니어 개발자처럼 코드를 읽도록 가르치다

  • 라인 단위로 코드를 읽는 것보다 핵심이 되는 부분을 먼저 읽어가도록
    • 핵심 > 기능 > 숲 > 나무
  • 단순히 생각하는 순서를 바꿔주었을뿐인데 이해도가 높아졌다는 사실
    • 맥락을 이해하게 되었고 패턴을 찾아 변경사항이 어떤 영향을 주는지 파악
    • 전체를 보게 되었기 때문에 개선점 (중복된 코드, 일관되지 않은 패턴, 성능 개선) 을 알려줄 수 있게 되었음
  • 나 스스로에게도 가르쳐 본다면
    • 단순히 코드를 빠르게 작성하는게 아니라 전체를 이해하고 있는 역량 필요

(번역) 실제 코드에서 리액트 컴파일러의 성능

  • 상태를 사용자에게 표현하기 위해 리액트를 리렌더링이 발생한다

    • 부모에서 자식으로, 트리 아래로 연쇄적으로 일어난다
    • 중간에 어떤 컴포넌트에서 비용이 크다면 성능 문제가 발생할 수 있다
    • 이를 방지하기 위에 상태를 내리거나 메모이제이션 한다
      • 메모가 제대로 작동하려면 리렌더링 사이에 모든 프로퍼티가 *정확히* 동일하게 유지되도록 해야 합니다.
      • 참조 비교를 하기 때문에 ... 컴포넌트 내부에서 이러한 비원시값을 선언하면 리렌더링할 때마다 다시 생성되고 참조가 변경되며 메모이제이션이 작동하지 않습니다.
    • 이를 해결하기 위해 useCallback, useMemo 가 있는데
    • 이러한 메모, 메모, 메모… 상황을 해결하고자 나온 것이 컴파일러이다
    • 이 내용은 항상 컴파일러 이야기가 나오면 다시 생각해보는 현재 상황이다
  • 컴파일러는 모든 코드를 읽고 알아서 메모이제이션 해준다는 것을 목표로 한다

    1. 메모이제이션을 리액트가 직접 해야하기에 초기 로드 성능에 영향이 갈 수 있지 않을까?
      • lighthouse에서 확인했을때 문제가 없는 것으로 나왔고
    2. 리렌더링은 정말 문제일까?
      • 이건 정말 문제라고 생각했는데, 이번 포스팅을 읽고나서 조금 바뀔 수 있겠다는 생각 🤔
    3. 컴파일러가 모든 리렌더링 문제를 해결해줄 수 있을까?
    • 위의 3가지 질문에 대해 검증해본다
  • 메모이제이션이 정말 되었을까?

    <VerySlowComponentMemo>
      <ChildMemo />
    </VerySlowComponentMemo>
    
    // 이러한 형태의 children 은 이렇게 직접 객체 형태로 넘기는 것과 동일하다 🙈
    <VerySlowComponentMemo children={{ type: ChildMemo }} />
    
    // 이렇게 되어야만 진짜 메모이제이션
    const children = useMemo(() => <ChildMemo />, []);
    <VerySlowComponentMemo>{children}</VerySlowComponentMemo>
    
    • 이 코드가 컴파일러에 의해 제대로 처리 되었다는 사실
  • 근데 의문인 것은 props로 객체가 전달되었을때 왜 컴파일러가 스스로 처리하지 못했을까 하는 것

    • 예시 에서 컴파일러가 메모이제이션 처리 했음에도 리렌더가 발생하는 문제를 해결하기 위한 내용인데
    • 컴파일러 문제를 디버깅할 때 가장 먼저 하는 일은 고전적인 도구로 메모이제이션을 다시 구현하는 것입니다.
      • 이런 경우 디버깅은 수동 메모이제이션을 이용한다는 것은 재미난 사실
      • 만약 수동 메모로 리렌더 문제가 해결되었다면 컴파일러의 문제점으로 볼 수 있지만 여기서 문제는 여전했다
    <GalleryCardMemo
      href={`/examples/code-examples/${example.key}`}
      key={example.key}
      title={example.name}
      // 여기 props 로 전달하는 previewUrl 자체가 객체였다
    	preview={example.previewUrl}
    />
    
    // 문제가 되는 부분을 직접 수정하여 해결
    <GalleryCard
      href={`/examples/code-examples/${example.key}`}
      key={example.key}
      title={example.name}
      // 전체 객체 대신 원시 값들을 전달
      previewLight={example.previewUrl.light}
      previewDark={example.previewUrl.dark}
    />
    
    • 컴파일러는 표면적?으로 작성된 코드에 대해 메모이제이션 적용하였다
      • 하지만 그 표면 아래에 숨어있던 previewUrl 이라는 객체가 그대로 props 로 전달되었을때에는 메모이제이션 하지 못하였다
      • 컴포넌트 자체가 리렌더 되고 있으니 컴파일러로써는 어떻게 해결할 수 없었던듯
      • 전달되는 모든 props 를 메모이제이션 하지는 않나보다 🤔
    • 사람의 노력이 들어가면 더 좋은 효과를 만들 수 있습니다.
  • 마지막 내용처럼 사람의 노력이 들어간다면 더 좋은 효과를 낼 것이다

어떻게 공부할 것인가

  • 모르는것이 더 늘어가는 요맘때
  • 아는 것도 잊어버리고 있기 때문이다
  • 아는것과 모르는것을 구분하는 것이 중요하다
  • 책을 읽고 있을때엔 지금 막 새로 알게 되었더라도 기억하고 있고 써먹을 수 있을 것 같은 기분이다
    • 하지만 아는 것을 써봐라 한다면 벌써 잊어버린 것이 현실
    • 모르지만 마치 알고 있다고 느끼는 거짓 감각
  • 이를 더 오래 간직하기 위하여
    • 집중적인 학습과 그 학습 내용을 증명해보는 것
      • 책 읽고 독후감을 써야했던 이유가 그 책을 온전히 이해했는지 알아 볼 수 있는 방법이였다
    • 알고 있는 것을 실전에 적용해보거나 누군가에게 아는 것을 공유하는 것
    • 글로 쓰는 것
      • 이야기는 하나의 구조다. 구조 형성을 잘하는 사람은 기반 시설과 중심 건물의 블록을 알아볼 수 있고 새로운 정보를 지식의 더 큰 구조에 편입할지, 관계없다고 판단하여 치워버릴지 가려내는 기술을 계발한다.
      • 책을 많이 본 사람이 글을 잘 쓰고 글을 잘 쓰는 사람이 생각을 잘 표현할 수 있다고 하는데
      • 구조를 잘 만들고 구조에 적합한 형태를 골라낼 수 있다는 것

어디선가 읽었는데

  • 우연하게도 시간이 있어 잠시 들른 서점에서 우연하게도 책을 몰아서 읽게 되었고 우연하게도 그 내용은 비슷했다

    • 아마도 그 자리를 벗어나지 않은채 같은 책장에서 꺼내 읽어서 그런걸지도
    • 관심사가 그러하니 그럴지도
  • 자극을 받았던 문장을 기억하고 공유하고 싶었는데 떠오르는 방법이 없어서 OCR

    • 그렇게 가져온 문장은 조금 어색하지만 괜찮은걸?

    승리는 협상을 모른다. 당신은 이기거나 지거나 둘 중 하나다. 승리는 당신이 얼마나 피나게 노력했는지에 관심이 없고, 당신을 방해한 장애물이 있었다 한들 정상을 참작해주지 않는다. 승리는 이렇게 말한다. "열심히 노력했다고? 다행이군. 난 열심히 노력하고 똑똑하고 민첩하게 움직이는 사람이 좋거든. 자, 이제 다시 가서 줄 서. 그리고 해내보라고."

    해내라. 미친 사람이라는 소리를 들을 만큼 당신의 모든 것을 쏟아부어라. 모방하지 말고 혁신적으로 움직여라. 그리고 무엇보다도, 이렇게 저렇게 해야 한다는 주변 이들의 입방아에 휘둘리지 마라. 그들이 진짜 방법을 알았다면 진작 승자가 되었을 것이다.

    마이클의 뛰어난 점 중 하나는 실행 능력이었다. 동작의 아주 작은 차이가 그를 코트 위의 누구보다 빠르게 행동하고 반응해 게 만들었다. 그가 빠르게 슛을 쓸 수 있었던 까닭은 무릎을 굽 힌 상태로, 어깨가 슛에 적합한 각도가 된 상태로 공을 잡았기 때문이다. 즉 이미 슛을 날릴 자세가 된 상태로 말이다. 다른 선 수들은 보통 공을 잡고 나서 슛을 쏠 자세를 잡는다. 마이클은 이미 그 자세였다. 이미 장전된 총이었다. 패스를 받고 나서 자 세를 가다듬거나 어깨를 돌리는 경우는 드물었다. 그가 해야 할 일은 공을 잡은 뒤 고개를 돌려 골대를 확인하는 것뿐이었다. 지금도 나는 이것이 그가 연습으로 습득한 능력인지, 아니면 그저 자연스럽게 그 경지에 이른 것인지 잘 모르겠다. 확실한 것은 그 덕분에 단 0.1초도 추가 스텝을 어떻게 해야 할지 생각 하거나 신체적, 정신적 에너지를 낭비할 필요가 조금도 없었다 는 사실이다.

    따라서 우리의 허영, 자기애는 천재 예찬을 부추긴다. 천재 를 우리와 아주 동떨어진 존재, 기적으로 생각할 때만 그 사 람 때문에 기분이 상하는 일이 없기 때문이다. ••• 천재도 먼 저 벽돌 쌓는 법을 배운 뒤 건물 짓는 법을 배우며, 끊임없이 재료를 찾고 그 재료를 써서 꾸준히 자신을 만들어간다. 천 재의 활동만이 아니라 사람의 모든 활동은 놀라울 만치 복 잡하다. 하지만 그 어느 것도 '기적'은 아니다. -프리드리히 니체, 『인간적인, 너무나 인간적인』

    진정한 학습은 원 래 어렵다. 그것은 주의와 집중을 요구한다. 무엇이든 쉽 게 익히는 편이었다면 오히려 더 그럴 수 있다. 필요한 일 을 어떻게든 해치우기는 했으나 자신이 어떻게 발전해 나 갔는지 스스로 평가할 줄 몰랐던 사람도 마찬가지다. 우리 는 결과를 과정과 동일시하는 경향이 있다. 그러나 학습은 그런 식으로 이루어지지 않는다. 우리의 지성은 돌파구에 이르기까지 모든 단계에서 성장한다. 언뜻 볼 때 결과가 비슷해 보인다고 해도 어떻게 배웠느냐에 따라 성장의 정 도는 다르다. 막막함을 견디며 버거운 과제에 몰입한다는 것이 쉽지는 않지만, 그 끝에 새로운 지평이 열린다면 치 를 수 없는 값도 아니다. 스트레칭할 때 닿기 힘든 곳까지 몸을 뻗는 순간 근육이 자란다고 한다. 지적 근육 또한 새 롭고 낯설고 조금 불편한 시도를 통해 자란다.