원문 : https://philippspiess.com/scheduling-in-react/
현재는 아래 방법과 다르게 suspense / useTransition을 사용하는 방법으로 변경되었습니다만,
개념을 이해하는데는 아주 좋은 리소스라 생각해서 번역 및 정리하였습니다.
아래의 컨셉을 실제로 프로젝트에 적용하는 방식에 대해서는 아래 두 게시물을 참고해 주세요
https://tech.kakaopay.com/post/react-query-2/
https://academind.com/tutorials/react-usetransition-vs-usedeferredvalue
본문 시작
TLDR
개인적으로 읽고 해석을 덧붙인 내용입니다.
- Immediate : 동기적으로 우선적으로 실행해야 하는 작업입니다.
- 디폴트인데 스케쥴링을 사용하려면 useTransition을 사용해야겠죠?
- UserBlocking (250ms timeout) 사용자 상호작용의 결과로 실행되어야 하는 작업(예: 버튼 클릭).
- 컨커런트 모드의 인터랙션 결과 렌더링
- Normal (5s timeout) 즉각적으로 느껴질 필요가 없는 업데이트의 경우.
- 컨커런트 모드의 일반적인 컴포넌트 렌더링
- Low (10s timeout) 연기할 수 있지만 결국에는 완료해야 하는 작업(예: 분석 알림)
- 여기서부터 비렌더링 관심사.
- Idle (no timeout) 전혀 실행할 필요가 없는 작업(예: 숨겨진 오프스크린 콘텐츠).
- 아예 결과를 렌더링에 반영할 필요도 없음
Scheduling in User Interfaces
- 더 많이 입력할수록 아래 차트가 더 자세히 표시됩니다.
- 두 업데이트(입력 요소 및 차트)가 동시에 실행되기 때문에 브라우저는 너무 많은 계산을 수행하여 일부 프레임을 삭제해야 합니다.
- 이로 인해 눈에 띄는 지연과 나쁜 사용자 경험이 발생합니다.
우리는 더 잘할 수 있습니다!
Browser Event Loop
업데이트 우선 순위를 적절하게 지정하는 방법에 대해 자세히 알아보기 전에
브라우저에 이러한 종류의 사용자 상호 작용에 문제가 있는 이유를 더 자세히 살펴보겠습니다.
- JavaScript 코드는 하나의 스레드에서 실행됩니다. 즉, 주어진 시간에 JavaScript 한 줄만 실행할 수 있습니다.
- 동일한 스레드는 레이아웃 및 페인트와 같은 다른 문서 수명 주기도 담당합니다.
- 이는 JavaScript 코드가 실행될 때마다 브라우저가 다른 작업을 수행할 수 없도록 차단됨을 의미합니다.
대부분의 JavaScript 프레임워크(현재 버전의 React 포함)는 업데이트를 동기식으로 실행합니다.
이 동작을 DOM이 업데이트된 후에만 반환되는 render() 함수로 생각할 수 있습니다.
이 시간 동안 메인 스레드가 차단됩니다.
Problems with Current Solutions
- 장기 실행 작업은 프레임 드랍을 유발합니다.
- 각 작업의 크기를 가늠하고,각각 몇 밀리초 내에 완료될 수 있는지 확인해야 한 프레임 내에서 실행할 수 있습니다.
- 각 작업 별 다른 우선 순위가 있습니다.
- 위의 예제 애플리케이션에서 사용자 입력의 우선 순위를 지정하면 전반적으로 더 나은 경험을 얻을 수 있음을 확인했습니다.
- 이렇게 하려면 순서를 정의하고 그에 따라 작업을 예약하는 방법이 필요합니다.
Concurrent React and the Scheduler
Concurrent React (also known as Time Slicing).
- React 16과 함께 출시된 새로운 Fiber architecture 재작성 덕분에 React는 이제 렌더링 중에 일시 중지되고 메인 스레드에 양보2할 수 있습니다.
- 앞으로 Concurrent React에 대해 더 많이 듣게 될 것입니다.
- 현재로서는 이 모드가 활성화되면 React가 React 컴포넌트의 동기 렌더링을 여러 프레임에서 실행되는 조각으로 분할한다는 것을 이해하는 것이 중요합니다.
- ➡️ 이 기능을 사용하면 장기 실행 렌더링 작업을 작은 청크로 분할할 수 있습니다.
- (주 : 더 알아보기 : Suspense, useTransition)
스케줄러
범용 협력 메인 스레드 스케줄러는 React Core 팀에서 개발했으며 브라우저에서 다른 우선 순위 수준으로 콜백을 등록할 수 있습니다.
이 문서를 작성하는 시점에서 우선 순위 수준은 다음과 같습니다.
- Immediate : 동기적으로 우선적으로 실행해야 하는 작업입니다.
- UserBlocking (250ms timeout) 사용자 상호작용의 결과로 실행되어야 하는 작업(예: 버튼 클릭).
- Normal (5s timeout) 즉각적으로 느껴질 필요가 없는 업데이트의 경우.
- Low (10s timeout) 연기할 수 있지만 결국에는 완료해야 하는 작업(예: 분석 알림).
- Idle (no timeout) 전혀 실행할 필요가 없는 작업(예: 숨겨진 오프스크린 콘텐츠).
- 현재 구현(current implementation)에서는 requestAnimationFrame() 콜백 내에서 postMessage()를 사용하여 이 작업을 수행합니다. 그러면 프레임이 렌더링된 직후에 호출됩니다.
Scheduling in Action
// The app shows a search box and a list of names. The list is
// controlled by the searchValue state variable, which is updated
// by the search box.
function App() {
const [searchValue, setSearchValue] = React.useState();
function handleChange(value) {
setSearchValue(value);
}
return (
<div>
<SearchBox onChange={handleChange} />
<NameList searchValue={searchValue} />
</div>
);
}
// 검색 상자는 기본 HTML 입력 요소를 렌더링하고
// inputValue 변수를 사용하여 제어합니다.
// 새 키가 눌리면 먼저 로컬 inputValue를 업데이트한 다음
// App 컴포넌트의 searchValue를 업데이트한 다음
// 서버에 대한 분석 알림을 시뮬레이션합니다.
//
function SearchBox(props) {
const [inputValue, setInputValue] = React.useState();
function handleChange(event) {
const value = event.target.value;
setInputValue(value);
props.onChange(value);
sendAnalyticsNotification(value); // 매우 느림
};
return (
<input
type="text"
value={inputValue}
onChange={handleChange}
/>
);
}
ReactDOM.render(<App />, container);
아래 검색 상자에 이름(예: "Ada Stewart")을 입력하고 작동 방식을 확인하세요.
- 첫 번째 keypress 이벤트를 처리한 후 브라우저는 대기열에서 보류 중인 이벤트를 보고 프레임을 렌더링하기 전에 이벤트 리스너를 실행하기로 결정합니다.
- ReactDOM.render(<App />, container);
+ const root = ReactDOM.unstable_createRoot(rootElement);
+ root.render(<App />);
import { unstable_next } from "scheduler";
function SearchBox(props) {
const [inputValue, setInputValue] = React.useState();
function handleChange(event) {
const value = event.target.value;
setInputValue(value);
unstable_next(function() {
props.onChange(value);
sendAnalyticsNotification(value);
});
}
return <input type="text" value={inputValue} onChange={handleChange} />;
}
실제로 이 변경으로 인해 입력 상자가 이미 훨씬 더 반응적으로 느껴지고 입력하는 동안 프레임이 더 이상 떨어지지 않습니다.
성능 탭을 함께 다시 살펴보겠습니다.
장기 실행 작업은 이제 단일 프레임 내에서 완료할 수 있는 더 작은 작업으로 분할됩니다.
프레임 드랍을 나타내는 빨간색 삼각형도 사라졌습니다.
(눈에 보이지 않으면 매우 낮은 우선순위를 먹인다 - 비렌더링 관심사)
import {
unstable_LowPriority,
unstable_scheduleCallback
} from "scheduler";
function sendDeferredAnalyticsNotification(value) {
unstable_scheduleCallback(unstable_LowPriority, function() {
sendAnalyticsNotification(value);
});
}
Try it out!
스케줄러의 한계
- 리소스 파이팅
- 스케줄러는 사용 가능한 모든 리소스를 사용하려고 합니다.
- 스케줄러의 여러 인스턴스가 동일한 스레드에서 실행되고 리소스를 놓고 경쟁하는 경우 문제가 발생합니다.
- 애플리케이션의 모든 부분이 동일한 스케쥴러 인스턴스를 사용하도록 해야 합니다.
- 사용자 정의 작업과 브라우저 작업의 균형.
- 스케줄러는 브라우저에서 실행되기 때문에 브라우저가 노출하는 API에만 액세스할 수 있습니다.
- 렌더링 또는 가비지 수집과 같은 도큐먼트 생명 주기는 제어할 수 없는 방식으로 작업을 방해할 수 있습니다.
결론
- Concurrent React와 Scheduler를 사용하면 애플리케이션에서 작업 스케줄링을 구현할 수 있어 응답성이 뛰어난 사용자 인터페이스를 만들 수 있습니다.
- 이러한 기능의 공식 릴리스는 2019년 2분기에 있을 것입니다. 그때까지는 불안정한 API를 가지고 놀 수 있지만 변경될 것이라는 점에 유의하십시오.
- 이러한 API가 언제 변경되거나 새로운 기능에 대한 문서가 언제 작성되는지 가장 먼저 알고 싶다면 This Week in React ⚛️⚛️를 구독하세요.
'FrontEnd' 카테고리의 다른 글
리액트 프로젝트의 결합도를 관리하는 방법 (3) | 2022.07.31 |
---|---|
리액트 컴포넌트의 응집도를 관리하는 방법 (0) | 2022.07.30 |
리액트 점진적으로(일부만) 도입하기 (제이쿼리와 함께 쓸수 있을까?): ReactDOMServer (0) | 2022.07.25 |
타입스크립트 : React.FC는 그만! children 타이핑 올바르게 하기 (0) | 2022.07.24 |
리액트 테스트 : Context API 테스트와 커스텀 렌더 함수 사용하기 (0) | 2022.07.23 |