介绍
动态加载组件是一种可以替代import
许多组件写入的技术。您可以为组件的路径使用动态值,而不是声明可以使用的每个可能的组件。
您还可以使用延迟加载来为特定时刻的最终用户提供所需的代码包。最终用户的较小包大小应该会导致性能改进。
React 16.6.0+ 提供React.lazy
并React.Suspsense
支持延迟加载 React 组件。import
延迟加载将允许您仅import
在需要时加载其他组件,而不是加载所有组件。
在本文中,您将探索如何动态加载组件的概念。您还将探索如何按需加载组件的概念。
先决条件
要完成本教程,您需要:
- 了解 JavaScript 变量和函数。您可以查看如何在 JavaScript 中编码系列以了解更多信息。
- 了解导入、导出和渲染 React 组件。您可以查看我们的How To Code in React.js系列以了解更多信息。
不需要本地开发。
提供了CodeSandbox示例以供进一步实验。
动态加载组件
Reddit 是一个网站,具有针对不同主题的多个subreddit。每个 subreddit 都遵循具有r/
前缀的模式。假设你正在开发一个应用程序,三个subreddits显示访问量:r/reactjs
,r/learnreactjs
,和r/javascript
。
假设您根据属性显示不同的组件subredditsToShow
:
import React from 'react';
import shortid from 'shortid';
import LearnReactView from './views/learnreactView';
import ReactView from './views/reactView';
import JavaScriptView from './views/javascriptView';
import NullView from './views/NullView';
export default function App({ subredditsToShow }) {
const subredditElementList = subredditsToShow.map(
subreddit => {
switch (subreddit) {
case 'reactjs':
return <ReactView key={shortid.generate()} />;
case 'learnreactjs':
return (
<LearnReactView key={shortid.generate()} />
);
case 'javascript':
return (
<JavaScriptView key={shortid.generate()} />
);
default:
return (
<NullView key={shortid.generate()}>
{`"r/${subreddit}" - not implemented`}
</NullView>
);
}
}
);
return <div>{subredditElementList}</div>;
}
注意:此示例用于shortid
生成唯一键。在此修订版中,shortid
现已弃用,nanoid
是推荐的替代方案。
在本例中,subredditsToShow
定义index.js
为:
const subredditsToShow = [
'reactjs',
'learnreactjs',
'pics',
'reactjs',
'learnreactjs',
'svelte',
'javascript',
'learnreactjs'
];
运行此应用程序时,您将观察到:
Outputr/reactjs
r/learnreactjs
"r/pics" - not implemented
r/reactjs
r/learnreactjs
"r/svelte" - not implemented
r/javascript
r/learnreactjs
pics
并且svelte
没有实施。没有case
处理它们,并且在您的应用程序中没有针对该 subreddit 的单独视图组件。该NullView
显示这些subreddits。
这种switch/case
方法适用于少数 subreddit。但是,处理额外的 subreddits 需要您:
- 添加新的导入 – 甚至导入未使用的导入。
- 更新开关组件 – 产生不可维护的代码。
您可以通过按 subreddit 动态加载组件并删除 switch 语句来防止这些问题,如下所示,使用useEffect
和useState
:
import React, { lazy, useEffect, useState } from 'react';
import shortid from 'shortid';
const importView = subreddit =>
lazy(() =>
import(`./views/${subreddit}View`).catch(() =>
import(`./views/NullView`)
)
);
export default function App({ subredditsToShow }) {
const [views, setViews] = useState([]);
useEffect(() => {
async function loadViews() {
const componentPromises =
subredditsToShow.map(async subreddit => {
const View = await importView(subreddit);
return <View key={shortid.generate()} />;
});
Promise.all(componentPromises).then(setViews);
}
loadViews();
}, [subredditsToShow]);
return (
<React.Suspense fallback='Loading views...'>
<div className='container'>{views}</div>
</React.Suspense>
);
}
让我们分解上面的代码。
importView
动态导入视图。它为不匹配的对象返回一个NullView
(空对象模式)subreddit
。- 然后在
views
完成导入后将组件存储在中以进行渲染useEffect
。 loadViews
insideuseEffect
导入视图并将它们存储在状态中setViews
。- 最后,您需要使用Suspense包装视图并带有回退,以显示其中的组件何时
views
加载。
这将使用字符串动态地结束加载组件。接下来您将探索更高级的示例。
使用对象动态加载组件
让我们考虑一种情况,您通过匹配数据属性来动态加载不同的“视图”。
Reddit API 公开搜索结果的 JSON 响应。这是搜索“react hooks”时的响应示例;
[seconary_label Output]
{
"data": {
"children": [
{
"data": {
"subreddit": "reactjs",
"title": "New tutorial for React hook",
"url": "..."
}
},
{
"data": {
"subreddit": "javascript",
"title": "React Hook Form",
"url": "..."
}
},
{
"data": {
"subreddit": "Frontend",
"title": "React hook examples",
"url": "..."
}
}
]
}
}
您可以subreddit
通过仅加载已实现的视图来处理不同的s:
import React, { lazy, useEffect, useState } from 'react';
import shortid from 'shortid';
const importView = subreddit =>
lazy(() =>
import(`./views/${subreddit}View`).catch(() =>
import(`./views/NullView`)
)
);
const searchSubreddit = async query =>
fetch(
`https://www.reddit.com/search.json?q=${query}`
).then(_ => _.json());
export default function App({ subredditsToShow }) {
const [views, setViews] = useState([]);
const extractData = response =>
response.data.children.map(({ data }) => data);
useEffect(() => {
async function loadViews() {
const subredditsToShow = await searchSubreddit(
'react hooks'
).then(extractData);
const componentPromises = subredditsToShow.map(
async data => {
const View = await importView(data.subreddit);
return (
<View key={shortid.generate()} {...data} />
);
}
);
Promise.all(componentPromises).then(setViews);
}
loadViews();
}, [subredditsToShow]);
return (
<React.Suspense fallback='Loading views...'>
<div className='container'>{views}</div>
</React.Suspense>
);
}
与上一节的不同之处在于:
- 您现在正在处理一个对象,
data
, 而不是subreddit
字符串。 - 您正在将数据传递给每个动态视图
<View key={shortid.generate()} {...data} />
每个视图现在都获得一个副本data
作为道具。
下面是一个例子reactjsView
。这是一个专为reactjs
subreddit设计的视图:
import React from 'react';
import Layout from './Layout';
import styled, { css } from 'styled-components';
const Container = styled.article`
display: flex;
flex-direction: column;
`;
export default ({ subreddit, title, url }) => (
<Layout
css={css`
&:hover {
background-color: papayawhip;
}
`}
>
<Container>
<h3>{title}</h3>
<p>{`r/${subreddit}`}</p>
<a href={url}>-> Visit the site</a>
</Container>
</Layout>
);
下面是一个例子reactjsView
。这是一个专为javascript
subreddit设计的视图:
import React from 'react';
import Layout from './Layout';
import styled, { css } from 'styled-components';
const Container = styled.article`
display: flex;
flex-direction: row;
background-color: rgba(0, 0, 0, 0.1);
padding: 2rem;
& > * {
padding-left: 1rem;
}
`;
export default ({ subreddit, title, url }) => (
<Layout
css={css`
background-color: papayawhip;
`}
>
<Container>
<h4>{title}</h4>
<p>({`r/${subreddit}`})</p>
<a href={url}>-> Visit the site</a>
</Container>
</Layout>
);
这两个视图可以使用subreddit
,title
和url
通过所提供的data
对象。
这结束了使用对象动态加载组件。
按需加载组件
在前面的示例中,您已经自动加载了组件,而没有提高性能。
您可以通过仅在用户执行操作时需要时发送 JavaScript 来改进这一点。
假设您需要为以下数据显示不同类型的图表:
const data = [
{
id: 'php',
label: 'php',
value: 372,
color: 'hsl(233, 70%, 50%)'
},
{
id: 'scala',
label: 'scala',
value: 363,
color: 'hsl(15, 70%, 50%)'
},
{
id: 'go',
label: 'go',
value: 597,
color: 'hsl(79, 70%, 50%)'
},
{
id: 'css',
label: 'css',
value: 524,
color: 'hsl(142, 70%, 50%)'
},
{
id: 'hack',
label: 'hack',
value: 514,
color: 'hsl(198, 70%, 50%)'
}
];
您可以更快地加载站点而无需发送未使用的 JavaScript 并仅在需要时加载图表:
import React, { lazy, useState } from 'react';
import shortid from 'shortid';
const importView = chartName =>
lazy(() =>
import(`./charts/${chartName}`)
.catch(() => import(`./charts/NullChart`))
);
const data = [ ... ];
const ChartList = ({ charts }) =>
Object.values(charts).map(Chart => (
<Chart key={shortid.generate()} data={data} />
));
export default function App() {
const [charts, setCharts] = useState({});
const addChart = chartName => {
if (charts[chartName]) return;
const Chart = importView(chartName);
setCharts(c => ({ ...c, [chartName]: Chart }));
};
const loadPieChart = () => addChart('Pie');
const loadWaffleChart = () => addChart('Waffle');
return (
<main>
<section className="container">
<button disabled={charts['Pie']}
onClick={loadPieChart}>
Pie Chart
</button>
<button disabled={charts['Waffle']}
onClick={loadWaffleChart}>
Waffle Chart
</button>
</section>
<section className="container">
<React.Suspense fallback="Loading charts...">
<div className="row">
<ChartList charts={charts} />
</div>
</React.Suspense>
</section>
</main>
);
}
importView
除了组件位置之外,与前面的示例中的相同。ChartList
迭代一个对象,其中名称是图表名称,值是导入的组件。- 的
App
状态,charts
是一个目的是跟踪已经加载组件。 addChart
按名称动态导入图表并添加到charts
状态,这就是您渲染的内容。loadPieChart
和loadWaffleChart
是方便的方法,您可以使用useMemo 进行记忆。return
呈现两个按钮,您需要使用Suspense
.
src/charts/Pie.js
并且src/charts/Waffle.js
仅在用户单击相应按钮时加载。
这结束了使用React.lazy
和按需加载组件React.Suspense
。
结论
在本文中,您了解了动态加载组件和按需加载组件。这些技术可以帮助改进维护和性能。
如果您想了解有关 React 的更多信息,请查看我们的 React 主题页面以获取练习和编程项目。