읽어보았던
useMemo와 useCallback
- [번역] useMemo 그리고 useCallback 이해하기
리렌더링
- 상태를 기반으로 DOM이 어떻게 그려졌는지에 대한 스냅샷
- 이러한 스냅샷을 빠르게 만들어서 사용자에게 UI 업데이트가 반영된 버전을 전달하기 위해
리렌더링 최적화
가 필요하다- 단순히 렌더링 과정에서 작업해야하는 양을 줄이거나
- 다시 렌더링 되어야하는 횟수를 줄이는 방법이 있다
useMemo, useCallback 이용하기
- 무겁고 시간이 오래 걸리는 연산을 다시 하지 않도록 캐싱된 데이터를 사용하는 것
- useMemo를 이용해 매번 계산을 하지 않고 다시 사용할 수 있도록 처리할 수 있다
- 리렌더링 되는 시점마다 새로운 상태 참조값을 반환하게 되고 이는 하위 컴포넌트를 전부 리렌더링 하게 된다
- 컴포넌트에 memo를 사용하게 된다면 마치 순수 컴포넌트인 것 처럼 여겨지지만
- 실제로는 메모이제이션 하지 않은 props를 전달 받는다면 (프리미티브 타입이라면 그나마 괜찮지만 그렇지 않은 경우)
- memo가 되었더라도 참조값 변경된 props에 의해 컴포넌트 전체가 리렌더링 된다
- 이것은 참조가 변경되지 않도록 children도 useMemo 해야하는 이유
- useMemo이 값의 참조 캐싱을 위한 것이라면 useCallback 값 대신 함수
- 이 글에서는 과한 useMemo, useCallback 사용을 적극 권하고 있지 않지만 개인적으로는 적극 권한다
- 분명 이슈가 생기기 때문이다
- 아마 이 글에서는 과하게 사용하기 보다 최적화 이슈가 발생했을때 해당 원인을 좀 더 정확하게 파악하면서 처리하기를 기대하는 의미인 것 같다
useEffect 완벽가이드
- 코뿔소 JS 완벽 가이드처럼... useEffect 완벽가이드
- 모든 랜더링은 고유의 Prop과 State가 있다
- state를 업데이트할 때마다, 리액트는 컴포넌트를 호출합니다. 이 state값은 함수 안에 상수로 존재하는 값입니다
- 리액트를 통해 전달된 값(state)은 컴포넌트가 다시 호출됨에 따라 DOM에 업데이트 되는 것
- 각각의 렌더링마다 격리된 고유한 값이 바뀌는 것이다
- 특정 랜더링 시 그 내부에서 props와 state는 영원히 같은 상태로 유지됩니다
컴포넌트의 랜더링 안에 있는 모든 함수는 (이벤트 핸들러, 이펙트, 타임아웃이나 그 안에서 호출되는 API 등) 랜더(render)가 호출될 때 정의된 props와 state 값을 잡아둔다
- 왜 useReducer가 Hooks의 치트 모드인가
- 랜더링간 dispatch 의 동일성은 여전히 보장는 것을 이용하여
- reducer를 컴포넌트 내부에서 정의하고 props에 접근하도록 하고 모든 랜더링마다 새로운 props를 바라보도록 정리
- dispatch는 그냥 액션만 기억할뿐 새로운 스코프의 리듀서가 동작
- 하지만 저는 이 함수를 이펙트 안에 넣을 수 없어요
- 의존성을 추가하고 싶지 않은 경우
- prop이나 state를 반드시 요구하지 않는 함수는 컴포넌트 바깥에 선언
- 유틸 함수로 빼는 것과 동일
- 함수도 데이터 흐름의 일부인가?
useCallback, useMemo 을 사용하면, 함수는 명백하게 데이터 흐름에 포함됩니다. 만약 함수의 입력값이 바뀌면 함수 자체가 바뀌고, 만약 그렇지 않다면 같은 함수로 남아있다고 말 할 수 있습니다
- 콜백을 props로 내려보내는 것을 피하는 것이 더 좋다
- 여러개의 콜백 props 보다 dispatch 전달하도록
- 하지만 안티패턴을 자주 사용하고 있다
- 그 예로 useCurring...
react-native-screens
🚨
v3.18.0
이 나와서 드디어 fabric + android 조합을 쓸 수 있을까 기대했다- 문제의 안드로이드 이슈가 해결되어 보였는데
- 아직 여전히 빌드는 실패하고 있다
- 그럼에도 긍적적인!
- 에러 메세지가 바뀌었다
- 👍
Execution failed for task ':app:mergeDebugNativeLibs'. > A failure occurred while executing com.android.build.gradle.internal.tasks.MergeNativeLibsTask$MergeNativeLibsTaskWorkAction > 2 files found with path 'lib/arm64-v8a/libc++_shared.so' from inputs: - /Users/jiggag/Projects/react-native-starter/node_modules/react-native/ReactAndroid/build/intermediates/library_jni/debug/jni/arm64-v8a/libc++_shared.so - /Users/jiggag/.gradle/caches/transforms-3/87b4b130d2c85494c51740eb879d8459/transformed/jetified-react-native-0.70.0-debug/jni/arm64-v8a/libc++_shared.so If you are using jniLibs and CMake IMPORTED targets, see https://developer.android.com/r/tools/jniLibs-vs-imported-targets
- 무언가 패키지 설정을 정리해주어야만 할 것 같다 🤔
React Fiber
- React Fiber 톱아보기
Fiber
- Reconciliation 엔진 재구성
- 애니메이션, 레이아웃, 제스처, 중단 또는 재사용 기능과 같은 영역에 대한 적합성을 높이고 다양한 유형의 업데이트에 우선 순위를 지정
- 렌더링 작업을 여러 덩어리로 나누어 여러 프레임에 분산
Virtual DOM
- Real DOM의 in-memory 표현
React에서는 Virtual DOM이란 UI를 나타내는 객체로 통용되며, React elements와 연관된다.
React는 컴포넌트 트리에 대한 추가 정보를 포함하기 위해 fibers라는 내부 객체를 사용한다.
reconciliation
: 호출되는 렌더 함수에서 화면에 표시되는 요소가 되는 사이에 발생한 단계- Virtual DOM을 reconciliation 과정을 통해 React Elements를 나타낸다
- Real DOM의 in-memory 표현
Reconciliation
- React가 변경해야 할 부분을 결정하기 위해
한 트리를 다른 트리와 비교하는 데 사용하는 알고리즘
이다.- 앱에 업데이트를 반영하기 위해 앱 전체를 리렌더 하는 것은 비효율적이다
- 따라서
Reconciliation
과정을 통해 최적화
- Virtual DOM이 한다고 여겨지는 것의 알고리즘
- 각각 노드 트리 컴포넌트의 type, key를 확인하여 업데이트 요청한다
- 컴포넌트의 type, key 자체가 다르면 diffing 조차 하지 않고 트리를 완전히 교체한다
- 상위 컴포넌트 type, key이 달라지면 트리가 아예 교체되어 버리니 하위 컴포넌트도 리렌더 해야한다
- React가 변경해야 할 부분을 결정하기 위해
Reconciler !== Renderer
Reconciler
은 트리의 어떤 부분이 변경됐는지 계산한다Renderer
은 계산된 정보를 앱을 실제로 업데이트하는 데 사용한다React Core가 제공하는 동일한 Reconciler를 공유하면서 각자 자체적인 Renderer의 사용을 가능하게 한다.
- 지난번에 보았던 React를 Reconciler 엔진을 통해 변환하면 Renderer가 그리는 내용!
그렇다면 Fiber는 왜 나왔을까
- Fiber는 Reconciliation 엔진 재구성한 버전이다
Fiber === Reconciliation가 하던 역할 + 최적화
- 이미
Reconciliation
는 diffing을 통해 업데이트를 진행하고 있는데, 여기서 좀 더 최적화를 진행한다면?
- Fiber는 Reconciliation 엔진 재구성한 버전이다
- 🚨
모든 업데이트를 즉시 적용할 필요는 없다
- 예를 들면, 스크린 전체가 업데이트가 되어야하지만 뷰포트 밖에 있는 것은 좀 더 나중에 업데이트 되어도 괜찮다
- 이런 우선순위(=스케줄링)를 Fiber가 판단하여 최적화하는 것을 목표로 한다
다시 Fiber
- Fiber의 목표는 스케줄링을 통해 리액트 최적화 하는 것
- 하던 일 멈추고 우선순위 높은 일 진행하고 다시 돌아와서 마무리하거나 필요하지 않은 일 정리하기 등
- Fiber의 목표는 스케줄링을 통해 리액트 최적화 하는 것
- 리액트 컴포넌트를 그리는 것은 결국 콜스택에 함수를 호출하는 것인데
- 이 콜스택을 스케줄링을 통해 제어한다면 최적화를 진행할 수 있게 된다
- Fiber가 이 대단한 일을 하는 것
- DOM을 재구성한 Virtual DOM
- Stack을 재구성한 Virtual Stack
- 메모리에 스택을 보관하고 원하는 시점에 실행
Fiber의 구조
type, key
- 컴포넌트 스택과 재조정 시 판단하기 위함
child, sibling
- 재귀적인 트리 구조로 다른 Fiber를 나타낸다
- 여기서 child가 하위 트리라고 생각했는데, 현재 Fiber에 의해 렌더되는 반환값이였다 😱
- 다시 생각해보니 렌더된 반환값이 child 인게 맞구나!
- Fiber가 A컴포넌트를 그렸다면 child는 그 반환값인 B겠당
retur
- Fiber가 반환해야하는 Fiber
- Parent Fiber
pendingProps, memoizedProps
- 함수 실행 시작 시 pendingProps, 끝에서 memoizedProps 설정
- 이 두 값이 같다면 Fiber는 이전 결과를 재사용하여 불필요한 업데이트 방지
pendingWorkPriority
- Fiber가 처리하려는 작업의 우선순위
alternate, output
- alternate=flush이면 output을 화면에 렌더링 하는 것을 의미
- 모든 Fiber가 output을 가지지만 트리 위로 전달되어 리프 노드에서만 생성되고
- Fiber가 호출한 함수의 반환값으로 최종적으로 렌더러가 처리한다
Fabric이 가져올 효과
- React Native Architecture — Old Vs New
- 아래 React를 Reconciler가 해석해서 RN 이 그린다에 이어서
- 그럼 RN이 그리는 방법은?
- JS 스레드에서 브릿지를 통해 비동기로 Native, Main 스레드에서 처리하고 받는데
- 직렬화된 json을 브릿지로 전달하고 해석하고 그리고..
- JS 스레드에서 이미 완료된 이벤트에 대해서도 비동기로 동작하다보니 아직 되돌려받지 못한 경우
- 백화로 그려지는 이슈를 마주하게 될 수 있다
- JS 스레드에서 브릿지를 통해 비동기로 Native, Main 스레드에서 처리하고 받는데
- 따라서 새로운 아키텍쳐인 Turbo module과 Fabric이 이걸 곧장 동기화 한다
Reconciler
- 작년 feconf에서 봤었는데, 다시 톱아보기
- 코드(js)를 엔진(v8)이 해석해서 cpu에게 전달하여 실행
- cpu가 이해하는 어셈블리어는 모른채 js로만 작성
- 리액트에서는?
reconciler
- function, class components, props, state, effect, ref... 등 리액트 문법
- 렌더러와 무관하게
리액트 코드를 해석하고 실행
(= 엔진)
react를 reconciler가 해석해서 호스트 환경(UI 렌더러)에 전달하여 실행
다양한 호스트 환경
에서동일하게 사용
할 수 있는 리액트 문법으로 작성- react-dom, react-native...
- DOM을 직접 조작하지 않고 Virtual DOM을 통하는 이유
- 직접 DOM을 제어하지 않는다는 것이
다양한 호스트 환경
을 고려한 것 - js를 실행하기만 할뿐 특정한 환경을 고려하지 않는다
- 참고
리액트 페키지들은 오직 당신이 리액트 특성들을 사용할수 있도록 해주지만 어떻게 사용될지에 대해서는 아무것도 모른다는 겁니다. 렌더러 페키지들은(리액트-돔, 리액트-네이티브, 등등) 리액트 특성들의 실행을 제공하고 특정-플렛폼의 논리를 제공합니다.
- 직접 DOM을 제어하지 않는다는 것이
- 대수적 효과?
- 리액트가 마주한 문제를 해결하기 위해 사용한 답
- 그리고 얻은 결과: context, suspense 🤔
호출 스택(이 경우, 리액트)의 중간에 있는 함수들을 신경쓸 필요 없이, 혹은 그 함수들을 async나 제너레이터 함수로 강제로 변경할 필요 없이, 호출 스택의 아래쪽에 있는 코드가 윗쪽에 있는 코드에게 뭔가를 전달할 수 있다
FE 테스트
- 테스트 가능한 프런트엔드: 좋은 것, 나쁜 것, 깨지기 쉬운 것
한 코드를 테스트하려고 할 때, 우리는 종종 React 컴포넌트를 렌더링하고 결과를 테스트하는 React Testing Library와 같은 것에서부터 시작하거나, 프로젝트와 잘 작동하도록 Cypress를 구성하느라 정신없이 하다가 잘못된 구성으로 끝나거나 포기하는 경우가 많습니다.
- 단위테스트는 모킹을 많이 하는 특정 환경에 의존하기 때문에 좋지 않다
- ex. 로컬 상태는 A이고 useHook에서 가져오는 것은 B이고 props에서 C를 가져오는 환경에서 이 기능은 D 한다
- 그렇다면 어떤 테스트를 해야할까?
- 최대한 외부 의존성(redux, context)이 없는 컴포넌트로 분리하고 특성에 따라 단위, 통합, E2E 테스트
관심사 분리
를 통해 테스트를 쉽게 진행할 수 있다- 관심사 분리를 하면 단위 테스트 또는 UI 컴포넌트 테스트만 진행하면 되겠지만
- 이런 분리가 쉽지 않은 서로 얽혀있는 경우에는 유일한 테스트 방법은 E2E 테스트로 모든 것을 테스트 하는 것