-
[React] 심화경남_ABC_FE/Section_3 2023. 12. 12. 15:39
Virtual DOM
React는 UI의 상태를 추적하고 변화가 일어난 요소들을 빠르게 업데이트할 수 있도록 Virtual DOM이라는 가상의 DOM 객체를 활용한다.
이는 실제 DOM의 사본 같은 개념으로, React는 실제 DOM 객체에 접근하여 조작하는 대신 이 가상의 DOM 객체에 접근하여 변화 전과 변화 후를 비교하고 바뀐 부분을 적용한다.
Virtual DOM이 나오게 된 배경
Real DOM (DOM)
DOM은 브라우저가 HTML 문서를 조작할 수 있도록 트리 구조화한 객체 모델이다.
-> 바뀐 부분만 비교해서 그 부분만 렌더링 할 수는 없을까?
-> Virtual DOM 등장
Virtual DOM이란?
JavaScript 객체로 이루어진 가상의 DOM 트리를 사용하여, 실제 DOM 조작을 최소화하고 성능을 최적화하는 기술
가상 DOM은 실제 DOM과 동기화되어, 상태가 변경될 때마다 가상 DOM을 새로 생성하여 이전 상태와 비교한다. 그리고 변경이 필요한 부분만 실제 DOM에 반영되어 업데이트하므로, 전체 UI를 다시 그리지 않아도 된다.
실제 DOM과 마찬가지로 가상 DOM 또한 HTML 문서 객체를 기반으로 한다.
가상 DOM은 리액트에서 컴포넌트의 상태나 속성이 변경될 때마다 새로 생성되며, 리액트는 이전 가상 DOM과 새로운 가상 DOM을 비교하여 변경된 부분만 실제 DOM에 반영한다.
Virtual DOM은 빠르다?
가상 DOM은 일반적으로 실제 DOM을 조작하는 것보다 빠르다는 것이 맞지만, 모든 경우에 그렇지는 않다. 때로는 직접 DOM을 조작하는 것이 더 빠를 수 있다.
React Hooks
useMemo
useMemo는 특정 값을 재사용하고자 할 때 사용하는 Hook이다.
/* useMemo를 사용하기 전에는 꼭 import해서 불러와야 합니다. */ import { useMemo } from "react"; function Calculator({value}){ const result = useMemo(() => calculate(value), [value]); return <> <div> {result} </div> </>; }이런 식으로 useMemo를 호출하여 calculate를 감싸주면, 이전에 구축된 렌더링과 새로이 구축되는 렌더링을 비교해 value 값이 동일할 경우에는 이전 렌더링의 value값을 그대로 재활용할 수 있게 된다.
useCallback
useCallback 또한 useMemo와 마찬가지로 메모이제이션 기법을 이용한 Hook이다. useMemo는 값의 재사용을 위해 사용하는 Hook이라면, useCallback은 함수의 재사용을 위해 사용하는 Hook이다.
useCallback Hook을 사용하면 그 함수가 의존하는 값들이 바뀌지 않는 한 기존 함수를 계속해서 반환한다.
/* useCallback를 사용하기 전에는 꼭 import해서 불러와야 합니다. */ import React, { useCallback } from "react"; function Calculator({x, y}){ const add = useCallback(() => x + y, [x, y]); return <> <div> {add()} </div> </>; }-> 자식 컴포넌트의 props로 함수를 전달해줄 때 이 useCallback을 사용하기가 좋다.
useCallback과 참조 동등성
useCallback은 참조 동등성에 의존한다.
객체는 메모리에 저장할 때 값을 저장하는 게 아니라 값의 주소를 저장하기 때문에, 반환하는 값이 같을지라도 일치연산자로 비교했을 때 false가 출력된다.
function doubleFactory(){ return (a) => 2 * a; } const double1 = doubleFactory(); const double2 = doubleFactory(); double1(8); // 16 double2(8); // 16 double1 === double2; // false double1 === double1; // true-> React 컴포넌트 함수 내에서 다른 함수의 인자로 넘기거나 자식 컴포넌트의 prop으로 넘길 때 예상치 못한 성능 문제를 막을 수 있다.
Custom Hooks
- 상태관리 로직의 재활용이 가능하고
- 클래스 컴포넌트보다 적은 양의 코드로 동일한 로직을 구현할 수 있으며
- 함수형으로 작성하기 때문에 보다 명료하다는 장점이 있다.
Custom Hook을 정의할 때 필요한 일종의 규칙
- Custom Hook을 정의할 때는 함수 이름 앞에 use를 붙이는 것이 규칙이다.
- 대개의 경우 프로젝트 내의 hooks 디렉토리에 Custom Hook을 위치시킨다.
- Custom Hook으로 만들 때 함수는 조건부 함수가 아니어야 한다. 즉 return 하는 값은 조건부여서는 안된다.
일반 함수 내부에서는 React 내장 Hook을 불러 사용할 수 없지만 Custom Hook에서는 가능하다.
코드 분할
번들이 거대해지는 것을 방지하기 위한 좋은 해결 방법은 번들을 물리적으로 나누는 것이다.
코드 분할은 런타임 시 여러 번들을 동적으로 만들고 불러오는 것으로, Webpack, Rollup과 같은 번들러가 지원하는 기능이다.
따라서 코드 분할을 하게 되면 지금 당장 필요한 코드가 아니라면 따로 분리를 시키고, 나중에 필요할 때 불러와서 사용할 수 있다.
이를 통하여 대규모 프로젝트의 앱인 경우에도 페이지의 로딩 속도를 개선할 수 있게 된다.
번들 분할 혹은 줄이는 법
서드파티 라이브러리는 사용자에게 다양한 메서드를 제공하기 때문에 코드의 양이 많고, 번들링 시 많은 공간을 차지한다. 따라서 사용 중인 라이브러리의 전부를 불러와서 사용하는 것보다 따로따로 불러와서 사용할 수 있다면 많은 공간을 차지하지 않을 수 있게 된다.
필요한 것 한 두 개만 가져다 쓰는 식으로 개발하는 것이 훨씬 좋다.
/* 이렇게 lodash 라이브러리를 전체를 불러와서 그 안에 들은 메서드를 꺼내 쓰는 것은 비효율적입니다.*/ import _ from 'lodash'; ... _.find([]); /* 이렇게 lodash의 메서드 중 하나를 불러와 쓰는 것이 앱의 성능에 더 좋습니다.*/ import find from 'lodash/find'; find([]);React에서의 코드 분할
React에서 코드 분할 하는 방법은 dynamic import를 사용하는 것이다. 코드 파일의 가장 최상위에서 import 지시자를 사용해 사용하고자 하는 라이브러리 및 파일을 불러오는 방법은 static import라고 한다.
form.addEventListener("submit", e => { e.preventDefault(); /* 동적 불러오기는 이런 식으로 코드의 중간에 불러올 수 있게 됩니다. */ import('library.moduleA') .then(module => module.default) .then(someFunction()) .catch(handleError()); }); const someFunction = () => { /* moduleA를 여기서 사용합니다. */ }dynamic import는 then 함수를 사용해 필요한 코드만 가져온다. 이 방식을 사용하면 번들링 시 분할된 코드를 지연 로딩시키거나 요청 시에 로딩할 수 있다.
이 dynamic import는 React.lazy와 함께 사용할 수 있다.
React.lazy와 Suspense
React.lazy()
React는 React.lazy를 통해 컴포넌트를 동적으로 import를 할 수 있기 때문에 이를 사용하면 초기 렌더링 지연시간을 어느 정도 줄일 수 있게 된다.
import Component from './Component'; /* React.lazy로 dynamic import를 감쌉니다. */ const Component = React.lazy(() => import('./Component'));이 React.lazy로 감싼 컴포넌트는 단독으로 쓰일 수는 없고, React.suspense 컴포넌트의 하위에서 렌더링을 해야 한다.
React.Suspense
Suspense 컴포넌트의 fallback prop은 컴포넌트가 로드될 때까지 기다리는 동안 로딩 화면으로 보여줄 React 엘리먼트를 받아들인다. Suspense 컴포넌트 하나로 여러 개의 lazy 컴포넌트를 보여줄 수도 있다.
/* suspense 기능을 사용하기 위해서는 import 해와야 합니다. */ import { Suspense } from 'react'; const OtherComponent = React.lazy(() => import('./OtherComponent')); const AnotherComponent = React.lazy(() => import('./AnotherComponent')); function MyComponent() { return ( <div> {/* 이런 식으로 React.lazy로 감싼 컴포넌트를 Suspense 컴포넌트의 하위에 렌더링합니다. */} <Suspense fallback={<div>Loading...</div>}> {/* Suspense 컴포넌트 하위에 여러 개의 lazy 컴포넌트를 렌더링시킬 수 있습니다. */} <OtherComponent /> <AnotherComponent /> </Suspense> </div> ); }React.lazy와 Suspense의 적용
앱에 코드 분할을 도입할 곳을 결정하는 것은 사실 까다롭기 때문에, 중간에 적용시키는 것보다는 웹 페이지를 불러오고 진입하는 단계인 Route에 이 두 기능을 적용시키는 것이 좋다.
초기 렌더링 시간이 줄어드는 분명한 장점이 있으나 페이지를 이동하는 과정마다 로딩 화면이 보이기 때문에 서비스에 따라서 적용 여부를 결정해야 한다.
import { Suspense, lazy } from 'react'; import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; const Home = lazy(() => import('./routes/Home')); const About = lazy(() => import('./routes/About')); const App = () => ( <Router> <Suspense fallback={<div>Loading...</div>}> <Routes> <Route path="/" element={<Home />} /> <Route path="/about" element={<About />} /> </Routes> </Suspense> </Router> );'경남_ABC_FE > Section_3' 카테고리의 다른 글
Github와 Git Flow (0) 2023.12.15 [Backend] 인증/보안 (0) 2023.12.06 [React] 상태 관리 (1) 2023.12.04 [React] Custom Component (0) 2023.11.30 [사용자 친화 웹] UI/UX (1) 2023.11.28