第二部分:React进阶
系列文章目录
第一章:React从入门到进阶之初识React
第一章:React从入门到进阶之JSX简介
第三章:React从入门到进阶之元素渲染
第四章:React从入门到进阶之JSX虚拟DOM渲染为真实DOM的原理和步骤
第五章:React从入门到进阶之组件化开发及Props属性传值
第六章:React从入门到进阶之state及组件的生命周期
第七章:React从入门到进阶之React事件处理
第八章:React从入门到进阶之React条件渲染
第九章:React从入门到进阶之React中的列表与key
第十章:React从入门到进阶之表单及受控组件和非受控组件
第十一章:React从入门到进阶之组件的状态提升
第十二章:React从入门到进阶之组件的组合使用
第十三章:React从入门到进阶之组件的组件的懒加载及上下文Context
文章目录
前言
在前面的十二个章节中我们已经学习了React的一些基本使用,那么React远远不止这些内容。那么在接下来的章节中,我们将继续学习React的进阶部分。
在本章节中我们将学习React中关于组件的懒加载及上下文Context的一些知识。
接下来就让我们开始我们的React进阶之旅吧!
组件懒加载 - React.lazy()
- 在前面的学习中,我们在导入一个组件时是通过import xxx from 'xxx’这种方式导入的
- 但是随着我们应用的增长,我们需要导入的组件肯定也会越来越多,如果是一次就把所有依赖的组件都导入进来,那么明显就会导致页面加载时间过长。这个时候我们就需要考虑来优化我们的项目了。
- 在React中为我们提供了一种动态导入组件的方式,这种方式可以让我们能够按需导入,也就是说需要用到哪个组件时再动态的把它导进来,这种方式也被称作是“懒加载”。懒加载能够明显的提高我们应用的性能,尽管并没有减少应用整体的代码体积,但是至少可以避免加载一些用户永远不需要的代码,并在初始加载的时候减少所需加载的代码量。
- 在React中有一个lazy方法,可以帮助我们实现组件的懒加载,但是需要注意的是该方法必须要配合React中的Suspense组件一起使用。React.lazy 函数能让我们像渲染常规组件一样处理动态引入(的组件),接下来我们看一下如何使用该方法:
- 使用前
import OtherComponent from './OtherComponent';
- 使用后
const OtherComponent = React.lazy(() => import('./OtherComponent'));
- 其实使用方式很简单,此代码将会在组件首次渲染时,自动导入包含 OtherComponent 组件的包。
- React.lazy 接受一个函数,这个函数需要动态调用 import()。它必须返回一个 Promise,该 Promise 需要 resolve 一个 defalut export 的 React 组件,也就是说调用import()导入的组件中必须是通过 “export default”的方式导出的。
- 然后我们前面还提到,lazy必须要与Suspense组件结合使用,并且要在 Suspense 组件中渲染 lazy 组件。 另外在使用Suspense时必须要传递一个fallback的props属性,这样就可以使得我们在等待加载 lazy 组件时做优雅降级(比如 loading 指示器等)。
import React, {
Suspense } from 'react';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<div>
<Suspense fallback={
<div>Loading...</div>}>
<OtherComponent />
</Suspense>
</div>
);
}
Suspense中的fallback 属性接受任何在组件加载过程中我们想展示的 React 元素。同时我们也可以将 Suspense 组件置于懒加载组件之上的任何位置。甚至还可以用一个 Suspense 组件包裹多个懒加载组件
Context(无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。)
- 在我们前面学习的React中,一般情况下数据都是通过props属性自上而下(由父及子)进行传递的,但是这种做法对于某些类型的组件是非常繁琐的(比如从上到下传递的层级比较深)。这种情况下props显然就不太合适了。
- 为了解决这一问题,React又提供了一个叫Context的东西,这哥们就比props要好使的多,它提供了一种在组件之间共享属性的方式,而不必显式的通过组件树进行逐层传递props。
- 何时使用 Context
Context 设计目的就是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言。举个例子,在下面的代码中,我们通过一个 “theme” 属性手动调整一个按钮组件的样式:
class App extends React.Component {
render() {
return <Toolbar theme="dark" />;
}
}
function Toolbar(props) {
// Toolbar 组件接受一个额外的“theme”属性,然后传递给 ThemedButton 组件。
// 如果应用中每一个单独的按钮都需要知道 theme 的值,这会是件很麻烦的事,
// 因为必须将这个值层层传递所有组件。
return (
<div>
<ThemedButton theme={
props.theme} />
</div>
);
}
class ThemedButton extends React.Component {
render() {
return <Button theme={
this.props.theme} />;
}
}
上面代码代码中就是我们传统的使用props进行层层传递的方式。下面来我们来看看使用Context后的代码
// Context 可以让我们无须明确地传遍每一个组件,就能将值深入传递进组件树。
// 为当前的 theme 创建一个 context(“light”为默认值)。
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
// 使用一个 Provider 来将当前的 theme 传递给以下的组件树。
// 无论多深,任何组件都能读取这个值。
// 在这个例子中,我们将 “dark” 作为当前的值传递下去。
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
// 中间的组件再也不必指明往下传递 theme 了。
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
class ThemedButton extends React.Component {
// 指定 contextType 读取当前的 theme context。
// React 会往上找到最近的 theme Provider,然后使用它的值。
// 在这个例子中,当前的 theme 值为 “dark”。
static contextType = ThemeContext;
render() {
return <Button theme={
this.context} />;
}
}
- 上面的代码中,我们首先通过React的createContext创建一个ThemeContext默认值为light,然后再用ThemeContext.Provider把想要使用该属性的子组件包裹起来,这样无论层级有多深都能够获取到该theme值
- 在需要用到该theme值的组件中先指定 contextType 读取到当前的 theme context,React 会往上找到最近的 theme Provider,然后使用它的值。
- 另外,Context虽然能够解决层层传递的问题,并且它的主要应用场景就是很多不同层级的组件需要访问同样一些的数据。但在使用之前也需要我们做一些衡量,也要谨慎使用,因为它可能会使组件的复用性变差。
- 比如有这样一个例子,某属性a通过组件A传给B,B传给C,C传给D,D又传给了E,结果在中间传递过程中都没有用到该属性(仅仅作为一个传递的桥梁)而真正使用的确是最后一层E。那么这种情况Context明显也是不太合适了,因为中间经历了很多冗余的传递。那么这种情况就需要考虑一下上一章节中我们讲到的组件组合效果会更好一些。
- 再比如,一个某属性值需要传递的层次并不深,只需要在兄弟组件间互相共享,那么这个时候使用我们前面学习的状态提升肯定是最佳选择。
- 所以组件间传值的方式有很多,但是要根据不同场景选择最佳的方式才能达到最好的效果
多个Context嵌套使用
// Theme context,默认的 theme 是 “light” 值
const ThemeContext = React.createContext('light');
// 用户登录 context
const UserContext = React.createContext({
name: 'Guest',
});
class App extends React.Component {
render() {
const {
signedInUser, theme} = this.props;
// 提供初始 context 值的 App 组件
return (
<ThemeContext.Provider value={
theme}>
<UserContext.Provider value={
signedInUser}>
<Layout />
</UserContext.Provider>
</ThemeContext.Provider>
);
}
}
function Layout() {
return (
<div>
<Sidebar />
<Content />
</div>
);
}
// 一个组件可能会消费多个 context
function Content() {
return (
<ThemeContext.Consumer>
{
theme => (
<UserContext.Consumer>
{
user => (
<ProfilePage user={
user} theme={
theme} />
)}
</UserContext.Consumer>
)}
</ThemeContext.Consumer>
);
}
API
- React.createContext
- 如果我们要使用context,那么我们首先需要通过React的createContext方法来创建一个Context对象。当 React 渲染一个订阅了这个 Context 对象的组件时,这个组件会从组件树中离自己最近的那个匹配的 Provider 中读取到当前的 context 值。
- 创建Context时可以指定一个默认值,但是只有当组件所处的树中没有匹配到 Provider 时,其 defaultValue 才会生效。这有助于我们在不使用 Provider 包装组件的情况下对组件进行测试。另外需要注意的是:如果将 undefined 传递给 Provider 的 value ,那么组件的 defaultValue 是不会生效的。
const MyContext = React.createContext(defaultValue);
- Context.Provider
如果我们想要将属性值通过context进行层层传递,在创建好Context对象好,还需要将需要使用属性值的后代组件用Context.Provider包裹起来
- 每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化。
- rovider 接收一个 value 属性,传递给消费组件。一个 Provider 可以和多个消费组件有对应关系。同时多个 Provider 也可以嵌套使用,但是里层的会覆盖掉外层的数据。
- 当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染。Provider 及其内部 组件都不受制于 shouldComponentUpdate 函数,因此当 消费组件在其祖先组件退出更新的情况下也能更新。
<MyContext.Provider value={
/* 某个值 */}>
- Class.contextType
挂载在 class 上的 contextType 属性会被重赋值为一个由 React.createContext() 创建的 Context 对象。这能让我们使用 this.context 来消费最近 Context 上的那个值。我们可以在任何生命周期中访问到它,包括 render 函数中。
class MyClass extends React.Component {
componentDidMount() {
let value = this.context;
/* 在组件挂载完成后,使用 MyContext 组件的值来执行一些有副作用的操作 */
}
componentDidUpdate() {
let value = this.context;
/* ... */
}
componentWillUnmount() {
let value = this.context;
/* ... */
}
render() {
let value = this.context;
/* 基于 MyContext 组件的值进行渲染 */
}
}
MyClass.contextType = MyContext;
- Context.Consumer
这里我们还可以通过Context的Consumer来订阅组件中context的变更。这种订阅方式需要将一个函数作为子元素,这个函数接收当前的context值,并且返回一个React节点。传递个函数的value值相当于上层的组件树中里这个context最近的Proivder提供的value值,如果没有找到对应的Provider,则value相当于在创建context时传递给createContext的defaultValue
<MyContext.Consumer>
{
value => <div>{
value}</div>/* 基于 context 值进行渲染*/}
</MyContext.Consumer>
好了,关于React.lazy和Context的相关知识就说这么多。
下一章节中我们将学习Refs转发的一些知识