介绍
动态加载组件是一种技术,可以替代写导入
的许多组件,而不是声明可以使用的每个可能的组件,您可以使用一个动态值为组件的路径。
您还可以使用 lazy-loading 来服务最终用户在该特定时刻所需的代码包。
React 16.6.0+ 提供React.lazy
和React.Suspsense
来支持懒惰加载的 React 组件,而不是导入
所有组件,懒惰加载只允许您在需要时导入
额外的组件。
在本文中,您将探索如何动态地加载组件的概念,您还将探索如何按需加载组件的概念。
前提条件
要完成本教程,您将需要:
- 对 JavaScript 变量和函数的理解. 您可以查看 How To Code in JavaScript系列以获取更多信息.
- 了解导入、导出和渲染 React 组件。 您可以查看我们的 How To Code in React.js系列以获取更多信息.
不需要地方发展。
CodeSandbox的例子提供给进一步的实验。
动态地加载组件
Reddit 是一个网站,有多个 subreddits 用于不同的主题. 每个 subreddit 都遵循具有r/
前缀的模式. 假设您正在开发一个应用程序,该应用程序显示三个 subreddits 的视图:r/reactjs
,r/learnreactjs
和r/javascript
。
假设您根据属性显示一个不同的组件,‘subredditsToShow’:
1[label src/App.js]
2import React from 'react';
3import shortid from 'shortid';
4
5import LearnReactView from './views/learnreactView';
6import ReactView from './views/reactView';
7import JavaScriptView from './views/javascriptView';
8import NullView from './views/NullView';
9
10export default function App({ subredditsToShow }) {
11 const subredditElementList = subredditsToShow.map(
12 subreddit => {
13 switch (subreddit) {
14 case 'reactjs':
15 return <ReactView key={shortid.generate()} />;
16 case 'learnreactjs':
17 return (
18 <LearnReactView key={shortid.generate()} />
19 );
20 case 'javascript':
21 return (
22 <JavaScriptView key={shortid.generate()} />
23 );
24 default:
25 return (
26 <NullView key={shortid.generate()}>
27 {`"r/${subreddit}" - not implemented`}
28 </NullView>
29 );
30 }
31 }
32 );
33
34 return <div>{subredditElementList}</div>;
35}
<$>[注]
**注:**本示例使用 shortid
来生成独特的密钥. 截至本修订, shortid
现在已被贬值, nanoid
是推荐的替代方案。
在本示例中,subredditsToShow
在index.js
中定义为:
1const subredditsToShow = [
2 'reactjs',
3 'learnreactjs',
4 'pics',
5 'reactjs',
6 'learnreactjs',
7 'svelte',
8 'javascript',
9 'learnreactjs'
10];
在运行此应用程序时,您将观察到:
1[secondary_label Output]
2r/reactjs
3r/learnreactjs
4"r/pics" - not implemented
5r/reactjs
6r/learnreactjs
7"r/svelte" - not implemented
8r/javascript
9r/learnreactjs
pics
和 svelte
没有实现,没有处理它们的案例
,你的应用程序中没有该 subreddit 的单独视图组件。
这种交换/案例
方法适用于少数次元,但处理额外的次元将要求您:
- 添加新的导入 - 即使是未使用的导入
- 更新开关组件 - 产生不可维护的代码
您可以通过每 subreddit 动态加载组件来防止这些问题,并如下所示,使用useEffect
和useState
删除开关声明:
1[label src/App.js]
2import React, { lazy, useEffect, useState } from 'react';
3import shortid from 'shortid';
4
5const importView = subreddit =>
6 lazy(() =>
7 import(`./views/${subreddit}View`).catch(() =>
8 import(`./views/NullView`)
9 )
10 );
11
12export default function App({ subredditsToShow }) {
13 const [views, setViews] = useState([]);
14
15 useEffect(() => {
16 async function loadViews() {
17 const componentPromises =
18 subredditsToShow.map(async subreddit => {
19 const View = await importView(subreddit);
20 return <View key={shortid.generate()} />;
21 });
22
23 Promise.all(componentPromises).then(setViews);
24 }
25
26 loadViews();
27 }, [subredditsToShow]);
28
29 return (
30 <React.Suspense fallback='Loading views...'>
31 <div className='container'>{views}</div>
32 </React.Suspense>
33 );
34}
让我们把上面的代码打破一下。
importView
动态导入视图. 它返回一个NullView
(Nullobject pattern)为未匹配的subreddit
.- 然后您将组件存储在
Views
中,以便在useEffect
中完成导入后进行渲染. loadViews
在useEffect
中导入视图,并将它们存储在setViews
状态中。
这将动态地用字符串完成负载组件,下一步您将探索一个更先进的示例。
动态地与对象加载组件
让我们考虑一个情况,您通过对应数据属性来动态地加载不同的视图
。
Reddit API 暴露了搜索结果的 JSON 响应. 以下是搜索 "react hooks"时的响应的例子;
1[seconary_label Output]
2{
3 "data": {
4 "children": [
5 {
6 "data": {
7 "subreddit": "reactjs",
8 "title": "New tutorial for React hook",
9 "url": "..."
10 }
11 },
12 {
13 "data": {
14 "subreddit": "javascript",
15 "title": "React Hook Form",
16 "url": "..."
17 }
18 },
19 {
20 "data": {
21 "subreddit": "Frontend",
22 "title": "React hook examples",
23 "url": "..."
24 }
25 }
26 ]
27 }
28}
您可以通过仅加载已实现的视图来处理不同的subreddit
:
1[label src/App.js]
2import React, { lazy, useEffect, useState } from 'react';
3import shortid from 'shortid';
4
5const importView = subreddit =>
6 lazy(() =>
7 import(`./views/${subreddit}View`).catch(() =>
8 import(`./views/NullView`)
9 )
10 );
11
12const searchSubreddit = async query =>
13 fetch(
14 `https://www.reddit.com/search.json?q=${query}`
15 ).then(_ => _.json());
16
17export default function App({ subredditsToShow }) {
18 const [views, setViews] = useState([]);
19
20 const extractData = response =>
21 response.data.children.map(({ data }) => data);
22
23 useEffect(() => {
24 async function loadViews() {
25 const subredditsToShow = await searchSubreddit(
26 'react hooks'
27 ).then(extractData);
28 const componentPromises = subredditsToShow.map(
29 async data => {
30 const View = await importView(data.subreddit);
31 return (
32 <View key={shortid.generate()} {...data} />
33 );
34 }
35 );
36
37 Promise.all(componentPromises).then(setViews);
38 }
39
40 loadViews();
41 }, [subredditsToShow]);
42
43 return (
44 <React.Suspense fallback='Loading views...'>
45 <div className='container'>{views}</div>
46 </React.Suspense>
47 );
48}
与前一部分的差异是:
- 您现在正在处理一个对象,‘数据’,而不是‘subreddit’字符串
- 您正在将数据传递到每个动态视图中,使用
<View key={shortid.generate()} {...data} />
每一个视图现在都会收到一个副本的数据
作为一个支持。
以下是「reactjsView」的例子,是专为「reactjs」子路由器设计的视图:
1[label src/views/reactjsView.js]
2import React from 'react';
3import Layout from './Layout';
4import styled, { css } from 'styled-components';
5
6const Container = styled.article`
7 display: flex;
8 flex-direction: column;
9`;
10
11export default ({ subreddit, title, url }) => (
12 <Layout
13 css={css`
14 &:hover {
15 background-color: papayawhip;
16 }
17 `}
18 >
19 <Container>
20 <h3>{title}</h3>
21 <p>{`r/${subreddit}`}</p>
22 <a href={url}>-> Visit the site</a>
23 </Container>
24 </Layout>
25);
以下是「reactjsView」的例子,它是专为「javascript」次元设计的视图:
1[label src/views/javascriptView.js]
2import React from 'react';
3import Layout from './Layout';
4import styled, { css } from 'styled-components';
5
6const Container = styled.article`
7 display: flex;
8 flex-direction: row;
9 background-color: rgba(0, 0, 0, 0.1);
10 padding: 2rem;
11 & > * {
12 padding-left: 1rem;
13 }
14`;
15
16export default ({ subreddit, title, url }) => (
17 <Layout
18 css={css`
19 background-color: papayawhip;
20 `}
21 >
22 <Container>
23 <h4>{title}</h4>
24 <p>({`r/${subreddit}`})</p>
25 <a href={url}>-> Visit the site</a>
26 </Container>
27 </Layout>
28);
这两种视图都可以使用由数据
对象提供的subreddit
,title
和url
。
这结束了与对象动态地加载组件。
按需装载部件
在上面的示例中,您已自动加载组件,而无需性能改进。
您可以通过在用户执行操作时仅在需要时发送JavaScript来改进此功能。
假设您需要显示以下数据的不同类型的图表:
1const data = [
2 {
3 id: 'php',
4 label: 'php',
5 value: 372,
6 color: 'hsl(233, 70%, 50%)'
7 },
8 {
9 id: 'scala',
10 label: 'scala',
11 value: 363,
12 color: 'hsl(15, 70%, 50%)'
13 },
14 {
15 id: 'go',
16 label: 'go',
17 value: 597,
18 color: 'hsl(79, 70%, 50%)'
19 },
20 {
21 id: 'css',
22 label: 'css',
23 value: 524,
24 color: 'hsl(142, 70%, 50%)'
25 },
26 {
27 id: 'hack',
28 label: 'hack',
29 value: 514,
30 color: 'hsl(198, 70%, 50%)'
31 }
32];
您可以更快地加载网站,而不需要发送未使用的JavaScript,并只在需要时加载图表:
1import React, { lazy, useState } from 'react';
2import shortid from 'shortid';
3
4const importView = chartName =>
5 lazy(() =>
6 import(`./charts/${chartName}`)
7 .catch(() => import(`./charts/NullChart`))
8 );
9
10const data = [ ... ];
11
12const ChartList = ({ charts }) =>
13 Object.values(charts).map(Chart => (
14 <Chart key={shortid.generate()} data={data} />
15 ));
16
17export default function App() {
18 const [charts, setCharts] = useState({});
19
20 const addChart = chartName => {
21 if (charts[chartName]) return;
22
23 const Chart = importView(chartName);
24 setCharts(c => ({ ...c, [chartName]: Chart }));
25 };
26
27 const loadPieChart = () => addChart('Pie');
28 const loadWaffleChart = () => addChart('Waffle');
29
30 return (
31 <main>
32 <section className="container">
33 <button disabled={charts['Pie']}
34 onClick={loadPieChart}>
35 Pie Chart
36 </button>
37 <button disabled={charts['Waffle']}
38 onClick={loadWaffleChart}>
39 Waffle Chart
40 </button>
41 </section>
42 <section className="container">
43 <React.Suspense fallback="Loading charts...">
44 <div className="row">
45 <ChartList charts={charts} />
46 </div>
47 </React.Suspense>
48 </section>
49 </main>
50 );
51}
importView
与以前的示例相同,除了组件位置ChartList
重复一个对象,名称是图名,值是导入的组件App
状态,charts
,是一个对象,用于追踪已经加载的组件addChart
以动态方式以名义导入图表,并将图表添加到charts
状态,这就是您渲染的值load PieChart
和loadWaffleChart
是便利方法,您可以用 [Memouse(INKL0]]] 回忆。
「src/charts/Pie.js」和「src/charts/Waffle.js」仅在用户点击相应按钮时才能加载。
这结束了按需装载组件的React.lazy
和React.Suspense
。
结论
在本文中,您介绍了动态装载部件和按需装载部件,这些技术可以帮助改善维护和性能。
如果您想了解更多关于 React 的信息,请参阅 我们的 React 主题页面以获取练习和编程项目。