Custom Hooks와 코드 분할/React.lazy()와 Suspense
Custom Hooks
개발자가 스스로 커스텀한 훅을 의미하며 이를 이용해 반복되는 로직을 함수로 뽑아내어 재사용할 수 있다
- 상태관리 로직의 재활용이 가능한 경우
- 클래스 컴포넌트보다 적은 양의 코드로 동일한 로직을 구현할 수 있는 경우
FriendStatus 컴포넌트는 사용자들이 온라인인지 오프라인인지 확인하고,
FriendListItem 컴포넌트는 사용자들의 상태에 따라 온라인이라면 초록색으로 표시하는 컴포넌트가 있다
이 두 컴포넌트는 정확하게 똑같이 쓰이는 로직이 존재한다
//FriendStatus : 친구가 online인지 offline인지 return하는 컴포넌트
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
//FriendListItem : 친구가 online일 때 초록색으로 표시하는 컴포넌트
function FriendListItem(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
return (
<li style={{ color: isOnline ? 'green' : 'black' }}>
{props.friend.name}
</li>
);
}
두 컴포넌트에서 사용하기 위해 동일하게 사용되고 있는 로직을 분리하여 함수 useFriendStatus로 만든다
Custom Hook을 정의할 때는 일종의 규칙이 있다
- Custom Hook을 정의할 때는 함수 이름 앞에 use를 붙여야한다
- 대개의 경우 프로젝트 내의 hooks 디렉토리에 Custom Hook을 위치 시킨다
- Custom Hook으로 만들 때 함수는 조건부 함수가 아니어야 한다
=>즉 return 하는 값은 조건부여서는 안 된다 useFriendStatus Hook은 온라인 상태의 여부를 boolean 타입으로 반환하고 있다
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}
useFriendStatus Hook을 두 컴포넌트에 적용
function FriendStatus(props) {
const isOnline = useFriendStatus(props.friend.id);
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
function FriendListItem(props) {
const isOnline = useFriendStatus(props.friend.id);
return (
<li style={{ color: isOnline ? 'green' : 'black' }}>
{props.friend.name}
</li>
);
}
코드 분할
React에서 코드 분할하는 방법은 dynamic import(동적 불러오기)를 사용하는 것이다
=>구문 분석 및 컴파일해야 하는 스크립트의 양을 최소화 시키기 위해 dynamic import 구문을 사용한다
Static Import
import 구문은 문서의 상위에 위치해야 하고 블록문 안에서는 위치할 수 없는 제약 사항이있다.
번들링 시 코드 구조를 분석해 모듈을 한 데 모으고 사용하지 않는 모듈은 제거하는 등의 작업을 하는데, 코드 구조가 간단하고 고정이 되어 있을 때에만 가능하다
/* 기존에는 파일의 최상위에서 import 지시자를 이용해 라이브러리 및 파일을 불러왔습니다. */
import moduleA from "library";
form.addEventListener("submit", e => {
e.preventDefault();
someFunction();
});
const someFunction = () => {
/* 그리고 코드 중간에서 불러온 파일을 사용했습니다. */
}
Dynamic Import
일반적인 정적인 Module Import 를 필요한 시점 에 로드 할 수 있도록 도와준다
=>첫 페이지 진입시에 필요한 최소한의 코드만 다운 받고, 사용자가 특정 페이지나 위치에 도달할 때마다 코드를 로드 한다면, 첫 페이지의 초기 성능을 올릴 수 있다
불러온 moduleA 가 다른 곳에서 사용되지 않는 경우, 사용자가 form을 통해 양식을 제출한 경우에만 가져오도록 할 수 있다 then 함수를 사용해 필요한 코드만 가져온다 가져온 코드에 대한 모든 호출은 해당 함수 내부에 있어야 한다
이 방식을 사용하면 번들링 시 분할된 코드(청크)를 지연 로딩시키거나 요청 시에 로딩할 수 있다
=>React.lazy 와 함께 사용할 수 있다.
form.addEventListener("submit", e => {
e.preventDefault();
/* 동적 불러오기는 이런 식으로 코드의 중간에 불러올 수 있게 됩니다. */
import('library.moduleA')
.then(module => module.default)
.then(someFunction())
.catch(handleError());
});
const someFunction = () => {
/* moduleA를 여기서 사용합니다. */
}
React.lazy()
React.lazy 함수를 사용하면 dynamic import를 사용해 컴포넌트를 렌더링할 수 있다.
React는 SPA(Single-Page-Application)이므로 한 번에 사용하지 않는 컴포넌트까지 불러오는 단점이 있는데 React는 React.lazy를 통해 컴포넌트를 동적으로 import를 할 수 있기 때문에 이를 사용하면 초기 렌더링 지연시간을 어느정도 줄일 수 있게 된다
React.lazy로 감싼 컴포넌트는 단독으로 쓰일 수는 없고, React.suspense 컴포넌트의 하위에서 렌더링을 해야 한다
import Component from './Component';
/* React.lazy로 dynamic import를 감쌉니다. */
const Component = React.lazy(() => import('./Component'));
React.Suspense
대표적으로, 비동기작업을 진행하면서 발생하는 로딩시간을 기다리면서 처리할 작업들을 대체하는 것이고 특정 데이터를 사용하는 컴포넌트에 데이터가 준비되지 않았음을 알리는 역할을 수행한다.
웹 페이지를 불러오고 진입하는 단계인 Route에 이 두 기능을 적용시키는 것이 좋다
React를 사용할때 로딩 화면을 사용하는 경우를 생각해보면 비동기 네트워크 요청을 통해 데이터를 받아와서 화면에 보여지도록 하는 과정에서 사용하고 특히 처음에 컴포넌트가 렌더링 되는 과정에서 자주 사용한다.
/* suspense 기능을 사용하기 위해서는 import 해와야 합니다. */
import { Suspense } from 'react';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));
function MyComponent() {
return (
<div>
{/* 이런 식으로 React.lazy로 감싼 컴포넌트를 Suspense 컴포넌트의 하위에 렌더링합니다. */}
<Suspense fallback={<div>Loading...</div>}>
{/* Suspense 컴포넌트 하위에 여러 개의 lazy 컴포넌트를 렌더링시킬 수 있습니다. */}
<OtherComponent />
<AnotherComponent />
</Suspense>
</div>
);
}
=>비동기로 작업되는 컴포넌트를 Suspense로 감싸고, 로딩 때 보여줄 화면을 fallback 속성으로 전달한다.
로딩화면을 생성하기 위한 useState와 같은 hook들이 생성될 필요가 없어졌다