본문 바로가기

리액트 공식 문서 읽기

26화: You Might Not Need an Effect

이 글은 단순히 리액트 공식 문서를 읽고 베꼈을 뿐이다!! 학습 목적이라면 공식 문서를 보는 걸 추천한다.

 

참새. Unsplash 에 Valentin Balan 님이 올림

공식 문서

https://react.dev/learn/you-might-not-need-an-effect

 

You Might Not Need an Effect – React

The library for web and native user interfaces

react.dev

불필요한 이펙트를 없애는 법

  • 렌더링에 쓸 데이터 변환을 위한 이펙트는 필요 없음: 이펙트 자체가 commit 이후 실행됨 -> 렌더링 두 번씩 일어남
  • 이용자가 하는 행동들을 처리하기 위한 이펙트는 필요 없음: 이벤트 핸들러가 해야 할 일임
  • 이펙트는 외부 시스템과의 연동을 위한 것: 외부 시스템 간섭이 없다면 이펙트도 필요 없음

props나 상태 기반으로 다른 상태를 업데이트하기

  • 어떤 값이 이미 있는 props나 상태로부터 계산될 수 있으면 따로 상태로 만들지 않기
  • 그냥 렌더링 도중에 계산하기
  • 자식들의 리렌더링을 줄일 수 있음
  • 코드가 간단해짐
  • 버그가 줄어듦

비싼 연산 캐싱하기

  • 연산이 비싸서 특정 값이 변했을 때만 연산하기 위해 이펙트를 사용하지 않기
  • useMemo로 적절한 메모이제이션 가능
  • useMemo에 들어가는 함수는 렌더링 도중에 사용하므로 순수해야 함
  • console.time(), console.timeEnd()로 얼마나 비싼지 측정 가능
    • Strict Mode에서는 두 번씩 렌더링하므로 측정이 잘 안 될 수도
    • 일반적으로 개발컴 사양이 일반컴 사양보다 좋을테니 감안하기
    • 크롬의 CPU 쓰로틀링 옵션 등 사용해보기

prop이 바뀌었을 때 모든 상태 초기화하기

  • 이펙트로 초기화를 하면 두 번 렌더링함
  • 부모 컴포넌트에서 key를 명시해주는 방법으로 상태 초기화 가능

prop이 바뀌었을 때 몇몇 상태 바꾸기

  • 이펙트로 하면 두 번 렌더링해야 함
  • 이전 prop을 기억하는 상태를 만들고, 렌더링에서 직접 if문을 이용해 차이가 있으면 상태 설정 함수 사용하기
  • 렌더링 도중에 상태 설정 함수가 사용되면 리액트는 해당 렌더에서 JSX가 return 되자마자 다시 렌더링함: 자식에 대한 연쇄적인 렌더 줄일 수 있음
  • 렌더링 도중에는 본인의 상태만 바꿀 수 있음
  • 이 패턴은 이펙트보다 효율적이긴 한데 대부분은 이것조차 필요하지 않음
  • 항상 key로 전체 상태 초기화 또는 렌더링 도중에 충분히 계산이 가능한지 생각해보기

이벤트 핸들러끼리 로직 공유하기

  • 여러 이벤트 핸들러에서 중복되는 로직을 줄이겠답시고 이펙트에 넣지 말기
  • 중복 로직은 이펙트가 아닌 다른 함수로 분리하는 방향으로 줄일 것
  • 어떤 코드를 이펙트에 넣기 전에 왜 넣어야 하는지 고민하기
  • 이펙트에는 컴포넌트가 화면에 보여졌기 때문에 실행해야 하는 로직을 넣기: 이벤트가 일어났기 때문에 실행해야 하는 로직은 넣지 말기

POST 요청 보내기

  • 이펙트에는 컴포넌트가 화면에 보여졌기 때문에 실행해야 하는 로직을 넣기: 이벤트가 일어났기 때문에 실행해야 하는 로직은 넣지 말기

이펙트의 연쇄 작용

  • A 상태를 바꾸면 이펙트로 B를 바꾸고, B가 바뀌면 이펙트로 C를 바꾸고, ... 이런거 하지말라는 뜻
  • 일단 느림: 계속 리렌더링 발생
  • 요구사항이 추가되거나 바뀌면 불안정해질 수 있음
  • 렌더링 도중에 할 수 있는 건 직접 게산하기
  • 상태 변경은 가능하면 이벤트 핸들러에서 처리하기

어플리케이션 초기화

  • 컴포넌트가 mount될 때 이펙트로 초기화를 할 경우 Strict Mode의 두 번 mount에서 버그가 날 수 있음
  • 최상단 변수를 이용해 이미 초기화가 되었는지 확인하기
  • 아니면 초기화 코드를 통째로 최상단에 넣기: 이러면 컴포넌트가 맨 처음 import됐을 때만 실행함. 하지만 느려질 수 있으므로 남용하지 말 것

부모 컴포넌트에게 상태가 바뀌었음을 알리기

  • 이펙트에는 컴포넌트가 화면에 보여졌기 때문에 실행해야 하는 로직을 넣기: 이벤트가 일어났기 때문에 실행해야 하는 로직은 넣지 말기
  • 여러 개의 다른 상태를 동기화해야 할 경우 상태 끌어올리기를 고려해보기 (부모의 로직이 커지긴 하지만 문제는 덜 생김)

부모 컴포넌트에게 정보 전달하기

  • 리액트에서 정보 흐름은 부모에게서 자식으로 내려가는 방식임
  • 거꾸로 하면 문제가 생겼을 때 추적이 힘듦
  • 부모와 자식이 같은 정보를 필요로 한다면 props로 내려줄 것

외부 저장소를 구독하기

  • 이펙트를 이용해서 외부 저장소와 동기화하는 건 가능
  • 근데 useSyncExternalStore라는 리액트 훅을 쓰면 더 편함
  • const snapshot = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)
    • subscribe: 외부 저장소를 구독하는 함수. 구독 해제 함수를 반환해야 함
    • getSnapshot: 저장소의 값 snapshot을 반환하는 함수. 저장소의 값이 변하지 않으면 항상 같은 결과를 반환해야 함. 결과를 Object.is로 비교해서 다르면 리렌더링
    • getServerSnapshot: 저장소의 초기값 snapshot을 반환하는 함수. 서버에서 컴포넌트를 렌더링하는 경우 필요

fetch

  • 현재 페이지 번호 또는 검색어가 바뀌었을 때 새로 fetch하는 로직 같은 애들은 이펙트에 있는 경우가 많음
  • 얘네는 이펙트에 둬도 됨
    • fetch를 해야 하는 이유가 검색어 입력이 아님: 일반적으로 검색어는 url에 들어 있는 경우도 많음
    • 그냥 네트워크의 정보와 컴포넌트의 정보를 페이지 번호, 검색어를 기반으로 동기화하고 싶은 것
    • 뒤로 가기, 앞으로 가기를 했을 때에도 다시 fetch를 해야 할 수도 있음
  • 근데 사실 고려해야 할 게 한두 가지가 아님
    • race condition 방지를 위해 cleanup 함수를 넣기
    • 중복되는 정보에 대한 fetch를 막기 위해 캐싱하기
    • 서버에서 렌더링해서 보내줄 경우 초기 데이터 설정하기
    • 부모의 fetch와 자식의 fetch를 동시에 실행해서 시간 절약하기
  • 프레임워크를 쓰는 경우 그 친구들이 제공해주는 방법을 쓰는 게 나을 가능성이 높음
  • 안 쓴다면 커스텀 훅을 만들어보기