React进阶
React进阶
异步组件(懒加载)
目的是让组件需要呈现时再加载。假设Other
是需要异步加载的组件,在其父组件引入时应写成:
const Other = React.lazy(() => import('./Other'))
另外,在render中引入时需要用suspense包裹并制定fallback:
<Suspense fallback={ <div>Loading...</div> }>
<Other />
</Suspense>
其中fallback是在加载过程中显示的内容。当然,这个内容也可以是一个单独的组件。
Suspense可以包多个组件,只要是异步都可以一并包进去。
如果想要观察效果,就按f12,把network throttling调慢然后刷新就能看到加载过程了:

Context
主要用来解决组件间的数据传递。Context(上下文对象) 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。在一个典型的 React 应用中,数据是通过 props 属性自上而下(由父及子)进行传递的,但此种用法对于某些类型的属性而言是极其繁琐的(例如:地区偏好,UI 主题),这些属性是应用程序中许多组件都需要的。Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。
假设目前的组件树为:
Theme -> Middle -> ThemedButton
此时需要从Theme中传一个值到ThemedButton中并显示在按钮上。传统做法是用Middle来传递props,而用Context的写法如下:
首先在Theme(传递者)中导出两个对象:
export const { Provider, Consumer } = React.createContext('Default data')
之后用Provider
包裹它的子组件,value
就是要往下传的值:
<Provider value={ this.state.message }>
<Middle />
</Provider>
而在接收者组件中,先引入刚才导出的Consumer
:
import { Consumer } from './Theme'
渲染时的写法是:
<Consumer>
{
value => {
return <button>{ value }</button>
}
}
</Consumer>
同样用Consumer
包裹需要使用的位置,之后把值写成一个箭头函数的形式,内部return出使用后的模板。
不要滥用
Context 主要应用场景在很多不同层级的组件需要访问同样一些的数据。请谨慎使用,因为这会使得组件的复用性变差。
错误边界
顾名思义,这个就是把可能出错的地方用一个边界包起来,它就算出错也不影响其它东西渲染:
return (
<div>
<p>static content</p>
<ErrorBoundary>
<List />
</ErrorBoundary>
</div>
)
这个例子中,就算<List />
报错(可能由于列表没有及时从后端传回或遇到500错误拿不到列表),页面上也不会有错误信息,static content也会正常显示。这个具体怎么实现的呢?ErrorBoundary
本身也是一个组件,它的state里包含是否出错的判断:
this.state = {
hasError: false
};
之后会依据如下代码来更新错误状态以及做对应处理:
static getDerivedStateFromError(error) {
return {
hasError: true
};
}
componentDidCatch(error, errorInfo) {
// 实际应该写发送错误到后端的逻辑
console.log(error, errorInfo);
}
render() {
if(this.state.hasError) {
return <h1>ERROR!</h1>
}
return this.props.children;
}
在遇到错误时执行生命周期钩子函数static getDerivedStateFromError(error)
,里面把错误标记设为true。render中可以按错误标记来决定渲染谁,如果没错就渲染它里边的东西。
注意,这里用到了两个生命周期钩子:static getDerivedStateFromError(error)
和 componentDidCatch(error, errorInfo)
,分别拿来更新state和向后台发错误数据,他俩是不能合并的。
React 需要先在渲染阶段确定备用 UI,然后在提交阶段安全地执行副作用。static getDerivedStateFromError(error)
在渲染阶段调用,是纯函数,它不能执行副作用;而componentDidCatch(error, errorInfo)
在提交阶段调用,可以执行副作用。
ref与DOM
ref可以理解为对特定DOM做的唯一标识。比如下面的例子中,把一个p标签绑定上ref,就能在DOM构建完成后读取到:
export default class RefMain extends Component {
constructor() {
super();
this.text = React.createRef();
}
componentDidMount() {
console.log(this.text.current.innerHTML);
}
render() {
return (
<div>
<p ref={ this.text }>Default content.</p>
</div>
)
}
}
一般情况下无需直接操作DOM,但是如果必须操作,就要用上面的ref方式。
Fragment(碎片)
React 中的一个常见模式是一个组件返回多个元素,Fragments 允许你将子列表分组,而无需向 DOM 添加额外节点:
render() {
return (
<div></div>
<>
<ChildA />
<ChildB />
<ChildC />
</>
);
}
说人话就是当你需要包裹一组标签但又觉得没必要用div或span增加一层标签的时候用空标签就对了。这样一来三个Child确实被包裹了,但是它们仍然和那个div同级,它们不算一个单独节点。
高阶组件
高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。
- 高阶组件本身是函数
- 高价组件的参数和返回值都是组件
基本写法
例如这样定义一个高阶组件:
const withFetch = (ComposeComponent) => {
return class extends Component {
render() {
return (
<ComposeComponent {...this.props} />
)
}
}
}
同时把这个组件作为参数传进去:
class MyData extends Component {
render() {
return (
<div>
MyData: {this.props.data}
</div>
)
}
}
就能获得一个高阶组件实例:
const WithFetch = withFetch(MyData);
最后使用高阶组件实例时还是相同的用法:
export default class Main extends Component {
render() {
return (
<div>
<WithFetch data='Advanced component' />
</div>
)
}
}
可复用性的体现
从刚才的例子不难看出,高阶组件有点像类与实例的关系。假设有很多组件都用到同一个生命周期钩子,就可以把它提取到高阶组件中:
const withFetch = (ComposeComponent) => {
return class extends Component {
componentDidMount() {
console.log('public part');
}
render() {
return (
<ComposeComponent {...this.props} />
)
}
}
}
这样一来之后的所有组件都可以写成这个高阶组件的实例,而不需要再单独写一遍钩子函数:
const WithFetch1 = withFetch(MyData1);
const WithFetch2 = withFetch(MyData2);
// ...
具体的应用例子
封装一个带有网络请求的组件WithFetch.jsx
:
const WithFetch = (url) => (View) => {
return class extends Component {
constructor() {
super();
this.state = {
data: null,
loading: true
};
}
componentDidMount() {
fetch(url)
.then(res => res.json())
.then(data => {
this.setState(() => ({
loading: false,
data
}));
})
}
render() {
if (this.state.loading) {
return <div>Loading...</div>
} else {
return <View data={this.state.data} />
}
}
}
}
它接收两个参数:要拿数据的url和要用的组件。当传入url后,它会在渲染完DOM后内部访问该url。另外,只有当fetch成功后才会把loading
置为false,渲染时也是按这个依据判断是否渲染组件,保证页面不会崩。渲染时通过props传fetch到的数据。使用方法是:
const UseAdvComp = WithFetch('http://iwenwiki.com/api/blueberrypai/getIndexMovement.php')(props => {
return (
<div>
<ul>
{
props.data.movement.map((element, index) => {
return <li key={ index }>{ element.content }</li>
})
}
</ul>
</div>
)
})
注意,要求第二个参数是一个View组件,这里直接写成一个箭头函数了。
严格模式
StrictMode
是一个用来突出显示应用程序中潜在问题的工具。与 Fragment
一样,StrictMode
不会渲染任何可见的 UI。它为其后代元素触发额外的检查和警告。生产模式下无效。
使用
直接加在
<React.StrictMode>
<div>
<ComponentOne />
<ComponentTwo />
</div>
</React.StrictMode>
能检测的问题
- 识别不安全的生命周期
- 关于使用过时字符串 ref API 的警告
- 关于使用废弃的 findDOMNode 方法的警告
- 检测意外的副作用
- 检测过时的 context API
- 确保可复用的 state
PropTypes类型检查
其实就是一个props类型的验证器,具体写法是:
PropTypesDemo.propTypes = {
title: PropTypes.string
}
PropTypesDemo.defaultProps = {
age: 0
}
注意要先引入:
import PropTypes from 'prop-types'
组件性能优化
阻止不必要的重新渲染
一个简单的例子,父组件有一个由按钮触发的增加值事件,同时其内部有个子组件:
export default class OptimizeMain extends Component {
constructor() {
super();
this.state = {
message: 'notice',
count: 0
}
}
handleIncrement = () => {
this.setState(() => ({
count: this.state.count + 1
}));
}
render() {
console.log('====== Main render ======');
return (
<div>
<h3>Optimize</h3>
<p>{ this.state.count }</p>
<button onClick={ this.handleIncrement }>Increase</button>
<Child1 message={ this.state.message } />
</div>
)
}
}
子组件中仅在被渲染时打印一条日志:
export default class Child1 extends Component {
render() {
console.log('====== Child1 render ======');
return (
<div>
<p>{ this.props.message }</p>
</div>
)
}
}
测试时发现,每当父组件点击按钮触发重绘,子组件也会被重新渲染,即使它并不需要被更新:

这是因为,react中只要父组件重新渲染,那所有的子组件都要重新渲染。组件多的时候这是很吃性能的。想要避免这件事,可以利用生命周期钩子shouldComponentUpdate(nextProps, nextState)
,两个参数分别是更新后的props和state值。这样就可以加判断,如果更新前后值没变,那就返回一个false,不更新了:
shouldComponentUpdate(nextProps, nextState) {
if(nextProps.message === this.props.message) return false;
}
但是这样写的话,需要为所有数据加是否更新的判断,有些麻烦,有一个简单写法是继承React.PureComponent
,它会对数据浅比较(即只比较值,不看类型内存啥的):
export default class Child2 extends React.PureComponent {
render() {
return (
<div>
<p>{ this.props.message }</p>
</div>
)
}
}
这样就不用写判断了。
需要优化的情况
- 定时器需要在组件销毁时取消
- 网络请求在组件销毁时取消
- 持续的事件监听在组件销毁时销毁处理函数
如果不这么干会报错。一般在componentWillUnmount()
钩子中执行销毁逻辑。