前言
在上篇文章React 导读(一)中学习到了写第一个 Web 组件,这篇文章将继续之前的目录,开始新的知识点补充:
- [x] React 如何编写 Hello World!
- [x] React 中三个最基础、最重要的东西
- [x] React 中的 JSX
- [x] 你的第一个 Web 组件
- [ ]React 中最开始需要关注的生命周期
- [ ]React 一个组件集合的简单交互
- [ ]React 开始一个项目的一点建议
- [ ]React 简单的项目结构组织
五、React 中最开始需要关注的生命周期
其实在学习 React 之前,就应该了解目前前端推荐的是组件化开发的方式,React 是让组件化更加简单的库。那么组件开发必不可少的就是生命周期,说直白一点就是运行组件的过程是 React 来做,运行过程中需要有一些代码钩子来让我们去调用,在组件执行的某一个地方去执行我们自己写的代码。这里先介绍拥有的生命周期钩子,下面的方法 constructor
和 render
不属于生命周期,我按功能分类了一下,也就是学习的时候不一定要按部就班,应该以学习之后能真正写一些东西为目标:
(1) 与组件挂载相关的方法,包括构造函数
- constructor
- componentWillMount
- componentDidMount
- componentWillUnmount
最常用的生命周期应该是最后 2 个,constructor
和 componentWillMount
目前先理解成能满足的功能大体相同,如果这里解释太复杂不太好。
对于最开始关注的是:this.state
的初始化以及 ajax
在哪里请求。
this.state
在 constructor 进行初始化,ajax
推荐在componentDidMount
中进行请求。
-
componentDidMount
就是在组件已经挂载到 DOM 中后的钩子,可以理解为 jQuery 中提供的ready
方法。 -
componentWillUnmount
是在组件即将被卸载前一刻的钩子,一般用于取消componentDidMount
中订阅的事件等作用,清理一些不要的变量等,避免内存泄漏。
下面通过一个简单的例子说明一下:
先有一个 foods.json
文件来模拟请求的后台数据。
[
{
"id": 1,
"name": "香蕉"
},
{
"id": 2,
"name": "苹果"
},
{
"id": 3,
"name": "猕猴桃"
}
]
// 1. 挂载相关的生命周期
class CycleMount extends React.Component {
constructor() {
super();
this.state = {
foods: []
};
console.log('1. constructor 执行了...');
}
componentDidMount() {
// 这里使用原生的 fetch API 进行 ajax 请求,你也可以使用 $.ajax 进行请求,原理是一样的,重点是关注 setState 的地方
fetch('/mock/foods.json',
{
method: 'GET',
headers: new Headers({
'Accept': 'application/json'
})
}
).then(dataResult => {
if(dataResult.status === 200) {
return dataResult.json();
} else {
return [];
}
}).then(data => {
// 这里的 data 就是 foods.json 里面的数据
// 调用 setState 来更新 render 里面调用的 this.state 的值
this.setState({
foods: data
});
});
console.log('2. componentDidMount 执行了...');
}
render() {
// foods 是一个数组,map 方法是数组自带的方法,可以查询相关 api
const foodItems =
this.state.foods.map(food => {
return (<li key={food.id}>{food.name}</li>);
});
// 这里是返回的最终组件结构
return (
<ul>
{foodItems}
</ul>
);
}
}
上面有了完整的注释,也能看到基本上项目中可能会将代码写到何处,我也打了两个日志,来识别到底是谁先执行,结果可以自己运行一下,执行顺序就是我标记的1,2。
好了,基本的学习了,可以自己动手试试订阅一个事件,然后在卸载的时候取消这个事件。
(2) 优化相关
- shouldComponentUpdate
这个方法比较重要,但是我这里不会介绍得太过于复杂,太复杂只会让重要的部分不那么突出。这个方法的返回值是一个 boolean
类型,分别代码的意义:
-
true
组件应该更新,执行render
方法以及相关生命周期; -
false
组件状态没有更新,不执行render
等方法,意味着网页界面不会改变。
那么它直观上的作用是能够通过返回值来决定界面是否改变,实际的意义就是当我们知道当前 oldState = this.state
的值和新的 newState = this.state
值完全相等的时候(或者是新传入的 props)就不用再浪费性能去重新渲染组件了。
API 上的定义是这样的:
/* nextProps: 新的 props, nextState: 新的 state */
shouldComponentUpdate(nextProps, nextState): boolean
举个例子来直观说明一下:
class ComponentOptimize extends React.Component {
state = {
count: 0
};
shouldComponentUpdate(nextProps, nextState) {
// 当 count > 10 的时候就不能再重新渲染组件了
if(nextState.count > 10) {
return false;
}
return true;
}
handleUpdateCount() {
console.log('我点了一下哦!');
this.setState(prevState => {
return {
count: prevState.count + 1
};
});
}
render() {
return (
<div onClick={this.handleUpdateCount.bind(this)} style={{cursor: 'pointer'}}>
<h1>{this.state.count}</h1>
</div>
);
}
}
你会发现 10 过后界面就没有再更新过了,这样应该很直观了。
(3) Props 相关的生命周期
- componentWillReceiveProps
这个主要是在组件的 props 传入新的值后被调用,不管是不是传的一样的值或者 shouldComponentUpdate
返回了 false
,看下例子吧:
class Cat extends React.Component {
componentWillReceiveProps(nextProps) {
console.log('改一次我执行一次!');
}
shouldComponentUpdate(nextProps, nextState) {
// 改的名字一样的时候
return this.props.name !== nextProps.name;
}
render() {
console.log('猫改了一次名字!');
return (
<h1>我有新名字了!{this.props.name}</h1>
);
}
}
class App extends React.Component {
state = {
catName: '噜噜'
};
handleChangeCatName() {
const catNames = ['噜噜', '小白', '小黄', '小黑', '皮卡丘'];
const catIndex = this.getSomeOneIndex();
this.setState({
catName: catNames[catIndex]
});
}
getSomeOneIndex() {
return Math.floor(Math.random() * 5 + 0);
}
render() {
return (
<div>
{/* 给 Cat 传新的名字 */}
<Cat name={this.state.catName} />
<button onClick={this.handleChangeCatName.bind(this)}>点我给猫咪取新名字!</button>
</div>
);
}
}
最后肯定每次点击按钮都会输出这句的结果 console.log('改一次我执行一次!');
。
(4) 更新组件相关
- componentWillUpdate
- componentDidUpdate
因为都是讲解 API,所以国际惯例的先看下 API 的定义吧:
// 组件更新前执行
componentWillUpdate(nextProps, nextState)
// 组件更新后执行
componentDidUpdate(prevProps, prevState)
可以从定义中看出,它们都接受了两个参数:props && state,不过看变量前缀能够联想点什么。
暂时想不到什么实际项目的例子,随便假设点内容吧。不过这里需要注意的地方是:
- 1. 这两个方法里面都不要调用 setState!
- 2. 第一次初始化组件 render 的时候不会执行
- 3. shouldComponentUpdate 返回 false 不会执行
第一条的原因:容易形成一个递归的调用,不作就不会死...所以尽量不要在这里调~目前还没有碰到需要在这里调的需求。
第二条的原因:额,说好的更新才调,初始化不调用是符合逻辑的。
第三条的原因:额,这 2 个钩子是与组件更新相关的,所以也符合逻辑的,组件是否更新就是靠 shouldComponentUpdate
返回值。
在上面 Cat
的例子中加入下面的代码可以看下结果:
componentWillUpdate() {
console.log('componentWillUpdate 执行了!')
}
componentDidUpdate() {
console.log('componentDidUpdate 执行了!')
}
(5)组件错误
- componentDidCatch
就是在组件发生异常的时候可能会被调用的钩子,需要注意的有下面的地方:
- 只能在父级组件捕获子组件的异常;
- 如果异常被
try...catch
包裹父级组件的钩子就不会执行了。
看个例子吧:
class Cat extends React.Component {
componentWillReceiveProps(nextProps) {
// 这里手动抛一个异常,触发我们的钩子 componentDidCatch
throw new Error('miao miao~');
}
render() {
let miao = this.props.name;
return (
<div>
{miao}
</div>
);
}
}
class App extends React.Component {
state = {
catName: '噜噜',
isError: false,
};
handleChangeCatName() {
const catNames = ['噜噜', '小白', '小黄', '小黑', '皮卡丘'];
const catIndex = this.getSomeOneIndex();
this.setState({
catName: catNames[catIndex]
});
}
getSomeOneIndex() {
return Math.floor(Math.random() * 5 + 0);
}
componentDidCatch(error, info) {
console.log(error, info);
if(error) {
// 如果有错误信息,就重新渲染一下组件,可能是更好的交互
this.setState({
isError: true
});
}
}
render() {
return (
<div>
<Cat name={this.state.catName} />
{!this.state.isError ?
<button onClick={this.handleChangeCatName.bind(this)}>点我给猫咪取新名字!</button> :
<p>不要奴才给我取名字了!</p>
}
</div>
);
}
}
(6) 渲染相关
- render
额...这个不想写了,先睡觉吧~应该写了这么多个小例子也差不多了~可以动手试试哦!还不清楚的可以 Google 一下,你就知道。
生命周期很重要,其实学到这里也差不多可以上手写点项目熟练一下了,其他的更多是思维和编程方面的东西,周期的篇幅单独来一篇吧~其他主题之后再继续吧!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。