旧版生命周期
React的生命周期分为三个阶段:挂载、渲染、卸载。
React生命周期分为两类:挂载卸载过程、更新过程。
// 旧版本
export default class App extends Component {
constructor (props) {
super(props)
// 设置初始化状态
// 给组件的非钩子函数 bind this
// 设置ref
this.testRef = React.createRef
console.log('constructor')
this.state = { count: 1 }
}
componentWillMount () {
// 在新版本中废弃
console.log('componentWillMount')
}
render () {
// 类组件必不可少的
console.log('render')
return (
<div>
<button onClick = { () => {
this.setState({
count: this.state.count+1
})
}}>加1</button>
{ this.state.count }
</div>
)
}
componentDidMount () {
// 数据请求,DOM操作,实例化
console.log('componentDidMount')
}
// 运行时阶段
componentWillReceiveProps () {
// 监听组件的数据的变化
// 子组件接收父组件的数据 新版已经淘汰
console.log('componentWillReceiveProps')
}
shouldComponentUpdate () {
// 提升性能的关键,如果不需要更新,返回false ***
// 要不不写要写必写返回值
// 父动子动 ==》 父动子不动-子中添加return false
console.log('shouldComponentUpdate')
// return true
return false
}
componentWillUpdate () {
// 没什么用
console.log('componentWillUpdate')
}
// render () {} 与 初始化相同
componentDidUpdate () {
// DOM操作,实例话的操作
// 在特定条件下可以进行数据的请求
console.log('componentDidUpdate')
}
// 销毁
componentWillUnmount () {
// 类似于vue 的beforeDestory
console.log('componentDidUpdate')
}
// 错误
componentDidCatch () {
// 当前组件以及后代组件发生错误时触发该钩子
console.log('componentDidCatch')
}
}
新版生命周期
export default class App extends Component {
constructor (props) {
super(props)
// 设置初始化状态
// 给组件的非钩子函数 bind this
// 设置ref
this.testRef = React.createRef
console.log('constructor')
this.state = { count: 1 }
}
//
static getDerivedStateFromProps () {
// 一般人用不到
// state 的值在任何时候都取决于 props
console.log('getDerivedStateFromProps')
return null
}
render () {
// 类组件必不可少的一个
console.log('render')
return (
<div>
<button onClick = { () => {
this.setState({
count: this.state.count+1
})
}}>加1</button>
{ this.state.count }
</div>
)
}
componentDidMount () {
// 数据请求,DOM操作,实例化
console.log('componentDidMount')
}
// 运行时阶段 通过props获取或更新state的值
// static getDerivedStateFromProps (){}
shouldComponentUpdate () {
// 提升性能的关键,如果不需要更新,返回false
// 要不不写要写必写返回值
// 父动子动 ==》 父动子不动-子中添加return false
console.log('shouldComponentUpdate')
// return true
return false
}
getSnapshotBeforeUpdate () {
// 一般人用不到
// 出现在 UI 处理中,如需要以特殊方式处理滚动位置的聊天线程等
//在react `render()`后的输出被渲染到DOM之前被调用。它使您的组件能够在它们被潜在更改之前捕获当前值(如滚动位置)。这个生命周期返回的任何值都将作为参数传递给componentDidUpdate()。
console.log('getSnapshotBeforeUpdate')
return 666;
}
// render () {} 与 初始化相同
componentDidUpdate (prevProps,prevState,snapshot) {
// DOM操作,实例化的操作
// 在特定条件下可以进行数据的请求
console.log('componentDidUpdate')
}
// 销毁
componentWillUnmount () {
// 类似于vue 的beforeDestory
console.log('componentDidUpdate')
}
// 错误
componentDidCatch () {
// 当前组件以及后代组件发生错误时触发该钩子
console.log('componentDidCatch')
}
//
static getDerivedStateFromError () {
// 后代组件抛出错误后被调用
console.log('getDerivedStateFromError')
return null
}
}
常用的生命周期钩子
export default class App extends Component {
constructor (props) {
super(props)
// 设置初始化状态
// 给组件的非钩子函数 bind this
// 设置ref
this.testRef = React.createRef
console.log('constructor')
this.state = { count: 1 }
}
render () {
// 类组件必不可少的一个
console.log('render')
return (
<div>
<button onClick = { () => {
this.setState({
count: this.state.count+1
})
}}>加1</button>
{ this.state.count }
</div>
)
}
componentDidMount () {
// 数据请求,DOM操作,实例化
console.log('componentDidMount')
}
// 代替 旧版本中 componentWillReceiveProps 钩子
// render () {} 与 初始化相同
componentDidUpdate () {
// DOM操作,实例话的操作
// 在特定条件下可以进行数据的请求
console.log('componentDidUpdate')
}
}
componentDidUpdate 实现文字渐变效果
class App extends Component {
constructor() {
super();
this.state = {
opacity: 1//不透明
}
}
componentDidMount() {
setInterval(() => {
//报错,this指向不对,目前指向了window,可以使用箭头函数
let myOpacity = this.state.opacity;
myOpacity -= 0.05;
//如果透明度到0,重新置1
if (myOpacity <= 0) {
myOpacity = 1;
}
// 将改变的值保存到 state
this.setState({
opacity: myOpacity
})
}, 100);
}
render() {
return (
<div>
<h1 style={{ "opacity": this.state.opacity }}>hello world</h1>
</div>
)
}
}
shouldComponentUpdate 实例
src/App.jsx
class App extends Component {
constructor(arg) {
super();
this.state = {
count: 0,
name: "lili"
}
}
setCount = () => {
this.setState({
count: this.state.count + 1
})
}
changeName = () => {
this.setState({
name: "huaer"
})
}
render() {
return (
<div>
<p>点击按钮加一</p>
<button onClick={() => this.setCount()}>点击加一操作</button>
<h1>{this.state.count}</h1>
<button onClick={this.changeName}>修改Foo传递的值</button>
<Foo name={this.state.name} />
</div>
)
}
}
src/Foo.jsx
class Foo extends Component {
/*
nextProps 将要传过来的props的值
nextState 将要传过来的state的值
shouldComponentUpdate 作用:阻止了未发生变化的组件的再次渲染,增加了性能
局限性:对于传递的参数是对象或数组时,判断不出来对象或数组的内部元素是否发生了变化
*/
shouldComponentUpdate(nextProps, nextState) {
console.log(nextProps, nextState);
if (nextProps.name === this.props.name) {
return false;
} else {
return true;
}
//return true;
}
render() {
console.log("foo组件的渲染运行了");
return (
<div>Foo组件</div>
)
}
}
生命周期详解
1、初始化
在组件初始化阶段会执行
-
constructor
-
static getDerivedStateFromProps()
-
componentWillMount() / UNSAFE_componentWillMount()
-
render()
-
componentDidMount()
2、更新阶段
-
props
或state
的改变可能会引起组件的更新,组件重新渲染的过程中会调用以下方法: -
componentWillReceiveProps() / UNSAFE_componentWillReceiveProps()
-
static getDerivedStateFromProps()
-
shouldComponentUpdate()
-
componentWillUpdate() / UNSAFE_componentWillUpdate()
-
render()
-
getSnapshotBeforeUpdate()
-
componentWillUnmount()
-
componentDidCatch()
-
componentDidUpdate()
3、卸载阶段
-
componentWillUnmount()
4 、错误处理
-
componentDidCatch()
5、各生命周期详解
(1) constructor(props)
React组件的构造函数在挂载之前被调用。在实现React.Component
构造函数时,需要先在添加其他内容前,调用super(props)
,用来将父组件传来的props
绑定到这个类中,使用this.props
将会得到。
官方建议不要在constructor
引入任何具有副作用和订阅功能的代码,这些应当使用componentDidMount()
。
constructor
中应当做些初始化的动作,如:初始化state
,将事件处理函数绑定到类实例上,但也不要使用setState()
。如果没有必要初始化state或绑定方法,则不需要构造constructor
,或者把这个组件换成纯函数写法。
当然也可以利用props
初始化state
,在之后修改state
不会对props
造成任何修改,但仍然建议大家提升状态到父组件中,或使用redux
统一进行状态管理。
constructor(props) { super(props); this.state = { isLiked: props.isLiked }; }
(2) static getDerivedStateFromProps(nextProps, prevState)
React 的 16.3 版本中对生命周期进行了较大的调整,这是为了开发者能正确地使用生命周期,避免误解其概念而造成反模式。
本节将重点介绍 getDerivedStateFromProps 这个生命周期。要注意的是,React 16.3 的版本中 getDerivedStateFromProps 的触发范围是和 16.4^ 是不同的,主要区别是在 setState
和 forceUpdate
时会不会触发,具体可以看这个生命全周期图 。
React v16.4后的getDerivedStateFromProps
*static getDerivedStateFromProps(props, state)* 在组件创建时和更新时的render方法之前调用,它应该返回一个对象来更新状态,或者返回null来不更新任何内容。
注意:
1. getDerivedStateFromProps前面要加上static保留字,声明为静态方法,不然会被react忽略掉
2. getDerivedStateFromProps里面的this为undefined
static静态方法只能Class(构造函数)来调用(App.staticMethod✅),而实例是不能的( (new App()).staticMethod ❌ ); 当调用React Class组件时,该组件会实例化;
所以,React Class组件中,静态方法getDerivedStateFromProps无权访问Class实例的this,即this为undefined。
这里小结下 getDerivedStateFromProps 方法使用的注意点:
-
在使用此生命周期时,要注意把传入的 prop 值和之前传入的 prop 进行比较。
-
因为这个生命周期是静态方法,同时要保持它是纯函数,不要产生副作用。
我们应该谨慎地使用 getDerivedStateFromProps 这个生命周期。使用时要注意下面几点:
-
因为这个生命周期是静态方法,同时要保持它是纯函数,不要产生副作用。
-
在使用此生命周期时,要注意把传入的 prop 值和之前传入的 prop 进行比较(这个 prop 值最好有唯一性,或者使用一个唯一性的 prop 值来专门比较)。
-
不使用 getDerivedStateFromProps,可以改成组件保持完全不可控模式,通过初始值和 key 值来实现 prop 改变 state 的情景。
(3) componentWillMount() / UNSAFE_componentWillMount()
componentWillMount()
将在React未来版本(官方说法 17.0)中被弃用。UNSAFE_componentWillMount()
在组件挂载前被调用,在这个方法中调用setState()
不会起作用,是由于他在render()
前被调用。
为了避免副作用和其他的订阅,官方都建议使用componentDidMount()
代替。这个方法是用于在服务器渲染上的唯一方法。这个方法因为是在渲染之前被调用,也是惟一一个可以直接同步修改state的地方。
(4) render()
render()方法是必需的。当他被调用时,他将计算this.props
和this.state
,并返回以下一种类型:
-
React元素。通过jsx创建,既可以是dom元素,也可以是用户自定义的组件。
-
字符串或数字。他们将会以文本节点形式渲染到dom中。
-
Portals。react 16版本中提出的新的解决方案,可以使组件脱离父组件层级直接挂载在DOM树的任何位置。
-
null,什么也不渲染
-
布尔值。也是什么都不渲染。
当返回null
,false
,ReactDOM.findDOMNode(this)
将会返回null,什么都不会渲染。
render()
方法必须是一个纯函数,他不应该改变state
,也不能直接和浏览器进行交互,应该将事件放在其他生命周期函数中。 如果shouldComponentUpdate()
返回false
,render()
不会被调用。
(5) componentDidMount
componentDidMount
在组件被装配后立即调用。初始化使得DOM节点应该进行到这里。
通常在这里进行ajax请求
如果要初始化第三方的dom库,也在这里进行初始化。只有到这里才能获取到真实的dom.
(6) componentWillReceiveProps()/UNSAFE_componentWillReceiveProps(nextProps)
官方建议使用getDerivedStateFromProps
函数代替componentWillReceiveProps
。当组件挂载后,接收到新的props
后会被调用。如果需要更新state
来响应props
的更改,则可以进行this.props
和nextProps
的比较,并在此方法中使用this.setState()
。
如果父组件会让这个组件重新渲染,即使props
没有改变,也会调用这个方法。
React不会在组件初始化props时调用这个方法。调用this.setState
也不会触发。
(7) shouldComponentUpdate(nextProps, nextState)
调用shouldComponentUpdate
使React知道,组件的输出是否受state
和props
的影响。默认每个状态的更改都会重新渲染,大多数情况下应该保持这个默认行为。
在渲染新的props
或state
前,shouldComponentUpdate
会被调用。默认为true
。这个方法不会在初始化时被调用,也不会在forceUpdate()
时被调用。返回false
不会阻止子组件在state
更改时重新渲染。
如果shouldComponentUpdate()
返回false
,componentWillUpdate
,render
和componentDidUpdate
不会被调用。
官方并不建议在
shouldComponentUpdate()
中进行深度查询或使用JSON.stringify()
,他效率非常低,并且损伤性能。
(8) UNSAFE_componentWillUpdate(nextProps, nextState)
在渲染新的state
或props
时,UNSAFE_componentWillUpdate
会被调用,将此作为在更新发生之前进行准备的机会。这个方法不会在初始化时被调用。
不能在这里使用this.setState(),也不能做会触发视图更新的操作。如果需要更新state
或props
,调用getDerivedStateFromProps
。
(9) getSnapshotBeforeUpdate()
在react render()
后的输出被渲染到DOM之前被调用。它使您的组件能够在它们被潜在更改之前捕获当前值(如滚动位置)。这个生命周期返回的任何值都将作为参数传递给componentDidUpdate()。
getSnapshotBeforeUpdate() { let divWidth = this.divRef.current.getBoundingClientRect().width return divWidth } componentDidUpdate(prevProps, prevState, snapshot) { console.log(snapshot) }
(10) componentDidUpdate(prevProps, prevState, snapshot)
在更新发生后立即调用componentDidUpdate()
。此方法不用于初始渲染。当组件更新时,将此作为一个机会来操作DOM。只要您将当前的props与以前的props进行比较(例如,如果props没有改变,则可能不需要网络请求),这也是做网络请求的好地方。
如果组件实现getSnapshotBeforeUpdate()
生命周期,则它返回的值将作为第三个“快照”参数传递给componentDidUpdate()
。否则,这个参数是undefined
。
(11) componentWillUnmount()
在组件被卸载并销毁之前立即被调用。在此方法中执行任何必要的清理,例如使定时器无效,取消网络请求或清理在componentDidMount
中创建的任何监听。
(12) componentDidCatch(error, info)
错误边界是React组件,可以在其子组件树中的任何位置捕获JavaScript错误,记录这些错误并显示回退UI,而不是崩溃的组件树。错误边界在渲染期间,生命周期方法以及整个树下的构造函数中捕获错误。
如果类组件定义了此生命周期方法,则它将成错误边界。在它中调用setState()
可以让你在下面的树中捕获未处理的JavaScript错误,并显示一个后备UI。只能使用错误边界从意外异常中恢复; 不要试图将它们用于控制流程。
错误边界只会捕获树中下面组件中的错误。错误边界本身不能捕获错误。
6、PureComponent
PureComponnet
里如果接收到的新属性或者是更改后的状态和原属性、原状态相同的话,就不会去重新render了 在里面也可以使用shouldComponentUpdate
,而且。是否重新渲染以shouldComponentUpdate
的返回值为最终的决定因素。
import React, { PureComponent } from 'react'
// `PureComponnet`里如果接收到的新属性或者是更改后的状态和原属性、原状态相同的话,
// 就不会去重新render了
// PureComponent 与 Component 的区别
// 如果使用了 PureComponent ,还使用了shouldComponentUpdate ,后者的优先级高
export default class App extends PureComponent {
render() {
return (
<div>
</div>
)
}
}
7、ref
React提供的这个ref
属性,表示为对组件真正实例的引用,其实就是ReactDOM.render()
返回的组件实例,ref
可以挂载到组件上也可以是dom元素上。
-
挂到组件(
class
声明的组件)上的ref表示对组件实例的引用。不能在函数式组件上使用 ref 属性,因为它们没有实例: -
挂载到dom元素上时表示具体的dom元素节点。
在React 最新的版本中,要使用ref
, 需要使用React.createRef
方法先生成一个ref
。
import React, { PureComponent } from 'react'
class Child extends PureComponent {
state = { aa: 100 }
fn = () => {
console.log(this.state.aa)
}
render () {
return (
<div>
Child
</div>
)
}
}
export default class App extends PureComponent {
testRef = React.createRef()
render() {
return (
<div>
<Child ref={ this.testRef }/>
</div>
)
}
componentDidMount () {
console.log(this.testRef.current.state.aa) // 100
this.testRef.current.fn() // 100
}
}