一.Fetch-on-Render
지금까지, 우리가 따라온 베스트 프랙티스는 Fetch-on-Render 모드였습니다. 즉:
-
컴포넌트를 렌더링 (render) 할 때 데이터가 없으면, 먼저 loading 표시
-
componentDidMount시에 리퀘스트를 전송 (fetch) -
데이터가 돌아온 후, 데이터의 렌더링을 시작
이렇게 하는 이점은관심사별로 코드를 정리할 수 있어, 데이터 리퀘스트와 데이터에 대응하는 UI 렌더링 로직을 함께 배치할 수 있습니다. 그러나 단점도 명확합니다:
-
시리얼:프로세스 전체가 시리얼 (먼저 render 후 fetch) 이며, 더 깊은 데이터일수록 늦게 로드됨
-
fetch 와 render 의 바인드:lazy 컴포넌트의 fetch 타이밍도 lazy 된다는 것을 의미하며, 컴포넌트의 필요에 따른 로드에도 퍼포먼스 부담이 생김
명백히, 데이터 리퀘스트는 선행 가능하며, fetch 와 render 의 바인드도 합리적이지 않습니다. 그러나 최적화를 시작하기 전에, 하나의 문제를 검토해 봅시다:우리가 실현하고 싶은 목표는 무엇일까요?
二.물과 곰의 선택
사용자 경험에 관해, 우리가 달성하고 싶은 효과는:
-
가능한 한 빨리 가장 중요한 콘텐츠를 표시
-
동시에, 이차적인 콘텐츠가 페이지 전체 (완전한 콘텐츠) 의 로드 시간을 늦추는 것도 원하지 않음
일부 콘텐츠를 우선 표시하고 싶지만, 나머지 콘텐츠가 우선순위로 인해 지연 표시되는 것도 원하지 않음. 이는 물과 곰의 선택처럼 보이지만, 병행성에 의해 양립이 가능해졌습니다. 기술 구현에 대응하면:
-
데이터와 코드는 모두 (중요도에 따라) 증분 로드해야 함
-
그리고 병행이 바람직함
그래서, Render-as-You-Fetch 모드가 나타났습니다
三.Render-as-You-Fetch
구체적으로는, Render-as-You-Fetch 모드는 4 점으로 나뉩니다:
-
데이터 의존의 분리:데이터와 뷰의 병행 로드
-
데이터의 조기 로드:이벤트 핸들러 내에서 데이터를 로드
-
데이터의 증분 로드:중요한 데이터를 우선 로드
-
코드의 조기 로드:코드도 데이터로 간주
전 3 점은 데이터 로드의 what, when, how 를 대상으로 하고, 마지막 1 점은 view 를 대상. data 가 충분히 빠르면, view 도 따라야 하기 때문입니다. 결국v = f(d)이니까요
P.S.v = f(d)에 관한 상세 정보는, React 를 깊게 이해하다 참조
데이터 의존의 분리:데이터와 뷰의 병행 로드
fetch 와 render 의 바인드는, 데이터 로드의 how 와 when 이 render 에 제한되는 원인이 되어, 제 1 의 저해 요인입니다. 따라서, 먼저데이터 의존을 view 에서 추출하여, what(로드하는 데이터) 과 how(로드 방식) 와 when(로드 타이밍) 을 분리할 필요가 있습니다:
The key is that regardless of the technology we're using to load our data — GraphQL, REST, etc — we can separate what data to load from how and when to actually load it.
2 つ의 구현 방법이 있으며, 수동으로 분리하거나, 구축 도구에 자동 추출시키는가:
-
동명 파일을 정의:예를 들어
MyComponent.jsx에 대응하는 데이터 리퀘스트를MyComponent.data.js에 배치 -
컴파일 시에 데이터 의존을 추출:데이터 리퀘스트는 컴포넌트 정의 내에 배치하고, 컴파일러가 해석하여 데이터 의존을 추출
후자는 데이터 의존을 분리함과 동시에, 컴포넌트 정의의 응집성도 고려할 수 있어, Relay 가 채택하고 있는 방법입니다:
// Post.js
function Post(props) {
// Given a reference to some post - `props.post` - *what* data
// do we need about that post?
const postData = useFragment(graphql`
fragment PostData on Post @refetchable(queryName: "PostQuery") {
author
title
# ... more fields ...
}
`, props.post);
// Now that we have the data, how do we render it?
return (
<div>
<h1>{postData.title}</h1>
<h2>by {postData.author}</h2>
{/* more fields */}
</div>
);
}
Relay Compiler 가 컴포넌트 중의 GraphQL 데이터 의존을 추출하고, 더욱이 집약하여, 세세한 리퀘스트를 1 つ의 Query 에 통합할 수도 있습니다
데이터의 조기 로드:이벤트 핸들러 내에서 데이터를 로드
데이터와 뷰를 분리한 후, 양자는 병행하여 독립적으로 로드할 수 있습니다. 그렇다면, 언제 데이터 로드를 시작할까요?
물론 가능한 한 빠르기 때문에, 인터랙션 이벤트 (클릭, tab 전환, 모달 윈도우 열기 등) 를 받은 후, 동시에 코드와 데이터를 따로 로드할 필요가 있습니다:
The key is to start fetching code and data for a new view in the same event handler that triggers showing that view.
페이지 레벨의 데이터는, 루트에 일괄하여 데이터 로드 타이밍을 제어시킬 수 있습니다. 예를 들어:
// Manually written logic for loading the data for the component
import PostData from './Post.data';
const PostRoute = {
// a matching expression for which paths to handle
path: '/post/:id',
// what component to render for this route
component: React.lazy(() => import('./Post')),
// data to load for this route, as function of the route
// parameters
prepare: routeParams => {
const postData = preloadRestEndpoint(
PostData.endpointUrl,
{
postId: routeParams.id,
},
);
return { postData };
},
};
export default PostRoute;
더욱이 hover, mousedown 등의 더 빠른 타이밍에 프리로드하는 것도 가능 합니다:
If we can load code and data for a view after the user clicks, we can also start that work before they click, getting a head start on preparing the view.
이때, 프리로드 기능을 router 또는 코어 UI 컴포넌트에 집중 관리하는 것을 검토할 수 있습니다. 프리로드 특성의 온오프는 통상 사용자의 디바이스와 네트워크 상황에 의존하기 때문에, 집중 관리가 더 제어하기 쉽기 때문입니다
데이터의 증분 로드:중요한 데이터를 우선 로드
데이터 로드 타이밍이 충분히 빠르다면, 더욱 속도를 올리는 방법이 있을까요?
있습니다. 경험상, 모든 데이터가 돌아오는 것을 기다리지 않고, 더 중요한 view 를 우선 표시하고 싶다고 생각합니다:
But we still want to be able to show more important parts of the view without waiting for all of our data.
이를 위해, Facebook 은 GraphQL 에서 @defer 지시를 구현했습니다:
// Post.js
function Post(props) {
const postData = useFragment(graphql`
fragment PostData on Post {
author
title
# fetch data for the comments, but don't block on it being ready
...CommentList @defer
}
`, props.post);
return (
<div>
<h1>{postData.title}</h1>
<h2>by {postData.author}</h2>
{/* @defer pairs naturally with <Suspense> to make the UI non-blocking too */}
<Suspense fallback={<Spinner/>}>
<CommentList post={postData} />
</Suspense>
</div>
);
}
데이터를 스트리밍으로返し, @defer 이외의 필드를 우선 제공합니다. 이는 데이터 레벨의 Suspense 특성에 상당합니다. 이 사고는 REST API 에도 적용 가능하여, 예를 들어 데이터 필드를 우선도별로 그룹화하고, 2 つ의 리퀘스트로 분할하여 병행 송신하여, 중요하지 않은 데이터가 중요한 데이터를 늦추는 것을 회피 합니다
코드의 조기 로드:코드도 데이터로 간주
이들 모두를 수행한 후, 데이터 로드 면에서는 이미 극한에 달한 것처럼 보입니다
그러나, 또 하나 무시할 수 없는 요인은 React.lazy 가 실제로 렌더링될 때까지 (컴포넌트) 코드를 로드하지 않는 것으로, 코드 레벨의 Fetch-on-Render 입니다:
React.lazy won't start downloading code until the lazy component is actually rendered.
마찬가지로, 코드도 데이터로 간주할 수 있으며, 루트에 코드 로드 타이밍을 제어시키고, render 플로에 결정시키지 않도록 합니다
四.예
-
GraphQL 베이스의 Render-as-You-Fetch:Relay Hooks Example App - GitHub Issues Clone
-
REST API 베이스의 Render-as-You-Fetch:Suspense Demo for Library Authors
五.정리
로드 속도를 향상시키는 열쇠는코드와 데이터를 가능한 한 빨리, 증분적으로 로드하는 것입니다:
Start loading code and data as early as possible, but without waiting for all of it to be ready.
구체적으로는 4 점으로 나뉩니다:
-
데이터 의존의 분리:view(코드) 를 로드하는 동시에, 필요한 데이터를 병행 로드
-
데이터의 조기 로드:인터랙션 이벤트를 받은 후 바로 데이터를 로드하고, 더욱이 사용자의 행동을 예측하여 view 를 프리로드하는 것도 가능
-
데이터의 증분 로드:중요한 데이터를 우선 로드하고, 동시에 이차적인 데이터의 로드 속도에도 영향이 없음
-
코드의 조기 로드:(컴포넌트) 코드도 데이터로 간주하고, 동様の 방식을 통해 로드 속도를 향상
아직 댓글이 없습니다