웹에서 무한 스크롤 리스트를 제공하는 서비스들이 많은데요. 리스트 DOM의 복잡도에 따라 목록 스크롤이 늘어날수록 서비스가 느려질 수 있기 때문에 오늘 소개하는 방법을 적절히 사용하면 서비스 성능을 개선할 수 있습니다.
애플리케이션에서 긴 목록(수백 또는 수천행)을 렌더링하는 경우 “windowing” 이라는 가상화 기법을 사용하여 최적화 할 수 있습니다. 이 기법은 리엑트 공식 사이트에서 추천하고 있으며, 화면에 실제로 보여지는 부분만 DOM으로 렌더링하도록 연산하여 시스템 부하와 과도하게 낭비되는 리소스를 방지 하여 더 나은 서비스를 제공할 수 있습니다.
IntersectionObserver API는 타겟 엘리멘트가 화면(viewport)에 보여지고 있는지 관찰하는 API입니다.
크롬 51버전부터 사용 가능하며, 현재 대부분의 모던 브라우저에서 지원합니다.
IntersectionObserver 이전엔 화면에 보이는 요소를 감지하려면 Scroll 이벤트를 사용하여 스크롤의 좌표와 화면의 크기를 더해서 관찰하려는 요소의 offset 좌표가 포함되는지를 계산해야 하는 번거로움뿐만 아니라 반복적인 스크롤 연산 처리 과정의 성능 이슈가 따릅니다. 그래서 아래와 같은 상황에 이 API를 활용하는 것을 권장합니다.
어떻게 활용하면 좋을까요?
Image Lasy loading 구현할 때
Content Lasy loading 구현할 때
Infinite scrolling 구현할 때
화면에 보이는 요소 에니메이션 처리할 때
주요 API 알아보기
구문
const observer = new IntersectionObserver(callback, options);
메서드
observe, unobserve, disconnect 메서드를 주로 사용하게 된다.
observe(targetElement): 타겟 엘리먼트에 대한 관찰을 시작할 때 사용합니다.
unobserve(targetElement): 타겟 엘리먼트에 대한 관찰을 멈출 때 사용합니다.
disconnect(): 다수의 엘리먼트를 관찰하고 있을 때, 이에 대한 모든 관찰을 멈추고 싶을 때 사용합니다.
번들링은 훌륭하지만 앱이 커지면 번들도 커져서 로드 속도가 느려집니다. 프로젝트 초기라면 괜찮습니다. 하지만 프로젝트 규모가 커지고 최적화가 필요한 단계에서 Code Splitting을 한다면 서비스 퍼포먼스가 좋아지기 때문에 사용자들에게 보다 나은 사용자 경험을 줄 수 있습니다.
방법 #1. React.lazy
React.lazy 함수를 사용하면 동적 import를 사용해서 컴포넌트를 렌더링 할 수 있습니다.
(React.lazy는 React 16.6버전 부터 지원합니다.)
import 하는 페이지 내부에 사용중인 컴포넌트가 사용하지 않는 다른 컴포넌트와 index.js에 export를 편의상 묶어 사용하는 경우가 있습니다. 이때 컴포넌트 import시 객체 디스트럭쳐링을 사용할 경우 내부적으로 사용하지 않는 컴포넌트도 함께 로드되기 때문에 코드 분할 시 큰 효과를 보지 못합니다. 그렇기 때문에 실사용 컴포넌트만 개별 임포트 하는것이 중요합니다.
// 예로, PageA를 코드 분할하려고 합니다.
const PageA = React.lazy(() => import("./PageA"));
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<PageA />
</Suspense>
</div>
);
}
// PageA.js 에선 Button 컴포넌트를 사용하려고 import하고 있습니다.
// X 사용하지 않는 다른 컴포넌트도 함께 로드됩니다.
import { Button } from "./components";
// O 개별 컴포넌트로 로드해야 불필요한 컴포넌트가 불려 번들 사이즈가 커지는것을 방지할 수 있어요.
import Button from "./components/Button";
function PageA() {
return (
<div>
<h1>PageA</h1>
<Button>OK</Button>
</div>
);
}
// components/index.js
export { default as Loader } from "./common/Loader";
export { default as Button } from "./common/OtherSetting";
export { default as InsideSearch } from "./common/InsideSearch";
방법 #2. Loadable Components
React.lazy와 Suspense는 서버 사이드 렌더링을 지원하지 않습니다. 코드 분할을 클라이언트, 서버사이드 모두 가능하도록 만들고 싶다면 Loadable Components를 사용하면됩니다. (React 공식 문서에서 권장하는 방법)
React.lazy와 비교
Library
Suspense
SSR
Library splitting
import(./${value})
React.lazy
✅
❌
❌
❌
@loadable/component
✅
✅
✅
✅
Install
npm install @loadable/component
# or use yarn
yarn add @loadable/component
React-loadable 모듈은 React 공식 문서에서도 오랫동안 권장하던 방법이었습니다. 그러나 현재는 더 이상 유지 관리되지 않으며 Webpack v4 + 및 Babel v7 +와 호환되지 않는 부분들이 있습니다.
그래서 React.lazy 또는 Loadable components로 변경하는것을 추천합니다.
WebRTC (Web Real-Time Communication)는 웹 브라우저 간에 플러그인 설치 없이 스트림과 데이터를 상호 통신할 수 있도록 설계된 JavaScript API입니다.
WebRTC는 구글이 오픈소스화한 프로젝트에서 기원하였으며, 그 뒤로 국제 인터넷 표준화 기구(IETF)가 프로토콜 표준화 작업을, W3C가 API 정의를 진행하였으며, 음성 통화, 영상 통화, P2P 파일 공유 등으로 활용 될 수 있습니다.
WebRTC 지원 브라우저
최신 버전의 주요 브라우저들이 지원을 하고있습니다.
하지만 경험상 Edge브라우저는 개발과정에 디버깅이 힘들고 타 브라우저에 비해 성능도 떨어집니다.
Edge 크로미움 버전이 추후 나온다니 좋은 성능을 보여주기를 기대합니다.
경험상 크롬에서 먼저 개발하고 어느정도 안정화가 되면 브라우저를 확장해 나가는것을 추천합니다.
WebRTC 주요 API
GetUserMedia
사용자의 카메라와 마이크 접근을 담당
GetDisplayMedia
화면공유를 위한 접근을 담당
RTCPeerConnection
Peer간의 연결을 위한 인스턴스를 생성하고, 연결 후 스트림 전송에 사용 (생성시 STUN 서버 요청)
RTCDataChannel
Peer간의 Data를 주고 받을 수 있는 Tunnel API (WebSocket과 유사하지만, P2P라 속도가 보다 빠름)
WebRTC P2P기반 NAT 및 방화벽 통과 기법
STUN (Session Traversal Utilities for NAT) 서버
사내망 환경에 NAT를 통해 공인IP, Port를 알아내는 역할을 함
TURN (Traversal Using Relays around NAT) 서버
P2P 연결이 안되는 환경일때 트래픽을 중계하는데 사용함
ICE (Interactive Connectivity Establishment)
P2P 간 다이렉트로 통신을 위해 STUN, TURN 등의 기술을 종합 활용하여 라우팅 경로를 찾아내는 기술로 UDP hole punching (P2P간 공인IP가 아니더라도 최대한 연결 가능하도록 하는 기법)을 지원.
WebRTC P2P 연결 절차
위 과정은 아래의 webrtc-internals 화면을 열어서 함께 보는것이 이해하는데 도움이 됩니다.
WebRTC Connection Step 확인
크롬 주소창에 chrome://webrtc-internals 입력하면 WebRTC 연결 흐름과 상태를 볼 수 있습니다.
실제 개발을 할 때 개발자 콘솔창을 보는 시간 만큼 자주 보게되는 화면입니다.