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

공식 문서
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를 동시에 실행해서 시간 절약하기
- 프레임워크를 쓰는 경우 그 친구들이 제공해주는 방법을 쓰는 게 나을 가능성이 높음
- 안 쓴다면 커스텀 훅을 만들어보기
'리액트 공식 문서 읽기' 카테고리의 다른 글
28화: Separating Events from Effects (0) | 2023.06.22 |
---|---|
27화: Lifecycle of Reactive Effects (0) | 2023.06.22 |
25화: Synchronizing with Effects (0) | 2023.06.20 |
24화: Manipulating the DOM with Refs (0) | 2023.06.19 |
23화: Referencing Values with Refs (0) | 2023.06.19 |