React SuspenseãæŽ»çšããã¢ããªã±ãŒã·ã§ã³ã®ããã©ãŒãã³ã¹ãšãŠãŒã¶ãŒãšã¯ã¹ããªãšã³ã¹ãåäžãããã䞊åããŒã¿ãã§ããã®é«åºŠãªãã¯ããã¯ãæ¢æ±ããŸãã
React Suspense調æŽïŒäžŠåããŒã¿ãã§ããããã¹ã¿ãŒãã
React Suspenseã¯ãéåæåŠçãç¹ã«ããŒã¿ãã§ããã®åŠçæ¹æ³ã«é©åœããããããŸãããããŒã¿ãèªã¿èŸŒãŸããã®ãåŸ æ©äžã«ã³ã³ããŒãã³ããã¬ã³ããªã³ã°ãããµã¹ãã³ããããããšãå¯èœã«ããããŒãã£ã³ã°ç¶æ ã管çããããã®å®£èšçãªæ¹æ³ãæäŸããŸããããããåã ã®ããŒã¿ãã§ãããSuspenseã§ã©ããããã ãã§ã¯ããŠã©ãŒã¿ãŒãã©ãŒã«å¹æã«ã€ãªããã1ã€ã®ãã§ãããå®äºããŠããæ¬¡ã®ãã§ãããéå§ããããããããã©ãŒãã³ã¹ã«æªåœ±é¿ãåãŒãå¯èœæ§ããããŸãããã®ããã°èšäºã§ã¯ãSuspenseã䜿çšããŠè€æ°ã®ããŒã¿ãã§ããã䞊åã«èª¿æŽããã¢ããªã±ãŒã·ã§ã³ã®å¿çæ§ãæé©åããã°ããŒãã«ãªèŠèŽè åãã«ãŠãŒã¶ãŒãšã¯ã¹ããªãšã³ã¹ãåäžãããããã®é«åºŠãªæŠç¥ã«ã€ããŠæãäžããŠãããŸãã
ããŒã¿ãã§ããã«ããããŠã©ãŒã¿ãŒãã©ãŒã«åé¡ã®çè§£
ååãã¢ãã¿ãŒãæè¿ã®ã¢ã¯ãã£ããã£ãæã€ãŠãŒã¶ãŒãããã¡ã€ã«ã 衚瀺ããå¿ èŠãããã·ããªãªãæ³åããŠã¿ãŠãã ãããåããŒã¿ãé çªã«ãã§ããããå ŽåããŠãŒã¶ãŒã¯ååã®ããŒãã£ã³ã°ã¹ãããŒã次ã«ã¢ãã¿ãŒã®ããŒãã£ã³ã°ã¹ãããŒãæåŸã«ã¢ã¯ãã£ããã£ãã£ãŒãã®ããŒãã£ã³ã°ã¹ãããŒã確èªããããšã«ãªããŸãããã®é 次ããŒãã£ã³ã°ãã¿ãŒã³ã¯ãŠã©ãŒã¿ãŒãã©ãŒã«å¹æãçã¿åºããå®å šãªãããã¡ã€ã«ã®ã¬ã³ããªã³ã°ãé å»¶ããããŠãŒã¶ãŒãã€ã©ã€ã©ãããŸããããŸããŸãªãããã¯ãŒã¯é床ãæã€åœéçãªãŠãŒã¶ãŒã«ãšã£ãŠã¯ããã®é å»¶ãããã«é¡èã«ãªãå¯èœæ§ããããŸãã
ãã®ç°¡ç¥åãããã³ãŒãã¹ãããããæ€èšããŠãã ããïŒ
function UserProfile() {
const name = useName(); // ãŠãŒã¶ãŒåããã§ããããŸã
const avatar = useAvatar(name); // ååã«åºã¥ããŠã¢ãã¿ãŒããã§ããããŸã
const activity = useActivity(name); // ååã«åºã¥ããŠã¢ã¯ãã£ããã£ããã§ããããŸã
return (
<div>
<h2>{name}</h2>
<img src={avatar} alt="User Avatar" />
<ul>
{activity.map(item => <li key={item.id}>{item.text}</li>)}
</ul>
</div>
);
}
ãã®äŸã§ã¯ãuseAvatarãšuseActivityã¯ãuseNameã®çµæã«äŸåããŠããŸããããã¯æç¢ºãªãŠã©ãŒã¿ãŒãã©ãŒã«ãäœæããŸãâ useAvatarãšuseActivityã¯ãuseNameãå®äºãããŸã§ããŒã¿ãã§ãããéå§ã§ããŸãããããã¯éå¹çã§ãããäžè¬çãªããã©ãŒãã³ã¹ã®ããã«ããã¯ã§ãã
Suspenseã«ãã䞊åããŒã¿ãã§ããã®æŠç¥
Suspenseã䜿çšããŠããŒã¿ãã§ãããæé©åããããã®éµã¯ããã¹ãŠã®ããŒã¿ãªã¯ãšã¹ããåæã«éå§ããããšã§ãã以äžã«ã䜿çšã§ããããã€ãã®æŠç¥ã瀺ããŸãã
1. `React.preload`ãšãªãœãŒã¹ã䜿çšããããŒã¿ã®ããªããŒã
æã匷åãªãã¯ããã¯ã®1ã€ã¯ãã³ã³ããŒãã³ããã¬ã³ããªã³ã°ãããåã«ããŒã¿ãããªããŒãããããšã§ããããã«ã¯ãããªãœãŒã¹ãïŒããŒã¿ãã§ããpromiseãã«ãã»ã«åãããªããžã§ã¯ãïŒãäœæããããŒã¿ãäºåã«ãã§ããããããšãå«ãŸããŸãã`React.preload`ãããã«åœ¹ç«ã¡ãŸããã³ã³ããŒãã³ããããŒã¿ãå¿ èŠãšãããšãã«ã¯ããã§ã«å©çšå¯èœã«ãªã£ãŠãããããããŒãã£ã³ã°ç¶æ ãã»ãŒå®å šã«æé€ã§ããŸãã
補åããã§ããããããã®ãªãœãŒã¹ãæ€èšããŠãã ããïŒ
const createProductResource = (productId) => {
let promise;
let product;
let error;
const suspender = new Promise((resolve, reject) => {
promise = fetch(`/api/products/${productId}`)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
})
.then(data => {
product = data;
resolve();
})
.catch(e => {
error = e;
reject(e);
});
});
return {
read() {
if (error) {
throw error;
}
if (product) {
return product;
}
throw suspender;
},
};
};
// çšæ³ïŒ
const productResource = createProductResource(123);
function ProductDetails() {
const product = productResource.read();
return (<div>{product.name}</div>);
}
ããã§ãProductDetailsã³ã³ããŒãã³ããã¬ã³ããªã³ã°ãããåã«ããã®ãªãœãŒã¹ãããªããŒãã§ããŸããããšãã°ãã«ãŒãé·ç§»äžããããŒæã«ã
React.preload(productResource);
ããã«ãããProductDetailsã³ã³ããŒãã³ããå¿
èŠãšãããšããŸã§ã«ããŒã¿ãå©çšå¯èœã«ãªããããŒãã£ã³ã°ç¶æ
ãæå°éã«æããããããæé€ããããããå¯èœæ§ãé«ãŸããŸãã
2. `Promise.all`ã䜿çšããåæããŒã¿ãã§ãã
ãã1ã€ã®ã·ã³ãã«ã§å¹æçãªã¢ãããŒãã¯ãPromise.allã䜿çšããŠã1ã€ã®Suspenseå¢çå
ã§åæã«ãã¹ãŠã®ããŒã¿ãã§ãããéå§ããããšã§ããããã¯ãããŒã¿ã®äŸåé¢ä¿ãäºåã«ããã£ãŠããå Žåã«é©ããŠããŸãã
ãŠãŒã¶ãŒãããã¡ã€ã«ã®äŸãããäžåºŠèŠãŠã¿ãŸããããããŒã¿ãé çªã«ãã§ãããã代ããã«ãååãã¢ãã¿ãŒãã¢ã¯ãã£ããã£ãã£ãŒããåæã«ãã§ããã§ããŸãã
import { useState, useEffect, Suspense } from 'react';
async function fetchName() {
// APIåŒã³åºããã·ãã¥ã¬ãŒã
await new Promise(resolve => setTimeout(resolve, 500));
return 'John Doe';
}
async function fetchAvatar(name) {
// APIåŒã³åºããã·ãã¥ã¬ãŒã
await new Promise(resolve => setTimeout(resolve, 300));
return `https://example.com/avatars/${name.toLowerCase().replace(' ', '-')}.jpg`;
}
async function fetchActivity(name) {
// APIåŒã³åºããã·ãã¥ã¬ãŒã
await new Promise(resolve => setTimeout(resolve, 800));
return [
{ id: 1, text: 'Posted a photo' },
{ id: 2, text: 'Updated profile' },
];
}
function useSuspense(promise) {
const [result, setResult] = useState(null);
useEffect(() => {
let didCancel = false;
promise.then(
(data) => {
if (!didCancel) {
setResult({ status: 'success', value: data });
}
},
(error) => {
if (!didCancel) {
setResult({ status: 'error', value: error });
}
}
);
return () => {
didCancel = true;
};
}, [promise]);
if (result?.status === 'success') {
return result.value;
} else if (result?.status === 'error') {
throw result.value;
} else {
throw promise;
}
}
function Name() {
const name = useSuspense(fetchName());
return <h2>{name}</h2>;
}
function Avatar({ name }) {
const avatar = useSuspense(fetchAvatar(name));
return <img src={avatar} alt="User Avatar" />;
}
function Activity({ name }) {
const activity = useSuspense(fetchActivity(name));
return (
<ul>
{activity.map(item => <li key={item.id}>{item.text}</li>)}
</ul>
);
}
function UserProfile() {
const name = useSuspense(fetchName());
return (
<div>
<Suspense fallback=<div>ããŒãã£ã³ã°ã¢ãã¿ãŒ...</div>>
<Avatar name={name} />
</Suspense>
<Suspense fallback=<div>ããŒãã£ã³ã°ã¢ã¯ãã£ããã£...</div>>
<Activity name={name} />
</Suspense>
</div>
);
}
export default UserProfile;
ãã ãã`Avatar`ãš`Activity`ã®ããããã`fetchName`ã«ãäŸåããŠããããåå¥ã®suspenseå¢çå ã§ã¬ã³ããªã³ã°ãããå Žåã`fetchName`promiseãèŠªã«æã¡äžããŠReact Contextãä»ããŠæäŸã§ããŸãã
import React, { createContext, useContext, useState, useEffect, Suspense } from 'react';
async function fetchName() {
// APIåŒã³åºããã·ãã¥ã¬ãŒã
await new Promise(resolve => setTimeout(resolve, 500));
return 'John Doe';
}
async function fetchAvatar(name) {
// APIåŒã³åºããã·ãã¥ã¬ãŒã
await new Promise(resolve => setTimeout(resolve, 300));
return `https://example.com/avatars/${name.toLowerCase().replace(' ', '-')}.jpg`;
}
async function fetchActivity(name) {
// APIåŒã³åºããã·ãã¥ã¬ãŒã
await new Promise(resolve => setTimeout(resolve, 800));
return [
{ id: 1, text: 'Posted a photo' },
{ id: 2, text: 'Updated profile' },
];
}
function useSuspense(promise) {
const [result, setResult] = useState(null);
useEffect(() => {
let didCancel = false;
promise.then(
(data) => {
if (!didCancel) {
setResult({ status: 'success', value: data });
}
},
(error) => {
if (!didCancel) {
setResult({ status: 'error', value: error });
}
}
);
return () => {
didCancel = true;
};
}, [promise]);
if (result?.status === 'success') {
return result.value;
} else if (result?.status === 'error') {
throw result.value;
} else {
throw promise;
}
}
const NamePromiseContext = createContext(null);
function Avatar() {
const namePromise = useContext(NamePromiseContext);
const name = useSuspense(namePromise);
const avatar = useSuspense(fetchAvatar(name));
return <img src={avatar} alt="User Avatar" />;
}
function Activity() {
const namePromise = useContext(NamePromiseContext);
const name = useSuspense(namePromise);
const activity = useSuspense(fetchActivity(name));
return (
<ul>
{activity.map(item => <li key={item.id}>{item.text}</li>)}
</ul>
);
}
function UserProfile() {
const namePromise = fetchName();
return (
<NamePromiseContext.Provider value={namePromise}>
<Suspense fallback=<div>ããŒãã£ã³ã°ã¢ãã¿ãŒ...</div>>
<Avatar />
</Suspense>
<Suspense fallback=<div>ããŒãã£ã³ã°ã¢ã¯ãã£ããã£...</div>>
<Activity />
</Suspense>
</NamePromiseContext.Provider>
);
}
export default UserProfile;
3. 䞊åãã§ããã管çããããã®ã«ã¹ã¿ã ããã¯ã®äœ¿çš
æœåšçã«æ¡ä»¶ä»ãããŒã¿äŸåé¢ä¿ãæã€ããè€éãªã·ããªãªã§ã¯ã䞊åããŒã¿ãã§ããã管çããSuspenseã䜿çšã§ãããªãœãŒã¹ãè¿ãã«ã¹ã¿ã ããã¯ãäœæã§ããŸãã
import { useState, useEffect, useRef } from 'react';
function useParallelData(fetchFunctions) {
const [resource, setResource] = useState(null);
const mounted = useRef(true);
useEffect(() => {
mounted.current = true;
const promises = fetchFunctions.map(fn => fn());
const suspender = Promise.all(promises).then(
(results) => {
if (mounted.current) {
setResource({ status: 'success', value: results });
}
},
(error) => {
if (mounted.current) {
setResource({ status: 'error', value: error });
}
}
);
setResource({
status: 'pending',
value: suspender,
});
return () => {
mounted.current = false;
};
}, [fetchFunctions]);
const read = () => {
if (!resource) {
throw new Error('ãªãœãŒã¹ã¯ãŸã åæåãããŠããŸãã');
}
if (resource.status === 'pending') {
throw resource.value;
}
if (resource.status === 'error') {
throw resource.value;
}
return resource.value;
};
return { read };
}
// 䜿çšäŸïŒ
async function fetchUserData(userId) {
// APIåŒã³åºããã·ãã¥ã¬ãŒã
await new Promise(resolve => setTimeout(resolve, 300));
return { id: userId, name: 'User ' + userId };
}
async function fetchUserPosts(userId) {
// APIåŒã³åºããã·ãã¥ã¬ãŒã
await new Promise(resolve => setTimeout(resolve, 500));
return [{ id: 1, title: 'Post 1' }, { id: 2, title: 'Post 2' }];
}
function UserProfile({ userId }) {
const { read } = useParallelData([
() => fetchUserData(userId),
() => fetchUserPosts(userId),
]);
const [userData, userPosts] = read();
return (
<div>
<h2>{userData.name}</h2>
<ul>
{userPosts.map(post => <li key={post.id}>{post.title}</li>)}
</ul>
</div>
);
}
function App() {
return (
<Suspense fallback=<div>ãŠãŒã¶ãŒããŒã¿ãããŒãããŠããŸã...</div>>
<UserProfile userId={123} />
</Suspense>
);
}
export default App;
ãã®ã¢ãããŒãã¯ãããã¯å ã§promiseãšããŒãã£ã³ã°ç¶æ ã管çããè€éããã«ãã»ã«åããã³ã³ããŒãã³ãã³ãŒããããã¯ãªãŒã³ã«ããããŒã¿ã®ã¬ã³ããªã³ã°ã«éäžãããŸãã
4. ã¹ããªãŒãã³ã°ãµãŒããŒã¬ã³ããªã³ã°ã«ããéžæçãã€ãã¬ãŒã·ã§ã³
ãµãŒããŒã¬ã³ããªã³ã°ãããã¢ããªã±ãŒã·ã§ã³ã®å ŽåãReact 18ã¯ã¹ããªãŒãã³ã°ãµãŒããŒã¬ã³ããªã³ã°ã«ããéžæçãã€ãã¬ãŒã·ã§ã³ãå°å
¥ããŠããŸããããã«ããããµãŒããŒã§å©çšå¯èœã«ãªã£ããšãã«ãHTMLããã£ã³ã¯åäœã§ã¯ã©ã€ã¢ã³ãã«éä¿¡ã§ããŸããèªã¿èŸŒã¿ã®é
ãã³ã³ããŒãã³ãã<Suspense>å¢çã§ã©ããããããšã«ããããµãŒããŒã§é
ãã³ã³ããŒãã³ãããŸã èªã¿èŸŒãŸããŠããéã«ãããŒãžã®æ®ãã®éšåãã€ã³ã¿ã©ã¯ãã£ãã«ããããšãã§ããŸããããã«ãããç¹ã«ãããã¯ãŒã¯æ¥ç¶ãããã€ã¹ã®é
ããŠãŒã¶ãŒã«å¯ŸããŠãç¥èŠããã©ãŒãã³ã¹ãåçã«åäžããŸãã
ãã¥ãŒã¹ãŠã§ããµã€ãããäžçã®ããŸããŸãªå°åïŒããšãã°ãã¢ãžã¢ããšãŒããããååã¢ã¡ãªã«ïŒããã®èšäºã衚瀺ããå¿ èŠãããã·ããªãªãèããŠã¿ãŸããããäžéšã®ããŒã¿ãœãŒã¹ã¯ãä»ã®ããŒã¿ãœãŒã¹ãããé ãå ŽåããããŸããéžæçãã€ãã¬ãŒã·ã§ã³ã«ãããããé«éãªå°åããã®èšäºãæåã«è¡šç€ºããé ãå°åããã®èšäºããŸã èªã¿èŸŒãŸããŠããéã§ããããŒãžå šäœããããã¯ãããã®ãé²ãããšãã§ããŸãã
ãšã©ãŒãšããŒãã£ã³ã°ç¶æ ã®åŠç
Suspenseã¯ããŒãã£ã³ã°ç¶æ
ã®ç®¡çãç°¡çŽ åããŸããããšã©ãŒåŠçã¯äŸç¶ãšããŠéèŠã§ãããšã©ãŒå¢çïŒcomponentDidCatchã©ã€ããµã€ã¯ã«ã¡ãœããã䜿çšãããã`react-error-boundary`ãªã©ã®ã©ã€ãã©ãªã®useErrorBoundaryããã¯ã䜿çšïŒã䜿çšãããšãããŒã¿ãã§ãããŸãã¯ã¬ã³ããªã³ã°äžã«çºçãããšã©ãŒãé©åã«åŠçã§ããŸãããããã®ãšã©ãŒå¢çã¯ãç¹å®ã®Suspenseå¢çå
ã§ãšã©ãŒããã£ããããããã«æŠç¥çã«é
眮ããã¢ããªã±ãŒã·ã§ã³å
šäœãã¯ã©ãã·ã¥ããã®ãé²ããŸãã
import React, { Suspense } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
function MyComponent() {
// ... ãšã©ãŒãçºçããå¯èœæ§ã®ããããŒã¿ããã§ããããŸã
}
function App() {
return (
<ErrorBoundary fallback={<div>åé¡ãçºçããŸããïŒ</div>}>
<Suspense fallback={<div>ããŒãã£ã³ã°...</div>}>
<MyComponent />
</Suspense>
</ErrorBoundary>
);
}
ããŒãã£ã³ã°ç¶æ ãšãšã©ãŒç¶æ ã®äž¡æ¹ã«ãæ å ±çã§ãŠãŒã¶ãŒãã¬ã³ããªãŒãªãã©ãŒã«ããã¯UIãæäŸããããšãå¿ããªãã§ãã ãããããã¯ããããã¯ãŒã¯é床ãé ãããå°åçãªãµãŒãã¹åæ¢ã«ééããå¯èœæ§ãããåœéçãªãŠãŒã¶ãŒã«ãšã£ãŠç¹ã«éèŠã§ãã
Suspenseã§ããŒã¿ãã§ãããæé©åããããã®ãã¹ããã©ã¯ãã£ã¹
- éèŠãªããŒã¿ã®ç¹å®ãšåªå é äœä»ãïŒã¢ããªã±ãŒã·ã§ã³ã®æåã®ã¬ã³ããªã³ã°ã«äžå¯æ¬ ãªããŒã¿ãç¹å®ãããã®ããŒã¿ãæåã«ãã§ããããããšãåªå ããŸãã
- å¯èœãªå Žåã¯ããŒã¿ãããªããŒãããïŒ
React.preloadãšãªãœãŒã¹ã䜿çšããŠãã³ã³ããŒãã³ããå¿ èŠãšããåã«ããŒã¿ãããªããŒãããããŒãã£ã³ã°ç¶æ ãæå°éã«æããŸãã - ããŒã¿ãåæã«ãã§ããããïŒ
Promise.allãŸãã¯ã«ã¹ã¿ã ããã¯ã䜿çšããŠãè€æ°ã®ããŒã¿ãã§ããã䞊åã«éå§ããŸãã - APIãšã³ããã€ã³ããæé©åããïŒAPIãšã³ããã€ã³ããããã©ãŒãã³ã¹ã®ããã«æé©åãããã¬ã€ãã³ã·ãšãã€ããŒããµã€ãºãæå°éã«æããããããã«ããŸããGraphQLãªã©ã®ãã¯ããã¯ã䜿çšããŠãå¿ èŠãªããŒã¿ã®ã¿ããã§ããããããšãæ€èšããŠãã ããã
- ãã£ãã·ã¥ãå®è£ ããïŒé »ç¹ã«ã¢ã¯ã»ã¹ãããããŒã¿ããã£ãã·ã¥ããŠãAPIãªã¯ãšã¹ãã®æ°ãæžãããŸããå ç¢ãªãã£ãã·ã¥æ©èœã®ããã«ã`swr`ã`react-query`ãªã©ã®ã©ã€ãã©ãªã䜿çšããããšãæ€èšããŠãã ããã
- ã³ãŒãåå²ã䜿çšããïŒã¢ããªã±ãŒã·ã§ã³ãå°ããªãã£ã³ã¯ã«åå²ããŠãåæèªã¿èŸŒã¿æéãççž®ããŸããã³ãŒãåå²ãšSuspenseãçµã¿åãããŠãã¢ããªã±ãŒã·ã§ã³ã®ããŸããŸãªéšåãæ®µéçã«ããŒãããŠã¬ã³ããªã³ã°ããŸãã
- ããã©ãŒãã³ã¹ãç£èŠããïŒLighthouseãWebPageTestãªã©ã®ããŒã«ã䜿çšããŠãã¢ããªã±ãŒã·ã§ã³ã®ããã©ãŒãã³ã¹ã宿çã«ç£èŠããããã©ãŒãã³ã¹ã®ããã«ããã¯ãç¹å®ããŠå¯ŸåŠããŸãã
- ãšã©ãŒãé©åã«åŠçããïŒããŒã¿ãã§ãããšã¬ã³ããªã³ã°äžã«ãšã©ãŒããã£ãããããšã©ãŒå¢çãå®è£ ãããŠãŒã¶ãŒã«æ å ±çãªãšã©ãŒã¡ãã»ãŒãžãæäŸããŸãã
- ãµãŒããŒãµã€ãã¬ã³ããªã³ã°ïŒSSRïŒãæ€èšããïŒSEOãšããã©ãŒãã³ã¹ã®çç±ãããã¹ããªãŒãã³ã°ãšéžæçãã€ãã¬ãŒã·ã§ã³ã䜿çšããSSRã䜿çšããŠãããé«éãªåæãšã¯ã¹ããªãšã³ã¹ãæäŸããããšãæ€èšããŠãã ããã
çµè«
䞊åããŒã¿ãã§ããã®æŠç¥ãšçµã¿åããããšãReact Suspenseã¯ãå¿çæ§ãé«ãããã©ãŒãã³ã¹ã®é«ãWebã¢ããªã±ãŒã·ã§ã³ãæ§ç¯ããããã®åŒ·åãªããŒã«ããããæäŸããŸãããŠã©ãŒã¿ãŒãã©ãŒã«åé¡ã®çè§£ãšãããªããŒããPromise.allã䜿çšããåæãã§ãããã«ã¹ã¿ã ããã¯ãªã©ã®ãã¯ããã¯ã®å®è£
ã«ããããŠãŒã¶ãŒãšã¯ã¹ããªãšã³ã¹ã倧å¹
ã«åäžãããããšãã§ããŸãããšã©ãŒãé©åã«åŠçããããã©ãŒãã³ã¹ãç£èŠããŠãã¢ããªã±ãŒã·ã§ã³ãäžçäžã®ãŠãŒã¶ãŒåãã«æé©åãããç¶æ
ãç¶æããŠããããšã確èªããŠãã ãããReactãé²åãç¶ããã«ã€ããŠãã¹ããªãŒãã³ã°ãµãŒããŒã¬ã³ããªã³ã°ã«ããéžæçãã€ãã¬ãŒã·ã§ã³ãªã©ã®æ°æ©èœã調æ»ããããšã§ãå Žæããããã¯ãŒã¯ç¶æ³ã«é¢ä¿ãªããåªãããŠãŒã¶ãŒãšã¯ã¹ããªãšã³ã¹ãæäŸããããã®èœåãããã«åäžããŸãããããã®ãã¯ããã¯ãæ¡çšããããšã§ãæ©èœçã§ããã ãã§ãªããã°ããŒãã«ãªèŠèŽè
ã«ãšã£ãŠäœ¿çšããã®ã楜ããã¢ããªã±ãŒã·ã§ã³ãäœæã§ããŸãã
ãã®ããã°èšäºã¯ãReact Suspenseã䜿çšãã䞊åããŒã¿ãã§ããæŠç¥ã®å æ¬çãªæŠèŠãæäŸããããšãç®çãšããŠããŸããåèã«ãªã£ããšæã£ãŠããã ããã°å¹žãã§ãããããã®ãã¯ããã¯ãç¬èªã®ãããžã§ã¯ãã§è©ŠããŠãã³ãã¥ããã£ãšããªãã®ç¥èŠãå ±æããããšããå§ãããŸãã