Hooks 与 React 生命周期
一、Hooks 组件
函数组件 的本质是函数,没有 state 的概念的,因此不存在生命周期一说,仅仅是一个 render 函数而已。
但是引入 Hooks 之后就变得不同了,它能让组件在不使用 class 的情况下拥有 state,所以就有了生命周期的概念,所谓的生命周期其实就是 useState
、 useEffect()
和 useLayoutEffect()
。
即:Hooks 组件(使用了Hooks的函数组件)有生命周期,而函数组件(未使用Hooks的函数组件)是没有生命周期的。
下面,是具体的 class 与 Hooks 的生命周期对应关系:
为方便记忆,大致汇总成表格如下。
class 组件 | Hooks 组件 |
---|---|
constructor | useState |
getDerivedStateFromProps | useState 里面 update 函数 |
shouldComponentUpdate | useMemo |
render | 函数本身 |
componentDidMount | useEffect |
componentDidUpdate | useEffect |
componentWillUnmount | useEffect 里面返回的函数 |
componentDidCatch | 无 |
getDerivedStateFromError | 无 |
二、单个组件的生命周期
1. 生命周期
V16.3 之前
我们可以将生命周期分为三个阶段:
分开来讲:
挂载阶段
组件更新阶段
卸载阶段
这种生命周期会存在一个问题,那就是当更新复杂组件的最上层组件时,调用栈会很长,如果在进行复杂的操作时,就可能长时间阻塞主线程,带来不好的用户体验,Fiber 就是为了解决该问题而生。
V16.3 之后
Fiber 本质上是一个虚拟的堆栈帧,新的调度器会按照优先级自由调度这些帧,从而将之前的同步渲染改成了异步渲染,在不影响体验的情况下去分段计算更新。
对于异步渲染,分为两阶段:
其中,reconciliation
阶段是可以被打断的,所以 reconcilation
阶段执行的函数就会出现多次调用的情况,显然,这是不合理的。
所以 V16.3 引入了新的 API 来解决这个问题:
static getDerivedStateFromProps
:该函数在挂载阶段和组件更新阶段都会执行,即每次获取新的props
或state
之后都会被执行,在挂载阶段用来代替componentWillMount
;在组件更新阶段配合componentDidUpdate
,可以覆盖componentWillReceiveProps
的所有用法。同时它是一个静态函数,所以函数体内不能访问
this
,会根据nextProps
和prevState
计算出预期的状态改变,返回结果会被送给setState
,返回null
则说明不需要更新state
,并且这个返回是必须的。getSnapshotBeforeUpdate
: 该函数会在render
之后, DOM 更新前被调用,用于读取最新的 DOM 数据。返回一个值,作为
componentDidUpdate
的第三个参数;配合componentDidUpdate
, 可以覆盖componentWillUpdate
的所有用法。
注意:V16.3 中只用在组件挂载或组件 props
更新过程才会调用,即如果是因为自身 setState 引发或者forceUpdate 引发,而不是由父组件引发的话,那么static getDerivedStateFromProps
也不会被调用,在 V16.4 中更正为都调用。
即更新后的生命周期为:
挂载阶段
更新阶段
卸载阶段
2. 生命周期,误区
误解一:getDerivedStateFromProps
和 componentWillReceiveProps
只会在 props
改变 时才会调用
实际上,只要父级重新渲染,getDerivedStateFromProps
和 componentWillReceiveProps
都会重新调用,不管 props
有没有变化。所以,在这两个方法内直接将 props 赋值到 state 是不安全的。
// 子组件class PhoneInput extends Component { state = { phone: this.props.phone }; handleChange = e => { this.setState({ phone: e.target.value }); }; render() { const { phone } = this.state; return <input onChange={this.handleChange} value={phone} />; } componentWillReceiveProps(nextProps) { // 不要这样做。 // 这会覆盖掉之前所有的组件内 state 更新! this.setState({ phone: nextProps.phone }); }}// 父组件class App extends Component { constructor() { super(); this.state = { count: 0 }; } componentDidMount() { // 使用了 setInterval, // 每秒钟都会更新一下 state.count // 这将导致 App 每秒钟重新渲染一次 this.interval = setInterval( () => this.setState(prevState => ({ count: prevState.count + 1 })), 1000 ); } componentWillUnmount() { clearInterval(this.interval); } render() { return ( <> <p> Start editing to see some magic happen :) </p> <PhoneInput phone='call me!' /> <p> This component will re-render every second. Each time it renders, the text you type will be reset. This illustrates a derived state anti-pattern. </p> </> ); }}
class PhoneInput extends Component {state = { phone: this.props.phone };handleChange = e => {this.setState({ phone: e.target.value });};render() {const { phone } = this.state;return <input onChange={this.handleChange} value={phone} />;}componentWillReceiveProps(nextProps) {// 不要这样做。// 这会覆盖掉之前所有的组件内 state 更新!this.setState({ phone: nextProps.phone });}
}// 父组件
class App extends Component {constructor() {super();this.state = {count: 0};}componentDidMount() {// 使用了 setInterval,// 每秒钟都会更新一下 state.count// 这将导致 App 每秒钟重新渲染一次this.interval = setInterval(() =>this.setState(prevState => ({count: prevState.count + 1})),1000);}componentWillUnmount() {clearInterval(this.interval);}render() {return (<><p>Start editing to see some magic happen :)</p><PhoneInput phone='call me!' /> <p>This component will re-render every second. Each time it renders, thetext you type will be reset. This illustrates a derived stateanti-pattern.</p></>);}
}
实例可点击这里查看
当然,我们可以在 父组件App 中 shouldComponentUpdate
比较 props 的 email 是不是修改再决定要不要重新渲染,但是如果子组件接受多个 props(较为复杂),就很难处理,而且 shouldComponentUpdate
主要是用来性能提升的,不推荐开发者操作 shouldComponetUpdate
(可以使用 React.PureComponet
)。
我们也可以使用 在 props 变化后修改 state。
class PhoneInput extends Component { state = { phone: this.props.phone }; componentWillReceiveProps(nextProps) { // 只要 props.phone 改变,就改变 state if (nextProps.phone !== this.props.phone) { this.setState({ phone: nextProps.phone }); } } // ...}state = {phone: this.props.phone};componentWillReceiveProps(nextProps) {// 只要 props.phone 改变,就改变 stateif (nextProps.phone !== this.props.phone) {this.setState({phone: nextProps.phone});}}// ...
}
但这种也会导致一个问题,当 props 较为复杂时,props 与 state 的关系不好控制,可能导致问题
解决方案一:完全可控的组件
function PhoneInput(props) { return <input onChange={props.onChange} value={props.phone} />;}return <input onChange={props.onChange} value={props.phone} />;
}
完全由 props 控制,不派生 state
解决方案二:有 key 的非可控组件
class PhoneInput extends Component { state = { phone: this.props.defaultPhone }; handleChange = event => { this.setState({ phone: event.target.value }); }; render() { return <input onChange={this.handleChange} value={this.state.phone} />; }}<PhoneInput defaultPhone={this.props.user.phone} key={this.props.user.id}/>state = { phone: this.props.defaultPhone };handleChange = event => {this.setState({ phone: event.target.value });};render() {return <input onChange={this.handleChange} value={this.state.phone} />;}
}<PhoneInputdefaultPhone={this.props.user.phone}key={this.props.user.id}
/>
当 key
变化时, React 会创建一个新的而不是更新一个既有的组件
误解二:将 props 的值直接复制给 state
应避免将 props 的值复制给 state
constructor(props) { super(props); // 千万不要这样做 // 直接用 props,保证单一数据源 this.state = { phone: props.phone };}super(props);// 千万不要这样做// 直接用 props,保证单一数据源this.state = { phone: props.phone };
}
三、多个组件的执行顺序
1. 父子组件
static getDerivedStateFromProps
shouldComponentUpdate
第 二 阶段,此时 DOM 节点已经生成完毕,组件挂载完成,开始后续流程。先依次触发同步子组件以下函数,最后触发父组件的。
React 会按照上面的顺序依次执行这些函数,每个函数都是各个子组件的先执行,然后才是父组件的执行。
所以执行顺序是:
父组件 getDerivedStateFromProps —> 父组件 shouldComponentUpdate —> 子组件 getDerivedStateFromProps —> 子组件 shouldComponentUpdate —> 子组件 getSnapshotBeforeUpdate —> 父组件 getSnapshotBeforeUpdate —> 子组件 componentDidUpdate —> 父组件 componentDidUpdate
getSnapshotBeforeUpdate()
componentDidUpdate()
卸载阶段
componentWillUnmount()
,顺序为 父组件的先执行,子组件按照在 JSX 中定义的顺序依次执行各自的方法。
注意 :如果卸载旧组件的同时伴随有新组件的创建,新组件会先被创建并执行完 render
,然后卸载不需要的旧组件,最后新组件执行挂载完成的回调。
Hooks 与 React 生命周期相关推荐
- React 生命周期、Hooks
React React 生命周期 Hooks Hook 的特点 Effect Hook 自定义 hooks React 生命周期 react 生命周期是组件实例从创建运行到销毁的一系列过程 compo ...
- react生命周期_React生命周期事件
react生命周期 React class components can have hooks for several lifecycle events. React类组件可以具有多个生命周期事件的挂 ...
- 浅谈 React 生命周期
浅谈 React 生命周期 浅谈 React 生命周期 旧版的生命周期 新版的生命周期 详解各个生命周期函数 constructor getDerivedStateFromProps render c ...
- 深入react技术栈(5):React生命周期
我是歌谣 放弃很容易 但是坚持一定很酷 微信搜一搜前端小歌谣 React生命周期 挂载和卸载过程 组件得挂载 组件得卸载 数据更新过程 整体流程 文章参考深入学习React技术栈
- react学习(9)----react生命周期
react生命周期1.1.constructor() constructor()中完成了React数据的初始化,它接受两个参数 :props和context,当想在函数内部使用这两个参数时 ,需使用s ...
- react生命周期(自己的方式理解)
react生命周期(自己的方式理解) react的生命周期函数有:挂载,渲染/更新,卸载 首先要知道react生命周期最初始的要有哪些?最后生命中出现的又有哪些?我本人喜欢把react比作是一个人的一 ...
- 你不可不知道的React生命周期
点小蓝字加关注! 作者:kim 来源:原创 写在前面 咱今天聊的话题是React生命周期,灵感来自于最近在网上发现一篇关于react生命周期的文章,里面记录的知识点竟然与小编所get到的有出入.作为一 ...
- ES6中的React生命周期详解
太长时间没写react了,有点生.重新捡起来练练手. import React,{ Component } from 'react';class Demo extends Component {con ...
- react生命周期方法介绍
react生命周期 react生命周期主要包括三个阶段:初始化阶段.运行中阶段.销毁阶段 react在不同的生命周期会触发不同的钩子函数 初始化阶段 getDefaultProps() 设置组件默认的 ...
最新文章
- centos syslog-ng 配置
- 简单线性分类学习机(平分最近点法)matlab实现
- Json.net|NH|Log4net|Test等工具下载地址
- Verilog功能模块 —— 按键消抖
- 经典C语言程序100例之十六
- OC Autorelease
- 闲鱼有微信小程序吗_微信小程序商品展示页面(仿咸鱼)
- python读取git日志_Python获取gitlab提交历史!
- 【收藏版】深度学习中的各种优化算法
- 吴恩达深度学习笔记(归一化)
- js+java实现登录滑动图片验证功能
- 互联网大厂知识点整理
- c语言根据日期求星期蔡勒公式,利用蔡勒公式获得给定日期的星期数
- U盘写保护、不可访问、无法格式化问题的解决办法
- 教你写脚本屏蔽百度广告 csdn广告
- android一键改机之真改机build.prop
- 高级运维工程师证书_运维人员需要考什么证 linux运维工程师考证
- 微信公众号 java 教程视频下载_微信公众号开发视频教程java
- 数据库的锁机制理解和运用
- 如何在CSDN写笔记_写笔记前的插件安装
热门文章
- Excel VBA工作簿与工作表的操作
- #c语言sinx≈x-x^3^/3!+x^5^/5!-x^7^/7!+x^9^/9!-……,计算sinx的值,直到最后一项小于1e-5,并计算累加次数。
- 微信小程序自定义tabbar【中间凸起样式】
- MyBatis相关整理
- python字典操作练习题
- 【阅读笔记】Large-Scale Few-Shot Learning via Multi-Modal Knowledge Discovery
- 百度AI识别声音(sing)
- 《Android 应用案例开发大全(第3版)》——第2章,第2.4节壁纸的实现
- u盘盘符不显示 win10_Win10不显示U盘的盘符怎么办
- 普通人忽略了社会机制会很难