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 += 1
this.setState(this.state)
- 虽然可以起效,但不推荐,因为这样违反了数据不靠边的原则
5. 什么是生命周期 ⇧
类比如下代码
|
|
- 这是
create/constructor
创建/构建的过程const div = document.createElement('div')
- 这是初始化
state
div.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
- 文章链接:
- 版权声明
- 非自由转载-非商用-非衍生-保持署名