DOM을 조작할 때마다 이런 과정이 반복되는 것은 비용
이 많이 든다.
이 문제를 해결하기 위해서 virtual DOM
이 등장한다.
브라우저 렌더링을 과정을 계속해서 반복하는 것이 아니라 변화들을 virtual DOM에 반영한 후, 변화된 부분만을 모아서 실제 DOM에 적용하여 한 번만 렌더링 하도록 함으로써 성능을 최적화 합니다.
createElement
등으로 자바스크립트 객체로 변환한다.type = 태그이름
, props = 하위노드, 각종 속성 포함
이 담겨있다.render()
함수를 호출하면 실제 DOM요소가 된다.리액트는 virtual Dom 이전과, virtual Dom 이후를 가지고 있다.
이 두 트리의 스냅샷을 비교하여 변화된 부분만을 감지한 후에 변경된 부분만을 실제 DOM에 적용한다.
babel에 의해 변환된 자바스크립트에는 type, props가 존재한다.
만약 type === type일 때 props의 속성들을 비교해 변환된 속성만 업데이트 시켜준다.
만약 type !== type일 때 리액트는 이전 트리를 삭제하고 완전히 새로운 트리를 생성한다.
리액트에서 재조정과정에서 이전 virtual Dom과 새로운 virtual Dom을 비교한다.
자식 노드가 추가되었을 때 key값을 통해서 변경여부를 확인하는데 이 때 유니크한 값이 아닌 index로 하게 되면 제대로 동작하지 않을 수 있다.
ES5+ 코드를 자바스크립의 하위 호환 버전으로 변환하여 오래된 브라우저에서 실행하도록 변환하는 컴파일러입니다. 더 나아가 JSX까지 변환해준다.
- state가 바뀌었을 때
- props가 바뀌었을 때
부모 컴포넌트가 리렌더링 될 때마다 함수는 재생성이 되고 이전 함수와, 현재 함수가 서로 다른 참조 타입을 가지기 때문에 조치를 취하지 않는 이상 랜더링이 발생한다.
이 때 리액트에서 제공하는useCallback
hook을 사용하면 함수를 메모리제이션 해준다.
리액트 렌더링은
render phase
와commit phase
로 구성되어 있습니다.
render phase
에는 컴포넌트를 호출하여 React.Element를 반환하고 새로운virtual Dom
을 생성해줍니다. 그 후 재조정 과정을 거친 후 변경사항을 체크합니다.
commit phase
는 변경 사항을real Dom
에 반영합니다.
=> 즉render phase
는 실행되지만 usecallback을 활용해서 props 값을 이전과 같게 유지해주었기 때문에 commit phase는 실행되지 않습니다.
React.Memo를 사용하면 됩니다.
React.Memo란 전달받은 props가 이전 props와 비교했을 때 같으면, 컴포넌트의 리랜더링을 막아주고, 마지막으로 렌더링 된 결과를 재사용하는 고차 컴포넌트입니다.
=> 즉 컴포넌트가render phase
에 진입하기 전 props의 이전 값과 현재 값을 비교를 하는데요. 같으면 랜더링이 진행되지 않고 이전 렌더링 결과를 리턴하게 됩니다.
함수와 마찬가지로 객체는 참조 타입이기 때문에 렌더링 될 때마다 매번 다른 참조값을 가진 props를 전달하게 됩니다. 그래서 자식 컴포넌트들도 렌더링 되게 됩니다.
이 때useMemo
hook을 사용하면 값에 대한 메모리제이션을 할 수 있습니다.
이러한 것들도 코드이고 내부적으로 로직들이 실행이 되기 비용이 생깁니다. 매번 바뀔 수 밖에 없는 props에 useMemo를 쓰던가, 매번 렌더링이 되는 컴포넌트에 useCallback을 하게 되면 오히려 성능이 저하될 수 있습니다.
=> 따라서 컴포넌트를 props로 주입하는 코드를 작성하게 된다면 됩니다.
useState는 true/false나 string 같은 단순한 상태값일 때 사용
useReducer는 입력 폼이라던지, 복잡한 상태관리를 reducer로 분리하면 서로 의존적인 상태들을 깔끔하게 관리할 수 있다.
use
로 시작하는 사용자 정의 함수로 여러 컴포넌트에서 재사용 가능한 상태 로직을 말합니다. 함수처럼 재사용 가능한 비즈니스 로직을 분리하여 다른 컴포넌트에서 재사용할 수 있고, 재사용하지 않더라도 비즈니스 로직을 분리하여 코드 가독성을 향상할 수 있습니다.
useEffect는 의존성 배열이 있는데 의존성 배열 값이 변하면 사이드 이펙트로 실행해주는 훅입니다. 또한 컴포넌트가 언마운트 될 때 리소스를 정리할 경우 사용합니다.
단점으로는 의존성 배열을 제대로 사용하지 않으면 불필요한 렌더링이 발생할 수 있습니다.
리액트와 같은 SPA로 개발된 프로젝트는 빌드를 통해 배포됩니다.
빌드된 결과물을 보면, 하나의 큰 JavaScript 파일로 모든 코드가 번들링된 것을 확인할 수 있습니다.
이 경우, 초기 로딩 시 모든 페이지의 코드가 한 번에 불러와지므로, 첫 번째 로딩 시간이 길어지는 문제가 발생할 수 있습니다.
이를 해결하기 위해, 필요한 부분만 동적으로 로드하는 기술을 활용하여 성능을 최적화할 수 있습니다.
React.lazy와, suspense를 사용하면 됩니다.
React.lazy는 동적으로 불러올 컴포넌트를 import하면 되고
React.suspense에서 React.lazy가 적용된 컴포넌트를 렌더링하면 됩니다.
Context API를 사용하게 되면 props 깊이가 깊어지는 props drilling 문제를 해결할 수 있습니다.