1. useTransition
블로킹렌더링문제
- 한번 렌더링 연산이 시작되면 멈출 수 없음
- 대형화면 업데이트의 경우 렌더링되는동안 페이지 지연이 발생
useTransition
useTransition 훅은 사용자가 상태를 전환할 때 UI 업데이트를 더 부드럽게 만들기 위해 도입되었다. 이는 UI의 일부가 비동기 작업으로 인해 느려지는 것을 방지하고, 사용자가 인터랙션을 할 때 더 좋은 경험을 제공한다.
- 기능
- 사용자 인터페이스의 느린 업데이트를 처리하기 위해, 긴급하지 않은 업데이트를 우선 순위가 낮은 작업으로 처리한다.
- 사용자는 여전히 긴급한 업데이트(예: 버튼 클릭)에 빠르게 반응하는 UI를 경험한다.
- 사용법
- import { useTransition, useState } from 'react'; function MyComponent() { const [isPending, startTransition] = useTransition(); const [value, setValue] = useState(''); const handleChange = (e) => { startTransition(() => { setValue(e.target.value); }); }; return ( <div> <input type="text" onChange={handleChange} /> {isPending ? "Loading..." : <p>{value}</p>} </div> ); }
디바운스 vs useTransition
디바운스와 useTransition은 사용자 인터페이스에서 성능을 최적화하기 위해 사용되는 두 가지 접근 방식이다. 그러나 두 기술은 서로 다른 문제를 해결하고 다른 방식으로 동작한다.
디바운스 (Debounce)
디바운스는 특정 이벤트가 반복해서 발생하는 것을 방지하고, 마지막 이벤트 발생 후 일정 시간이 지난 후에만 실제 작업을 수행하는 기법이다. 주로 사용되는 사례는 사용자 입력이 빠르게 반복될 때 불필요한 작업을 줄이는 것이다.
- 기능
- 특정 시간 동안 이벤트가 발생하지 않을 때까지 작업을 지연시킨다.
- API 호출, 검색 필터링, 창 크기 조정 등에서 과도한 호출을 줄인다.
- 사용법
- function debounce(func, delay) { let timeoutId; return function(...args) { clearTimeout(timeoutId); timeoutId = setTimeout(() => { func.apply(this, args); }, delay); }; } const handleInputChange = debounce((event) => { console.log(event.target.value); // 실제 작업을 수행한다. }, 300); <input type="text" onChange={handleInputChange} />;
쓰로틀 (Throttle)
쓰로틀은 일정 시간 간격으로 이벤트를 실행하여 이벤트 발생 빈도를 제한하는 방법이다. 예를 들어, 사용자가 빠르게 스크롤할 때 성능을 유지하는 데 유용하다.
- 기능
- 일정 시간 동안 여러 번 발생한 이벤트를 하나의 이벤트로 묶어서 실행한다.
- 스크롤, 창 크기 조정 등 자주 발생하는 이벤트를 최적화한다.
- 사용법
- function throttle(func, limit) { let lastFunc; let lastRan; return function(...args) { if (!lastRan) { func.apply(this, args); lastRan = Date.now(); } else { clearTimeout(lastFunc); lastFunc = setTimeout(() => { if (Date.now() - lastRan >= limit) { func.apply(this, args); lastRan = Date.now(); } }, limit - (Date.now() - lastRan)); } }; } const handleScroll = throttle(() => { console.log('Scroll event'); }, 200); window.addEventListener('scroll', handleScroll);
비교
항목 디바운스 (Debounce) 쓰로틀 (Throttle) useTransition
목적 | 이벤트 과다 호출 방지 | 이벤트 호출 빈도 제한 | UI 상태 전환 시 부드러운 업데이트 |
주요 사례 | 사용자 입력 필터링, API 호출 지연 등 | 스크롤, 창 크기 조정 등 빈번한 이벤트 최적화 | 비동기 작업, 긴급하지 않은 상태 업데이트 |
지연 방식 | 마지막 이벤트 발생 후 일정 시간 지연 | 일정 시간 간격으로 이벤트 실행 | 긴급하지 않은 상태 업데이트를 낮은 우선 순위로 처리 |
장점 | 불필요한 작업 감소로 성능 최적화 | 이벤트 과부하 방지로 성능 최적화 | 사용자 인터페이스의 반응성을 유지하면서 부드러운 상태 전환 |
단점 | 사용자가 지연을 느낄 수 있음 | 일부 이벤트가 무시될 수 있음 | 초기 설정 및 이해에 시간이 걸릴 수 있음 |
React 전용 여부 | 일반적인 자바스크립트 기법 | 일반적인 자바스크립트 기법 | React 18 이상에서 사용 가능 |
결론
디바운스와 쓰로틀은 자주 발생하는 이벤트를 제한하여 성능을 최적화하는 데 사용되며, 각각 다른 방식으로 동작한다. 디바운스는 마지막 이벤트 후 일정 시간 동안 지연시키고, 쓰로틀은 일정 시간 간격으로 이벤트를 실행한다. useTransition은 React 컴포넌트에서 긴급하지 않은 상태 업데이트를 부드럽게 처리하여 UI의 반응성을 유지하는 데 중점을 둔다. 각각의 기술은 특정 상황에서 성능을 최적화하는 데 유용하며, 사용 목적에 맞게 선택하는 것이 중요하다
동시성
2개 이상의 작업을 나눠서 동시애 실행되는것처럼 프로그램을 구조화하는 방법
2. Suspense and SSR (서버 사이드 렌더링)
Client Side Rendering
CSR은 클라이언트 측에서 렌더링을 수행하는 방식으로, 초기 페이지 로드 시 최소한의 HTML과 자바스크립트 파일만 서버에서 가져온 후, 클라이언트에서 나머지 렌더링을 수행한다.
- 장점
- 초기 로딩 속도가 빠름: 첫 요청에서 최소한의 데이터만 가져오기 때문에 서버 응답이 빠름.
- 인터랙티브한 사용자 경험: 자바스크립트가 로드된 후 빠른 UI 업데이트 가능.
- 적은 서버 부하: 대부분의 렌더링 작업이 클라이언트에서 수행되기 때문에 서버 리소스가 절약됨.
- 단점
- SEO 문제: 검색 엔진이 자바스크립트를 실행하지 못하면 페이지 내용이 인덱싱되지 않을 수 있음.
- 초기 로딩 지연: 자바스크립트 파일이 크면 초기 로딩 시간이 길어질 수 있음.
Server Side Rendering
SSR은 서버에서 렌더링을 수행하여 완전한 HTML 페이지를 클라이언트로 전송하는 방식으로, 초기 페이지 로드 시 전체 페이지가 렌더링된 상태로 제공된다.
- SSR과정
- 서버 렌더링: 서버는 완전히 렌더링된 HTML을 클라이언트로 보낸다. 이 HTML에는 초기 데이터가 포함되어 있어, 사용자가 즉시 콘텐츠를 볼 수 있다.
- 클라이언트 Hydration: 클라이언트에서 React는 서버에서 받은 HTML을 사용해 초기 렌더링을 수행한다. 이때, React는 DOM 요소를 변경하지 않고, 기존의 HTML 구조에 이벤트 리스너를 연결한다.
- 동적 상호작용: Hydration이 완료되면, 클라이언트 측 React는 기존의 HTML을 사용해 동적인 상호작용을 가능하게 한다.
- 장점
- SEO 최적화: 검색 엔진 크롤러가 완전한 HTML을 가져가기 때문에 페이지 인덱싱이 용이함.
- 빠른 초기 로드: 첫 요청에서 완전한 HTML을 제공하므로, 사용자에게 빠른 첫 화면을 제공할 수 있음.
- 단점
- 높은 서버 부하: 모든 렌더링 작업이 서버에서 수행되므로 서버 리소스가 더 많이 필요함.
- 느린 인터랙티브성: 초기 로딩 후 클라이언트 측 자바스크립트가 다시 로드되고 실행되기 전까지 인터랙티브하지 않음.
- Hydration
- SSR(Server-Side Rendering)에서 Hydration은 서버에서 렌더링된 HTML을 클라이언트 측에서 다시 활성화하는 과정이다. 이 과정은 React 애플리케이션이 초기 로드 시 빠르게 보이고 동작하게 하기 위해 중요하다.
- 컴포넌트를 렌더링하고 이벤트 핸들러를 연결하는 과정(HTML에 인터렉션동작)
Suspense와 SSR
React 18에서는 Suspense와 SSR의 통합이 크게 개선되어, 데이터 로딩과 렌더링을 동시에 처리할 수 있게 되었다.
기존 SSR의 문제점
- 모든 data fetch가 끝나야 어떤것이라도 보여줄 수 있다.
- 모든 자바스크립트 코드를 로딩하기 전에는 Hydration단계로 넘어갈 수 없다.
- 앱이 상호작용할 수 있는 상태가 되려면 앱 전체가 Hydration이 완료되어야 한다
즉, 각 단계가 완료되어야 다른단계로 넘어갈 수 있었음.
해결
페이지의 각 부분을 suspense로 묶어 Data fetch - Redering - Load js - Hydrate 단계를 따로 처리한다.
기능
- 데이터가 로드될 때까지 UI의 특정 부분을 지연시키고, 로딩 상태를 표시할 수 있다.
- 서버 사이드 렌더링에서 Suspense를 사용하여 데이터를 미리 로드하고, 전체 페이지가 준비되기 전에 부분적으로 렌더링할 수 있다.
- 사용법
- import React, { Suspense } from 'react'; const OtherComponent = React.lazy(() => import('./OtherComponent')); function MyComponent() { return ( <div> <Suspense fallback={<div>Loading...</div>}> <OtherComponent /> </Suspense> </div> ); }
3. Automatic Batching
Batching
여러개의 state업데이트를 하나의 renderer가 발생하도록 그룹화
일반적인 Batching React 17에서는 기본적으로 이벤트 핸들러 내부에서 발생하는 상태 업데이트를 자동으로 배치 처리한다. 즉, 이벤트 핸들러 내에서 여러 개의 상태 업데이트가 있을 경우, 이들은 한 번의 렌더링으로 묶여 처리된다. 그러나 비동기 작업, 특히 setTimeout과 같은 함수 내부에서 발생하는 상태 업데이트는 React 17에서는 자동으로 배치되지 않는다.
import { useState } from 'react';
function MyComponent() {
const [count, setCount] = useState(0);
const [text, setText] = useState('');
const handleClick = () => {
setTimeout(() => {
setCount(count + 1);
setText('Clicked!');
// React 17에서는 이 두 상태 업데이트가 배치되지 않음
}, 1000);
};
return (
<div>
<button onClick={handleClick}>Click me</button>
<p>{count}</p>
<p>{text}</p>
</div>
);
}
위 예시에서 setTimeout 내부의 setCount와 setText는 React 17에서는 별도의 렌더링을 트리거한다. 따라서 두 번의 상태 업데이트가 각각 렌더링을 발생시켜, 두 번의 렌더링이 발생하게 된다.
Automatic Batching
React 18에서는 자동 배칭이 도입되어 여러 상태 업데이트를 하나의 렌더링으로 묶어서 처리할 수 있게 되었다. 이는 성능을 크게 향상시키고 불필요한 렌더링을 줄여준다.
- 자동 배치: React 18에서는 이벤트 핸들러, 비동기 함수, setTimeout, Promise 등의 여러 상황에서 상태 업데이트가 자동으로 배치된다. 이는 여러 상태 업데이트가 단일 렌더링 사이클로 합쳐지도록 하여 렌더링 성능을 향상시킨다
- 성능 개선: 자동 배치는 불필요한 렌더링을 줄여서 렌더링 성능을 개선한다. 예를 들어, 동일한 이벤트 핸들러에서 여러 상태 업데이트가 발생하면, React는 이를 묶어서 한 번의 렌더링으로 처리한다.
- 일관성 유지: 상태 업데이트가 자동으로 배치되기 때문에, 사용자 인터페이스(UI)의 일관성을 유지하면서도 더 효율적인 렌더링을 구현할 수 있다.
- 사전 준비가 필요 없음: 자동 배치는 별도의 설정이나 사전 준비가 필요 없이 기본적으로 활성화된다. React 18을 사용하면 자동 배칭을 자동으로 이용할 수 있다.
flushSync
flushSync는 React의 업데이트를 즉시 렌더링하도록 강제하는 메서드이다. 이 메서드는 특정 상황에서 상태 업데이트가 즉시 반영되도록 보장할 수 있다. 일반적으로 비동기 작업이나 다른 렌더링 작업이 진행 중일 때 즉시 UI를 업데이트할 필요가 있는 경우 사용된다.(batching에 묶이지 않음)
- 사용법:
- import { useState } from 'react'; import { flushSync } from 'react-dom'; function MyComponent() { const [count, setCount] = useState(0); const [text, setText] = useState(''); const handleClick = () => { setTimeout(() => { // flushSync를 사용하여 즉시 상태 업데이트가 반영되도록 강제 flushSync(() =. { setCount(count + 1); }); setText('Clicked!'); // setText는 자동 배치에 의해 배치될 수 있다. }, 1000); }; return ( <div> <button onClick={handleClick}>Click me</button> <p>{count}</p> <p>{text}</p> </div> ); }
'Frontend > React' 카테고리의 다른 글
React Props: setState와 함수 시그니처의 차이점 (0) | 2024.11.19 |
---|---|
[React] 데이터 fetch와 state 관리는 페이지 vs 컴포넌트 ?? (0) | 2024.09.13 |
MobX 를 이용한 간단한 앱 만들기 (0) | 2024.01.17 |
[React] 코드 스플리팅(React.lazy와 Suspense, 그리고 Loadable Components) (1) | 2024.01.10 |
[React] 리액트 미들웨어 redux-saga (1) | 2023.12.28 |