章节回顾:
目录
代码分割
作用 / 优点
对你的应用进行代码分割能够帮助你“懒加载”当前用户所需要的内容,能够显著地提高你的应用性能。
尽管并没有减少应用整体的代码体积,但你可以避免加载用户永远不需要的代码,
并在初始加载的时候减少所需加载的代码量。
方法
在你的应用中引入代码分割的最佳方式是通过动态 import()
语法。
例如:
import("./math").then(math => {
console.log(math.add(16, 26));
});
懒加载
React.lazy & Suspense
使用
import { Suspense, lazy } from 'react'
// 通过懒加载形式获取组件
// OtherComponent组件在文件中必须是export default OtherComponent形式导出的
const OtherComponent = lazy(() => import('./OtherComponent '))
不过,这样直接使用组件,在初始阶段会有一个加载空档期,
此时,react并不知道要做什么,所以需要用Suspense
包裹住懒加载的组件,
以此来告诉react在加载空档期要做什么。
Suspense
是一个组件,标签中有一个fallback
属性
fallback
属性接受任何在组件加载过程中你想展示的 React 元素。
比如空档期展示一个loading...
文案。
完整使用方法
import { Suspense, lazy } from 'react'
const OtherComponent = lazy(() => import('./OtherComponent '))
const MyComponent = () => {
return <div>
<Suspense fallback={ <div>loading...</div> }>
<OtherComponent />
</Suspense>
</div>
}
使用React.lazy & Suspense
还可以进行路由懒加载
import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));
const App = () => (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
</Switch>
</Suspense>
</Router>
);
loadable-components
React.lazy & Suspense
不支持服务端渲染,
如果需要服务端渲染,可以使用loadable-components
懒加载库。
git地址
https://github.com/gregberge/loadable-components
安装
npm install @loadable/component
使用
import loadable from '@loadable/component'
const OtherComponent = loadable(() => import('./OtherComponent'))
function MyComponent() {
return (
<div>
<OtherComponent />
</div>
)
}
Context
先回顾一下React中的组件间通信方法:
父传子
父组件通过props
向子组件传值
子传父
父组件通过props
向子组件传递一个方法
子组件把要传的值作为参数调用该方法
父组件通过该方法传入的参数,获取到值
兄弟间
先子传父
,再父传子
如果父子层级较多,那么需要每层组件都要手动传递一次或多次props,才能拿到想要的值。
这就很离谱!!!
Context
就是用来解决这个问题的。
它提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。
它可以让数据在整个组件树之间“共享”
在Class组件中的使用方法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="root"></div>
<!-- 开发模式下用development版本 -->
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script type="text/babel">
// 创建一个Context 并设定初始值"light"
const Context = React.createContext('light')
class Com1 extends React.Component {
render() {
return (
// 使用Provider传递值,在Com1组件树中的所有组件都能直接使用传递的值
// 设定传递的值value为"dark"
// 注意,这里的属性必须是value
<Context.Provider value="dark">
<Com2 />
</Context.Provider>
)
}
}
// 中间使用不到这个值的组件就不用管了
class Com2 extends React.Component {
render() {
return <div>
<Com3 />
</div>
}
}
class Com3 extends React.Component {
// 指定contextType 读取传递的值context
// this.context就是传递的值
static contextType = Context
render() {
return <p>{this.context}</p>
}
}
const wrap = document.querySelector('#root')
const root = ReactDOM.createRoot(wrap)
root.render(<Com1 />)
</script>
</body>
</html>
在函数组件中使用useContext
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="root"></div>
<!-- 开发模式下用development版本 -->
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script type="text/babel">
const {useContext} = React
// 创建一个Context 并设定初始值"light"
const Context = React.createContext('light')
const Com1 = () => {
return (
// 使用Provider传递值,在Com1组件树中的所有组件都能直接使用传递的值
// 设定传递的值value为"dark"
// 注意,这里的属性必须是value
<Context.Provider value="dark">
<Com2 />
</Context.Provider>
)
}
// 中间使用不到这个值的组件就不用管了
const Com2 = () => {
return <div>
<Com3 />
</div>
}
const Com3 = () => {
// 哪个组件要使用传递的值,就在哪个组件中使用useContext来获取
const context = useContext(Context)
return <p>{context}</p>
}
const wrap = document.querySelector('#root')
const root = ReactDOM.createRoot(wrap)
root.render(<Com1 />)
</script>
</body>
</html>
缺点
会导致组件复用性变差
错误边界(Error Boundaries)
什么是错误边界?
错误边界是一种 React 组件,这种组件可以捕获并打印发生在其子组件树任何位置的 JavaScript 错误,
并且,它会渲染出备用 UI,而不是渲染那些崩溃了的子组件树。
错误边界在渲染期间、生命周期方法和整个组件树的构造函数中捕获错误。
错误边界解决了什么问题?
部分UI组件内的js代码出现错误,会导致页面崩溃,整个页面无法渲染。
错误边界就是捕获和打印这些错误,并在出现该问题时,渲染备用UI
PS:
对开发人员来说,它的作用就是快速定位错误问题的位置。
举个例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="root"></div>
<!-- 开发模式下用development版本 -->
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script type="text/babel">
const Child = () => {
return <div>
{haha}
</div>
}
const App = () => {
return <Child />
}
const wrap = document.querySelector('#root')
const root = ReactDOM.createRoot(wrap)
root.render(<App />)
</script>
</body>
</html>
在示例的Child
组件中,我不小心把字符串"haha"
写成了变量haha
导致页面出现了问题,无法渲染。
现在,我给组件加上一个错误边界:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="root"></div>
<!-- 开发模式下用development版本 -->
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script type="text/babel">
class ErrorBoundary extends React.Component {
constructor(props) {
super(props)
this.state = {
hasError: false
}
}
static getDerivedStateFromError(error) {
// 更新 state 使下一次渲染能够显示降级后的 UI
return { hasError: true }
}
componentDidCatch(err, errInfo) {
console.log(err, errInfo)
}
render() {
if(this.state.hasError) {
return <h3>出错啦...</h3>
}
return this.props.children
}
}
const Child = () => {
return <div>
{haha}
</div>
}
const App = () => {
return <ErrorBoundary>
<Child />
</ErrorBoundary>
}
const wrap = document.querySelector('#root')
const root = ReactDOM.createRoot(wrap)
root.render(<App />)
</script>
</body>
</html>
错误边界组件ErrorBoundary
中捕获到了错误并进行了定位:
此时,js代码出现了错误,但是页面渲染了备用UI,
页面显示内容出错啦...
使用错误边界有哪些注意事项?
1. 错误边界组件必须是class组件
2. 错误边界仅可以捕获其子组件的错误,它无法捕获其自身的错误
3. 它仅用于开发环境,在生产环境必须将其禁用 。
4. 它无法捕获事件处理,因为React不需要它来捕获
5. 无法捕获异步代码中的错误
6. 无法捕获服务端渲染中的错误
提醒:
自 React 16 起,任何未被错误边界捕获的错误将会导致整个 React 组件树被卸载。
Fragment
Fragments 允许你将子列表分组,而无需向 DOM 添加额外节点。
先来看一个不使用Fragment的例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="root"></div>
<!-- 开发模式下用development版本 -->
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script type="text/babel">
const App = () => {
return <div>
<p>hello react !</p>
</div>
}
const wrap = document.querySelector('#root')
const root = ReactDOM.createRoot(wrap)
root.render(<App />)
</script>
</body>
</html>
页面渲染效果:
可以看到, 页面中多了一个无意义的div标签
使用Fragment:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="root"></div>
<!-- 开发模式下用development版本 -->
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script type="text/babel">
const App = () => {
return <React.Fragment>
<p>hello react !</p>
</React.Fragment>
}
const wrap = document.querySelector('#root')
const root = ReactDOM.createRoot(wrap)
root.render(<App />)
</script>
</body>
</html>
再看下页面渲染结果:
渲染正常,且没有无意义的div标签了。
它还有个短语法:
<></>
使用的时候一直报错,略过。
在当前版本中,React.Fragment
仅支持key
属性。
举例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="root"></div>
<!-- 开发模式下用development版本 -->
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script type="text/babel">
const App = () => {
return <div>
{
[1, 2, 3].map((item, index) => {
return <React.Fragment key={index}>
<p>hello, react {item}</p>
</React.Fragment>
})
}
</div>
}
const wrap = document.querySelector('#root')
const root = ReactDOM.createRoot(wrap)
root.render(<App />)
</script>
</body>
</html>