生命周期
let div = document.createElement('div')
//这是div的 create/construct 过程
div.textContent='hi'
//这是初始化state(这不是生命周期)
document.body.appendChild(div)
//这是div的 mount 过程
div.textContent='hi2'
//这是div的 update 过程
div.remove()
//这是div的 unmount 过程
- React组件也有这些过程,可以看作高级的div
1.React的生命周期
- 在对应的时期会调用相应函数
constructor() //创建组件时调用的函数:在这里初始化state
shouldComponentUpdate() //是否更新组件:return false 阻止更新,还要return true
render() //渲染:创建虚拟DOM
componentDidMount() //组件挂载完成:已经出现在页面
componentDidUpdate() //组件更新完成
componentWillUnmount() //组件将要卸载
1.constructor
- 初始化 props
- 初始化 state
注意:这里的初始化只能用 this.state,不能用setState初始化 - 用来写 bind this
constructor(){
//其他代码略
this.onClick = this.onClick.bind(this)
}
//等价于新语法:箭头函数不改变this
onClick = ()=>{}
constructor(){//其他代码略}
- constructor可不写
只有在需要初始化state才需要些constructor
只有props不用写
2.shouldComponentUpdate
- 返回 true 表示不阻止UI更新
- 返回 false 表示阻止UI更新
- shouldComponentUpdate接收两个参数
arguments[0]:newProps
arguments[1]:newState
不用也要占位
旧的数据就是:this - shouldComponentUpdate有什么用?
答:它允许我们手动判断是否要进行组件更新,我们可以根据应用场景灵活的设置返回值,避免不必要的更新
——Vue就不需要判断,因为它监听数据,我们修改了数据它就知道自动更新 - 代码
如:当产生的新对象和旧对象内容一样(地址是不同的),就阻止更新
shouldComponentUpdate(newProps,newState){
//这里的newProps虽然没有用到,但不能删掉,这是占位的
//因为要用的是第二个参数,根据位置来定的:arguments[1]
if(newState.n === this.state.n){
//当新state和旧state一样,就不更新
return false
}else{
return true
}
}
- 图解
多余的一步就是:render函数调用
- React内置该功能:PureComponent
——有了这个就不用对比新旧state返回ture和false,能在绝大多数情况代替shouldComponentUpdate
PureComponent 会在 render 之前对比新 state 和旧 state 的每一个 key,以及新 props 和旧 props 的每一个 key。
如果所有 key 的值全都一样,就不会 render;如果有任何一个 key 的值不同,就会 render。
class App extends React.PureComponent{
//代码略
}
3.render
- 展示视图
return (<div>...</div>)
1.这不是DOM元素,而是一个表示DOM元素的对象
2.称这个对象为:虚拟DOM
3.虚拟DOM:就是把HTML转化为了对象
——对象里面有属性:type、props等 - 只能有一个根元素
意思是:标签只能嵌套,不能并列 - 如果有两个根元素,就要用
<React.Fragment>
包起来
1.这个元素只是用来做占位,渲染的时候这个元素是不存在的
2.不需要创建一个多余的div <React.Fragment>
可以缩写成<></>
return (
<>
<div>hi</div>
<div>App
<div>
{this.state.n}
<button onClick={this.onClick}>+1</button>
</div>
</div>
</>
)
- 小技巧:render里面可以写
把render(){}
当做一个JS正常函数,将标签看作变量来写
——赋值:message = <div>hi</div>
,return:{ message }
1.if...else
2.?:
表达式
3.不能直接写for循环:因为for循环没有return,在第一次return的时候,整个循环就结束了
——解法一:创建一个空数组,遍历的标签都 push 到空数组里,return 数组
——解法二(推荐):使用map,map有返回值
array.map(n=><span>{n}</span>)
?:
表达式
render(){
return (
<>
{ this.state.n%2===0?<div>偶数</div>:<div>奇数</div>}
<button onClick={this.onClick}>+1</button>
</>
)
}
- array.map(循环)
注意:每个元素都有一个独一无二的key
render(){
//使用map对数组元素进行包裹
return this.state.array.map(n=><span key={n}>{n}</span>)
}
4.componentDidMount()
- 组件已经挂载
- 在元素插入页面后执行代码,这些代码依赖DOM
比如:想要获取div的高度
document.getElementById等一系列的代码中,document是文档,必须要元素出现在页面中才能获取 - 获取的两种方法
1.ID:document.getElementById(‘xxx’)
2.Ref:可以解决id冲突的问题,因为ref相当于是实例上的属性
——比ID更装逼的一种写法
class Demo extends React.Component {
divRef = undefined; // 好习惯,提前写在这里,告诉别人这里有个ref
constructor(props) {
super(props);
// 创建引用
this.divRef = React.createRef();
}
componentDidMount() {
// 页面挂载后才能获取到元素
const div = this.divRef.current;
const { width } = div.getBoundingClientRect();
}
render() {
return <div ref={this.divRef}>你好,这里是引用组件</div>;
}
}
- 此处可以发起加载数据的AJAX请求
问:如果想要发送加载数据的AJAX的请求,应该放在哪个生命周期里
答:componentDidMount() - 首次渲染会执行此钩子
- componentDidMount()函数中,没有接收参数
任何东西通过 this. 来获取
componentDidMount(){
//获取到当前的div赋值
const div = this.divRef.current
//析构赋值:获取div的宽度
const {width} = div.getBoundingClientRect()
this.setState({width})
}
render(){
return (
<div ref={this.divRef}>Hello world,宽度为{this.state.width}px</div>
)
}
5.componentDidUpdate()
- 在视图更新后执行代码
- 此处也可以发起AJAX请求,用于更新数据请求
比如:用户ID改变,获取新的用户信息 - 首次渲染不会执行此钩子
因为首次渲染不会更新任何东西 - 在此处setState可能会引起无限循环
1.在更新里改数据,改了又更新
2.因此:最好不要再 update 里面 setState,除非有条件判断(if)可以跳出循环 - 若 shouldComponentUpdate 返回false,则不会触发此钩子
阻止更新 - 接收3个参数
1.preProps
2.prevState
3.snapshot(不用)
6.componentWillUnmount
- 组件将要被移出页面然后销毁时执行代码
1.移出页面:DOM到内存
2.销毁:从内存中干掉 - unmount 过的组件不会再次 mount
1.死后不能复生
2.只能重新创建 - 使用(一般不用,只是占点内存)
这个是用于消除之前做过可能会产生后果的事情
如果:在componentDidMount中创建了事件监听/Timer/AJAX请求
,那么就要在componentWillUnmount中取消监听/Timer/AJAX请求
。 - 原则:谁污染谁治理
7.总结
- 一般不会手动销毁,只有后期有路由的时候才会销毁