为了让 Web 应用程序满足不同类型用户的需求,应用程序可能需要比一个用户类型更多的代码,以便应用程序可以处理和适应不同的场景和用例,从而带来新的功能。当这种情况发生时,随着代码库的增长,应用程序的性能下降是可以预料的。
代码分割是一种技术,它允许应用程序仅在需要时加载所需的代码,而不是加载更多代码。例如,当用户导航到主页时,可能不需要加载为后端仪表板提供支持的代码。使用代码分割,我们可以确保仅加载主页的代码,并将无关代码排除在外,以获得更优化的加载体验。
可以使用 React Loadable 在 React 应用程序中实现代码分割。它提供了一个 高阶组件,可以配置为在特定时间动态导入特定组件。
组件分割
在某些情况下,我们可能希望根据用户事件有条件地渲染组件,例如,当用户登录帐户时。处理此问题的常用方法是利用状态 - 组件根据应用程序的登录状态进行渲染。我们称之为组件分割。
让我们看看代码示例。
查看 Pen 
 React-Loadable 由 Kingsley Silas Chijioke (@kinsomicrote)
在 CodePen 上。
作为一个基本示例,假设我们想要有条件地渲染一个包含<h2> 标题“Hello”的组件。如下所示
const Hello = () => {
	return (
		<React.Fragment>
			<h2>Hello</h2>
		</React.Fragment>
	)
}我们在 App 组件中可以有一个openHello 状态,初始值为false。然后,我们可以使用一个按钮来切换状态,显示或隐藏组件。我们将把它放在handleHello 方法中,如下所示
class App extends React.Component {
	state = {
		openHello: false
	}
	handleHello = () => {
		this.setState({ openHello: !this.state.openHello })
	}
	render() {
		return (
			<div className="App">
				<button onClick={this.handleHello}>
					Toggle Component
				</button>
				{
					this.state.openHello ?
						<Hello />
					: null
				}
			</div>
		);
	}
}快速浏览 DevTools 并注意网络选项卡

现在,让我们重构以使用 LoadableHello。我们不会直接导入组件,而是使用 Loadable 进行导入。我们将首先安装 react-loadable 包
## yarn, npm or however you roll
yarn add react-loadable现在它已添加到我们的项目中,我们需要将其导入应用程序
import Loadable from 'react-loadable';我们将使用 Loadable 创建一个“加载”组件,它将如下所示
const LoadableHello = Loadable({
	loader: () => import('./Hello'),
	loading() {
		return <div>Loading...</div>
	}
})我们将一个函数作为值传递给loader,该函数返回我们之前创建的Hello 组件,并使用import() 动态导入它。我们想要在导入组件之前渲染的回退 UI 由loading() 返回。在这个例子中,我们返回一个div 元素,但如果需要,我们也可以在其中放入一个组件。
现在,我们不再直接在App 组件中输入Hello 组件,而是将LoadableHello 放在那里,以便条件语句如下所示
{
	this.state.openHello ?
		<LoadableHello />
	: null
}看看 - 现在我们的Hello 组件只有在通过按钮切换状态时才会加载到 DOM 中

这就是组件分割:能够异步加载一个组件来加载另一个组件!
基于路由的分割
好的,我们已经了解了如何使用 Loadable 通过其他组件加载组件。另一种方法是使用基于路由的分割。这里的区别在于组件根据当前路由加载。
因此,假设用户位于应用程序的主页,并点击了一个路由为/hello 的 Hello 视图。属于该路由的组件将是唯一加载的组件。这是一种在许多应用程序中处理分割的相当普遍的方式,并且通常效果很好,尤其是在不太复杂的应用程序中。
这是一个应用程序中定义路由的基本示例。在这种情况下,我们有两个路由:(1) 主页 (/) 和 (2) Hello (/hello)。
class App extends Component {
	render() {
		return (
			<div className="App">
				<BrowserRouter>
					<div>
						<Link to="/">Home</Link>
						<Link to="/hello">Hello</Link>
						<Switch>
							<Route exact path="/" component={Home} />
							<Route path="/hello" component={Hello} />
						</Switch>
					</div>
				</BrowserRouter>
			</div>
		);
	}
}就目前而言,当用户切换路径时,所有组件都将渲染,即使我们希望根据该路径渲染一个Hello 组件。当然,如果我们只讨论几个组件,这并不是什么大问题,但随着组件的增加和应用程序规模的增长,这种情况肯定会变得更加严重。
使用 Loadable,我们可以通过为每个组件创建可加载的组件来仅导入我们想要的组件
const LoadableHello = Loadable({
	loader: () => import('./Hello'),
	loading() {
		return <div>Loading...</div>
	}
})
const LoadableHome = Loadable({
	loader: () => import('./Home'),
	loading() {
		return <div>Loading...</div>
	}
})
class App extends Component {
	render() {
		return (
			<div className="App">
				<BrowserRouter>
					<div>
						<Link to="/">Home</Link>
						<Link to="/hello">Hello</Link>
						<Switch>
							<Route exact path="/" component={LoadableHome} />
							<Route path="/hello" component={LoadableHello} />
						</Switch>
					</div>
				</BrowserRouter>
			</div>
		);
	}
}现在,我们在正确的时间提供正确的代码。感谢 Loadable!
错误和延迟怎么办?
如果导入的组件加载速度很快,则不需要闪烁“加载”组件。值得庆幸的是,Loadable 具有延迟显示加载组件的功能。这有助于防止它过早地显示,在那里它会显得愚蠢,而是在预期看到它加载后的一段显著时间过去后显示它。
为此,我们的示例 Loadable 组件将如下所示;
const LoadableHello = Loadable({
	loader: () => import('./Hello'),
	loading: Loader,
	delay: 300
})在这里,我们将Hello 组件作为值传递给loading,它通过loader 导入。默认情况下,delay 设置为 200ms,但我们将我们的设置稍晚一些,为 300ms。
现在让我们在Loader 组件中添加一个条件,告诉它仅在我们设置的 300ms 延迟过去后才显示加载程序
const Loader = (props) => {
	if (props.pastDelay) {
		return <h2>Loading...</h2>
	} else {
		return null
	}
}因此,Loader 组件仅在Hello 组件在 300ms 后没有显示时才会显示。
react-loader 还提供了一个error 属性,我们可以用它来返回遇到的错误。而且,因为它是一个属性,我们可以让它输出我们想要的任何内容。
const Loader = (props) => {
	if (props.error) {
		return <div>Oh no, something went wrong!</div>;
	} else if (props.delay) {
		return <h2>Loading...</h2>
	} else {
		return null;
	}
}请注意,我们实际上将延迟和错误处理结合在一起!如果一开始就出现错误,我们将显示一些消息。如果没有错误,但 300ms 已过去,那么我们将显示一个加载程序。否则,请加载Hello 组件!
总结
我们现在能够更加自由灵活地加载和显示代码,难道不棒吗?代码分割 - 无论是通过组件还是通过路由 - 都是 React 的设计理念之一。React 允许我们编写包含隔离代码的模块化组件,我们可以随时随地提供这些组件,并允许它们与 DOM 和其他组件交互。非常酷!
希望这能让你对代码分割这个概念有一个很好的了解。当您开始动手实践并开始使用它时,值得查看更深入的文章,以更深入地了解这个概念。
- React Loadable GitHub 仓库 - 获取所有详细文档的完美位置。
- 介绍 React Loadable - 库作者提供的精彩概述。
- Frontend Masters 上的 React Loadable - 由 Brian Holt 教授的课程(需要订阅)
- React 的实验性 Suspense API - 处理加载状态的另一种方法入门指南。
- 与 React.lazy 的比较 - 两者之间有一些重叠,这篇文章分析了它们的不同之处。
感觉就像以前应用程序就是这样构建的,React 不断添加功能让它像应用程序一直以来的工作方式一样。在过去,这是页面刷新。这并没有太大区别。
此外,这就是我已经发现 Vue 开箱即用的工作方式。(无需编译)
像这样的事情的好处是,我们正在摆脱需要庞大构建环境和大量依赖项的单体应用程序。前端框架环境的竞争推动了这一进程,我很高兴看到 React 紧跟潮流,这对每个人都有好处。
这与 React.Lazy 相比如何?似乎功能相似,那么为什么选择这种方法而不是 React 自带的方法呢?
嘿,普雷斯顿!文章底部有一个方便的链接,可以解释清楚差异:https://www.smooth-code.com/open-source/loadable-components/docs/loadable-vs-react-lazy/
@Preston 服务器端渲染
你在最后一个链接中提供的(与 React.Lazy 的比较)说,react-loadable 已经不再维护,我们应该切换到另一个库。
如果你关心服务器渲染,React 文档推荐使用@loadable/component
https://reactjs.ac.cn/docs/code-splitting.html#reactlazy
react-loadable 最近似乎没有维护。另一个选择是 React Async Component,它对我来说一直很好用,尽管最近 Dan Abramov 警告我们不要使用它(或其依赖项之一)。我在某处读到,服务器端渲染将来会在 Suspense 中规划,但目前不支持,这就是 React.Lazy 不适合 SR 的原因。
嘿,金斯利,很棒的文章!
你能解释一下基于路由的分裂部分中的以下含义吗?
这意味着当更改路径时,它当前会加载两个路径的所有组件,还是会加载应用程序中的所有组件?