bookshelf/INSTRUCTIONS.md at exercises/09-performance · kentcdodds/bookshelf (github.com)
Background
이 페이지의 목표는 time to first meaningful paint를 개선하는 것입니다.
실습
코드를 논리적 단위로 분할하기 (페이지) => 코드 스플리팅
- Lazy Loading할 블록을 Suspense로 감싼다
- fallback은 로딩 보여주는 컴포넌트임
- 오류는 ErrorBoundary 사용
- React.lazy로 코드를 비동기로 가져온다.
- 가져올 코드는 Default Export여야 함
- /* webpackPrefetch: true */ 웹팩 매직 키워드를 통해 해당 페이지 소스코드를 비동기적으로 미리 가져옴.
- 사용자가 화면 사용 전에 코드 다운로드 기다릴 필요 없음.
import * as React from 'react'
import {useAuth} from './context/auth-context'
import {FullPageSpinner} from './components/lib'
const AuthenticatedApp = React.lazy(() =>
import(/* webpackPrefetch: true */ './authenticated-app'),
)
const UnauthenticatedApp = React.lazy(() => import('./unauthenticated-app'))
function App() {
const {user} = useAuth()
return (
<React.Suspense fallback={<FullPageSpinner />}>
{user ? <AuthenticatedApp /> : <UnauthenticatedApp />}
</React.Suspense>
)
}
export {App}
적용 효과
앱이 작아서 별거 없어보이지만 앱이 커질수록 효과가 커짐
사용 안하는 코드의 대부분은 리액트-라우터임
정말 최적화가 중요하면 로그인 페이지에서 라우터를 안쓰게 변경
컨텍스트 메모이제이션
훅을 제공하는 쪽에서 메모이제이션을 잘 하면 사용자가 직접 React.Memo나 React.useCallback을 사용하지 않아도 됨.
컨텍스트에서 value로 제공하는 부분은 메모이제이션을 적용해줘야 함.
bookshelf/auth-context.extra-2.js at exercises/09-performance · kentcdodds/bookshelf (github.com)
const login = React.useCallback(
form => auth.login(form).then(user => setData(user)),
[setData],
)
const register = React.useCallback(
form => auth.register(form).then(user => setData(user)),
[setData],
)
const logout = React.useCallback(() => {
auth.logout()
setData(null)
}, [setData])
const value = React.useMemo(() => ({user, login, logout, register}), [
login,
logout,
register,
user,
])
적용 효과
AuthProvider 메모이제이션 후
프론트엔드 모니터링 위한 React Profiler 적용
프로덕션 프로파일 적용 방법 with CRA
(기본적으로 꺼져있음)
npx react-script build --profile
당연히 해당 방법은 앱의 성능을 희생함.
페이스북의 경우 일부 유저만 샘플링해서 해당 방법을 적용한다 함.
인터랙션 API는 unstable 하니 주의.
인터랙션 API 리턴 객체는 Set임. 디스트럭처 해줌
bookshelf/profiler.extra-4.js at exercises/09-performance · kentcdodds/bookshelf (github.com)
import * as React from 'react'
import {client} from 'utils/api-client'
// 서버에 프로파일링 정보를 보내기 위한 부분
let queue = []
setInterval(sendProfileQueue, 5000)
function sendProfileQueue() {
if (!queue.length) {
return Promise.resolve({success: true})
}
const queueToSend = [...queue]
queue = []
return client('profile', {data: queueToSend})
}
// 추가 정보 전송과 phase filtering을 위한 wrapper component
function Profiler({metadata, phases, ...props}) {
function reportProfile(
id, // the "id" prop of the Profiler tree that has just committed
phase, // either "mount" (if the tree just mounted) or "update" (if it re-rendered)
actualDuration, // time spent rendering the committed update
baseDuration, // estimated time to render the entire subtree without memoization
startTime, // when React began rendering this update
commitTime, // when React committed this update
interactions, // the Set of interactions belonging to this update
) {
if (!phases || phases.includes(phase)) {
queue.push({
metadata, // 서버로 추가로 보내고 싶은 메타데이터 {metadata : Object}
id,
phase,
actualDuration,
baseDuration,
startTime,
commitTime,
interactions: [...interactions], // interaction 사용 시
})
}
}
return <React.Profiler onRender={reportProfile} {...props} />
}
export {Profiler}
export {unstable_trace as trace, unstable_wrap as wrap} from 'scheduler/tracing'
컴포넌트에서 사용 방법
bookshelf/list-item-list.extra-3.js at exercises/09-performance · kentcdodds/bookshelf (github.com)
아래와 같이 Profiler로 감싸고 id, metadata 정보를 적용하면 된다.
function ListItemList({filterListItems, noListItems, noFilteredListItems}) {
const listItems = useListItems()
const filteredListItems = listItems.filter(filterListItems)
if (!listItems.length) {
return <div css={{marginTop: '1em', fontSize: '1.2em'}}>{noListItems}</div>
}
if (!filteredListItems.length) {
return (
<div css={{marginTop: '1em', fontSize: '1.2em'}}>
{noFilteredListItems}
</div>
)
}
return (
<Profiler
id="List Item List"
metadata={{listItemCount: filteredListItems.length}}
>
<BookListUL>
{filteredListItems.map(listItem => (
<li key={listItem.id} aria-label={listItem.book.title}>
<BookRow book={listItem.book} />
</li>
))}
</BookListUL>
</Profiler>
)
}
인터랙션 트레이싱
bookshelf/status-buttons.extra-4.js at exercises/09-performance · kentcdodds/bookshelf (github.com)
해당 API는 Unstable함
당연히 상위 컴포넌트는 React.Profiler로 감싸져 있어야함.
import {trace} from 'components/profiler'
// Some Component
function handleClick() {
if (isError) {
reset()
} else {
trace(`Click ${label}`, performance.now(), () => {
run(onClick())
})
}
}
데이터 update정보 트레이싱
bookshelf/hooks.extra-4.js at exercises/09-performance · kentcdodds/bookshelf (github.com)
아래와 같이 update하는 부분을 감싸준다.
import {wrap} from 'components/profiler'
////// some function
return promise.then(
wrap(data => {
setData(data)
return data
}),
wrap(error => {
setError(error)
return error
}),
)
관련내용 심화
📜 Profile a React App for Performance
📜 Interaction tracing with React
참고
'FrontEnd' 카테고리의 다른 글
[Epic React][Build an Epic React App][Unit Test][단위 테스트] (0) | 2022.01.02 |
---|---|
[Epic React][Build an Epic React App][Render as you fetch][렌더링 하면서 필요한 데이터 가져오기] (0) | 2022.01.02 |
[Epic React][Build an Epic React App][Context] (0) | 2022.01.01 |
[Epic React][Build an Epic React App][Cache Management] (0) | 2022.01.01 |
[Epic React][Build an Epic React App][Router][라우터] (0) | 2021.12.31 |