Class 组件详解
大纲链接 §
[toc]
Class组件虽然是已淘汰的繁杂用法,没有函数组件那样的简洁清晰的语法,“能效比”低,新项目基本不用,只是使用 Class 组件必须有更扎实的JS功底,洞悉this的原理,好比汉字的「识繁书简」
1. 英语小课堂 ⇧
| 英语 | 翻译 |
|---|---|
derived |
导出的,衍生的,派生的 |
render |
渲染 |
super class |
超类、父类 |
property |
属性 |
state |
状态 |
mount |
挂载 |
2. 两种创建 class 组件的方式 ⇧
过时的
ES5方式
|
|
- 由于
ES5不支持class,才会有这种方式
ES6方式(也过时了,现在用函数组件)
|
|
- 关键词
extends、constructor、super - 构造方法中如果不用添加额外代码的话,可直接删除
constructor(props) {super(props)}
哪种好
ES6相较来说更好,老项目中只用这种方式创建类组件
浏览器不支持
ES6怎么办
- 公司还在搞
IE8,肯定是个破公司,跳槽吧 - 用
webpack+babel将ES6转译成ES5即可
3. 回顾props ⇧
父组件的state传给 子组件作为其 props ⇧
|
|
- 外部数据
props被包装为一个对象 {name: 'xxx', clickHandler: ..., children: 'hi'}- 此处
clickHandler是一个回调
子组件初始化 ⇧
|
|
- 要么自动初始化,即不写
constructor方法,自动继承 - 要么手动初始化,且必须写全套(不写
super直接报错) - 效果是将
props放到实例的this上
效果
- 这么做了之后,
this.props就是外部数据 对象的地址 了
子组件读取props ⇧
|
|
- 通过
this.props.xxx读取
原则:不准对组件的 props 进行写入 ⇧
尝试改写
props的值(一个对象的地址)
this.props = { /* 另一个对象 */ }- 这样破坏了单向数据流的原则
- 外部数据,应该由外部更新
尝试更改
props的属性
this.props.xxx = 'hi'- 这样同样破坏了单向数据流的原则
- 外部数据,不应该从内部改值
原则
- 应该由数据的 所有者 对数据进行更改
- 原则上可以用一个内部数据的属性 来承接,修改这个内部数据的属性
props相关的钩子 ⇧
componentWillReceiveProps钩子
- 当组件接受新的
props时,会触发此钩子 - 钩子函数 即 「特殊实际调用的函数」
- 示例
该钩子已经被弃用
- 更名为
UNSAFE_componentWillReceiveProps - 旧项目中
16.9-可能会看到
props的作用 ⇧
接受外部数据
- 只能读,不能写
- 外部数据由父组件传递,由父组件来维护
接受外部函数
- 在恰当的时机,调用该函数
- 该函数一般是父组件的函数
4. 回顾state ⇧
初始化 state ⇧
|
|
读写 state ⇧
读数据用
this.state
this.state.xxx.yyy.zzz
写数据用
this.setState(???, fn)
this.setState(newState, fn)- 注意
setState不会立即改变this.state- 而是会在当前代码运行完后,再去更新
this.state,从而触发UI更新
- 而是会在当前代码运行完后,再去更新
- 推荐总是使用
this.setState((state, props) => newState, fn)- 这种方式的
state反而更易于理解 fn会在写入成功后执行,是成功后的回调- 注意对象格式
this.setState((state, props) => ({xxx: state.xxx + 1 }), fn)
- 这种方式的
有时会
shallow merge浅合并(即合并对象的第一层属性)
setState会自动将新state与旧state进行一级合并
一种错误地做法:直接对
this.state的属性 进行再次赋值 来修改属性
this.state.n += 1this.setState(this.state)- 虽然可以起效,但不推荐,因为这样违反了数据不靠边的原则
5. 什么是生命周期 ⇧
类比如下代码
|
|
- 这是
create/constructor创建/构建的过程const div = document.createElement('div') - 这是初始化
statediv.textContent = 'hi' - 这是
div挂载mount的过程app.appendChild(div) - 这是
div的update的过程div.textContent = 'hi2' - 这是
div的unmount的过程div.remove()
同理
React组件也有这些过程,称之为组件的生命周期,示例代码reactLifecycle.js
|
|
5.0 生命周期钩子函数列表 ⇧
constructor()创建时调用,初始化state可忽略static getDerivedStateFromProps()shouldComponentUpdate()手动判断是否更新return false时阻止更新
render()渲染,创建虚拟DOM可忽略getSnapshotBeforeUpdate()componentDidMount()挂载后调用,组件已出现在页面componentDidUpdate()更新后调用,组件已更新componentWillUnmount()即将卸载时调用,组件将销毁可忽略static getDerivedStateFromError()可忽略componentDidCatch()
5.1 生命周期之 constructor ⇧
用途
- 初始化
props:constructor(props) {super(props)} - 初始化
state,但此时不能调用setState方法- 只能使用
this.state = {/*...*/}
- 只能使用
- 用来写
bind this,也可不写,自动继承
|
|
5.2 生命周期之 shouldComponentUpdate ⇧
用途
- 返回
true表示不阻止UI更新 - 返回
false表示阻止UI更新 shouldComponentUpdate文档
面试常问
shouldComponentUpdate有什么用- 它允许手动判断是否要进行组件更新
- 在其中添加自己的业务逻辑,可以根据不同应用场景,灵活地设置返回值,以避免不必要的更新
示例
+1 后立马 -1
|
|
- 首次就会
render一次 - 只要数据一有改动,即使最终值相同,也会执行
render{n: 1}和{n: 1}不是同一个对象- 但根据
DOM diff不会实际去更新页面
启发
- 其实可以将
newState和this.state的每个属性都对比一下- 如果全等,就不更新组件
- 如果有一个不等,就更新
再启发
React内置了这项功能,只不过另外取名叫React.PuerComponent,来代替React.Component,他可以替换绝大多数情况下的shouldComponentUpdate钩子判断
质疑
- 为什么要用新的对象
- 为什么不直接在
this.state上修改属性? - 这涉及到函数式编程,暂略
关于 React.PureComponent ⇧
|
|
PureComponent会在render之前- 对比新
state和旧state的每一个key - 对比新
props和旧props的每一个key - 作浅对比,值对比数据对象的第一层属性
- 对比新
- 如果所有
key的值全都一样,就不会render - 如果有任何一个
key的值不同,就会render
参考
5.3 生命周期之 render ⇧
用途
- 展示视图
return (<>...</>)里面是虚拟DOM render()方法是class组件中唯一必须实现的方法- 只能有一个根元素
- 如果有两个根元素,就要用
<React.Fragment></React.Fragment>文档片段包起来 <React.Fragment>一般缩写成<></>
- 如果有两个根元素,就要用
打印查看虚拟
DOM
|
|
技巧
render里可以写条件分支表达式- 可以写
if...else - 可以写
?...:...三元表达式 - 可以写
&&等短路判断
- 可以写
render里不能直接写for循环(return结束循环),需要用数组作中间变量render里可以写array.map(循环渲染)
|
|
|
|
|
|
|
|
参考
5.4 生命周期之 componentDidMount ⇧
用途
- 在元素插入页面之后执行逻辑,这些代码依赖
DOM- 例如获取元素高度,前提是元素必须出现在页面中
Element.getBoundingClientRect()- 不推荐使用
id来获取元素 - 推荐使用
ref来获取元素,不会出现id冲突的问题
- 也可以发起 加载数据 的
AJAX请求(官方推荐) - 首次渲染 即会 执行这个钩子函数
|
|
参考
5.5 生命周期之 componentDidUpdate ⇧
用途
- 在视图更新后执行逻辑
- 此处也可以发起 加载数据 的
AJAX请求,用于 更新数据 的请求,见文档- 区别于
componentDidMount时 加载数据 的请求
- 区别于
- 首次渲染 不会 执行这个钩子函数
- 注意
- 在此处
setState可能会引起 无限循环,两者互相调用,除非放在if判断中 - 若
shouldComponentUpdate返回false,则不会触发此钩子
- 在此处
|
|
参考
5.6 生命周期之 componentWillUnmount ⇧
用途
- 组件将要被 移除出页面(
DOM),然后被销毁(内存) 时执行逻辑 - 执行必要的清理操作,例如,清除
timer,取消网络请求或清除在componentDidMount()中创建的订阅等 unmount过的组件不会再次mount,即组件实例卸载后,将永远不会再挂载它componentWillUnmount()中不应调用setState()- 因为该组件将永远不会重新渲染
原则:谁污染谁治理(出于性能和用户体验考虑)防止内存泄漏
- 在
ComponentDidMount里监听了window.scroll- 那么就要在
ComponentWillUnmount里取消监听
- 那么就要在
- 在
ComponentDidMount里创建了timer(setTimeout)- 那么就要在
ComponentWillUnmount里清除timer(clearTimeout)
- 那么就要在
- 在
ComponentDidMount里创建了AJAX请求- 那么就要在
ComponentWillUnmount里取消请求AbortController MDN
- 那么就要在
参考
生命周期函数小结 ⇧
分阶段查看钩子执行顺序
生命周期钩子函数回顾,分为三个阶段
- 首次渲染
constructor()初始化数据render()创建虚拟DOM- 更新
UI componentDidMount()组件已出现在页面
- 再次渲染
props变了、setState变了或者forceUpdate触发再次渲染shouldComponentUpdate()或阻止组件更新- 该更新时不要忘记
return true否则不会触发更新
- 该更新时不要忘记
render()创建虚拟DOM- 更新
UI componentDidUpdate()组件已更新
- 销毁
componentWillUnmopunt()组件将死
7. 总结 ⇧
平时工作中以下推荐顺序写组件
- 函数组件 > 类组件 >
React.PureComponent>React.Component props只读;state可读可写- 类组件中
this.setState((state, props) => newState, fn)>this.setState(newState, fn)
| 组件类型\负担对比 | 心智负担 | 手指负担 | 代码量 | 代码组合方式 |
|---|---|---|---|---|
| 函数组件 | 较轻 | 较轻 | 较少 | useXxx钩子函数Hooks API |
| 类组件 | 较重 | 较重 | 较多 | 类继承、setState、生命周期API等 |
8. 课后习题 测试 Class 组件
-
React Class组件的constructor在某些情况下可以省略 - 如果有
constructor,则constructor中的super(props)必须写 -
<B name="xxx" onClick={this.onClick}>hi</B>组件接收到的props的值是{name:"xxx", onClick:this.onClick, children: 'hi'} -
setState的shallow merge是React只会检查新state和旧state第一层的区别,并把新state缺少的数据从旧state里拷贝过来 - 被
React弃用的生命周期有componentWillMount()componentWillUpdate()componentWillReceiveProps()- 提示,前面加了
UNSAFE_前缀的生命周期,都是被弃用的 - 可以在 React 文档的某个页面里通过 Ctrl + F 找到
- (搜google)
参考文章 ⇧
- React Class 组件详解.pdf
- React文档首页
- 英文文档
- 中文文档
- codesandbox.io/s/yqrkxpm01
相关文章 ⇧
- 作者: Joel
- 文章链接:
- 版权声明
- 非自由转载-非商用-非衍生-保持署名