原文来源于:程序员成长指北;作者:小杜杜
原文:https://juejin.cn/post/7124486630483689485
如有侵权,联系删除
大家好,我是小杜杜,俗话说的好,工欲善其事必先利其器,什么意思呢?就是说你想玩转React
就必须知道React
有什么,无论是否运用到,首先都要知道,提升思维广度~
其实React
官方提供了很多Api
,只是这些Api
我们并不常用,所以往往会忽略它们,但在一些特定的场景下,这些Api
也会起到关键性的作用,所以今天就逐个盘点一下,说说它们的使用方法和使用场景。
当然这些Api
并不需要全部掌握,只需要知道有这个知识点就好了~
本文将会全面总结所有的React
Api,包含组件类
、工具类
、生命周期
、react-hooks
、react-dom
五大模块,并配带示例,帮助大家更好的掌握,大家可以边嗑瓜子边阅读,如有不全、不对的地方欢迎在评论区指出~
由于本文过长,建议点赞
+收藏
, 在正式开始前,我抽取了一些问题,一起来看看:
-
1.
React v18
中对react-dom
做了那些改动,增加了那些新的hooks
? -
2.
useRef
除了获取元素的节点信息,还能做什么? -
3.为什么会有
Children.map
?它与不同的遍历有和不同 -
4.类组件的生命周期在不同的版本是怎样变化的?
-
5.子元素如何渲染到父元素上面的?
-
...
其实问题很多,看完这篇文章后,相信一定能帮你解答的非常清楚,还请各位小伙伴多多支持一下
前言
我的这个专栏的目的就是对
React
的深入,就是希望对React
有一个全面的提升写这篇文章的主要目的有:
提升知识广度,要想深入
React
就必须全面了解React
,首先要学会用,要知道,如果连知道都不知道,谈何深入?
React v18
对react-dom
的改动还是比较大的,并且新增了五个hooks
,逐一盘点一下,看看做了那些改动这个专栏实际上是循序渐进的,相互之间都有一定的关联,同时要想看懂,也需要有一定的
React
基础,对刚开始学习React
的小伙伴可能并不是太友好,所以特地总结一番,用最简单的示例,帮你掌握这些Api对之后的源码有帮助,所以本篇文章将会全面解读
React Api
的使用方法,和场景,如有不足,还希望在评论区指出~
附上一张今天的学习图谱~
全面解读ReactApi.png
组件类
Component
在React中提供两种形式,一种是类组件
,另一种是函数式组件
,而在类组件
组件中需要使用Component
继承,这个组件没有什么好讲的,我们可以看看源码:
文件位置packages/react/src/ReactBaseClasses.js
image.png
可以看出Component
进行一些初始化的工作,updater
保存着更新组件的方法
PureComponent
PureComponent:会对props
和state
进行浅比较,跳过不必要的更新,提高组件性能。
可以说PureComponent
和Component
基本完全一致,但PureComponent
会浅比较,也就是较少render
渲染的次数,所以PureComponent
一般用于性能优化
那么什么是浅比较,举个🌰:
import { PureComponent } from 'react';
import { Button } from 'antd-mobile';
class Index extends PureComponent{
constructor(props){
super(props)
this.state={
data:{
number:0
}
}
}
render(){
const { data } = this.state
return <div style={{padding: 20}}>
<div> 数字: { data.number }</div>
<Button
color="primary"
onClick={() => {
const { data } = this.state
data.number++
this.setState({ data })
}}
>数字加1</Button>
</div>
}
}
export default Index;
复制代码
效果:
img1.gif
可以发现,当我们点击按钮的时候,数字并没有刷新,这是因为PureComponent
会比较两次的data
对象,它会认为这种写法并没有改变原先的data
,所以不会改变
我们只需要:
this.setState({ data: {...data} })
复制代码
这样就可以解决这个问题了
与shouldComponentUpdate的关系如何
在生命周期中有一个shouldComponentUpdate()
函数,那么它能改变PureComponent
吗?
其实是可以的,shouldComponentUpdate()
如果被定义,就会对新旧 props
、state
进行 shallowEqual
比较,新旧一旦不一致,便会触发 update
。
也可以这么理解:PureComponent
通过自带的props
和state
的浅比较实现了shouldComponentUpdate()
,这点Component
并不具备
PureComponent
可能会因深层的数据不一致而产生错误的否定判断,从而导致shouldComponentUpdate
结果返回false,界面得不到更新,要谨慎使用
memo
memo:结合了pureComponent纯组件
和 componentShouldUpdate
功能,会对传入的props进行一次对比,然后根据第二个函数返回值来进一步判断哪些props需要更新
要注意memo
是一个高阶组件,函数式组件
和类组件
都可以使用。
memo
接收两个参数:
-
第一个参数:组件本身,也就是要优化的组件
-
第二个参数:(pre, next) => boolean,
pre
:之前的数据,next
:现在的数据,返回一个布尔值
,若为true
则不更新,为false
更新
性能优化
接下来,我们先看这样一个🌰:
import React, { Component } from 'react';
import { Button } from 'antd-mobile';
const Child = () => {
return <div>
{console.log('子组件渲染')}
大家好,我是小杜杜~
</div>
}
class Index extends Component{
constructor(props){
super(props)
this.state={
flag: true
}
}
render(){
const { flag } = this.state
return <div style={{padding: 20}}>
<Child/>
<Button
color="primary"
onClick={() => this.setState({ flag: !flag })}
>状态切换{JSON.stringify(flag)}</Button>
</div>
}
}
export default Index;
复制代码
在上述代码中,我们设置一个子组件,也就是Child
和一个按钮,按钮的效果是切换 flag
的状态,可以看出flag
和Child
之间没有任何关系,那么在切换状态的时候,Child
会刷新吗?
直接看看效果:
img2.gif
可以看出,在我们切换状态的时候,Child
实际上也会刷新,我们肯定不希望组件做无关的刷新,那么我们加上memo
来看看的效果:
const HOCChild = memo(Child, (pre, next) => {
return true
})
复制代码
效果:
可以看出,加上memo
后,Child
不会再做无关的渲染,从而达到性能优化
的作用
第二个参数的作用
栗子🌰:
import React, { Component, memo } from 'react';
import { Button } from 'antd-mobile';
const Child = ({ number }) => {
return <div>
{console.log('子组件渲染')}
大家好,我是小杜杜~
<p>传递的数字:{number}</p>
</div>
}
const HOCChild = memo(Child, (pre, next) => {
if(pre.number === next.number) return true
if(next.number < 7) return false
return true
})
class Index extends Component{
constructor(props){
super(props)
this.state={
flag: true,
number: 1
}
}
render(){
const { flag, number } = this.state
return <div style={{padding: 20}}>
<HOCChild number={number} />
<Button
color="primary"
onClick={() => this.setState({ flag: !flag})}
>状态切换{JSON.stringify(flag)}</Button>
<Button
color="primary"
style={{marginLeft: 8}}
onClick={() => this.setState({ number: number + 1})}
>数字加一:{number}</Button>
</div>
}
}
export default Index;
复制代码
效果:
img4.gif
当数字小于7,才会出发Child
的更新,通过返回的布尔值来控制
memo的注意事项
React.memo
与PureComponent
的区别:
-
服务对象不同:
PureComponent
服务与类组件,React.memo
既可以服务于类组件,也可以服务与函数式组件,useMemo
服务于函数式组件(后续讲到) -
针对的对象不同:
PureComponent
针对的是props
和state
,React.memo
只能针对props
来决定是否渲染
这里还有个小的注意点:memo
的第二个参数的返回值与shouldComponentUpdate
的返回值是相反的,经常会弄混,还要多多注意
-
memo
:返回true
组件不渲染 , 返回false
组件重新渲染。 -
shouldComponentUpdate
: 返回true
组件渲染 , 返回false
组件不渲染。
forwardRef
forwardRef
:引用传递,是一种通过组件向子组件自动传递引用ref
的技术。对于应用者的大多数组件来说没什么作用,但对于一些重复使用的组件,可能有用。
听完介绍是不是感觉云里雾里的,官方对forwardRef
的介绍也很少,我们来看看转发的问题
在React
中,React
不允许ref
通过props
传递,因为ref
是组件中固定存在的,在组件调和的过程中,会被特殊处理,而forwardRef
就是为了解决这件事而诞生的,让ref
可以通过props
传递
举个栗子🌰:父组件想要获取孙组件上的信息,我们直接用ref
传递会怎样:
企业微信截图_0bc3e73d-e6b4-45a9-bd66-c43cd8bbea11.png
接下来看看利用forwardRef
来转发下ref
,就可以解决这个问题了:
import React, { Component, forwardRef } from 'react';
const Son = ({sonRef}) => {
return <div>
<p>孙组件</p>
<p ref={sonRef}>大家好,我是小杜杜~</p>
</div>
}
const Child = ({ childRef }) => {
return <div>
<div>子组件</div>
<Son sonRef={childRef} />
</div>
}
const ForwardChild = forwardRef((props, ref) => <Child childRef={ref} {...props} />)
class Index extends Component{
constructor(props){
super(props)
}
node = null
componentDidMount(){
console.log(this.node)
}
render(){
return <div style={{padding: 20}}>
<div>父组件</div>
<ForwardChild ref={(node) => this.node = node} />
</div>
}
}
export default Index;
复制代码
效果:
企业微信截图_c7a4aa75-362d-4bba-8c83-ded0b4dce01b.png
如此以来就解决了不能在react
组件中传递ref
的问题,至于复用的组件可能会用到,目前也没思路用forwardRef
干嘛,就当熟悉吧~
Fragment
在React
中,组件是不允许返回多个节点的,如:
return <p>我是小杜杜</p>
<p>React</p>
<p>Vue</p>
复制代码
我们想要解决这种情况需要给为此套一个容器元素,如<div></div>
return <div>
<p>我是小杜杜</p>
<p>React</p>
<p>Vue</p>
</div>
复制代码
但这样做,无疑会多增加一个节点,所以在16.0
后,官方推出了Fragment
碎片概念,能够让一个组件返回多个元素,React.Fragment 等价于<></>
return <React.Fragment>
<p>我是小杜杜</p>
<p>React</p>
<p>Vue</p>
</React.Fragment>
复制代码
可以看到React.Fragment
实际上是没有节点的
另外,react
中支持数组的返回,像这样:
return [
<p key='1'>我是小杜杜</p>,
<p key='2'>React</p>,
<p key='3'>Vue</p>
]
复制代码
还有我们在进行数组遍历的时候,React
都会在底层处理,在外部嵌套一个<React.Fragment />
Fragment 与 <></>
的不同
我们都知道<></>
是<Fragment></Fragment>
的简写,从原则上来说是一致的,那么你知道他们又什么不同吗?
实际上,Fragment
这个组件可以赋值 key
,也就是索引,<></>
不能赋值,应用在遍历数组上,有感兴趣的同学可以试一试~
lazy
lazy:允许你定义一个动态加载组件,这样有助于缩减 bundle 的体积,并延迟加载在初次渲染时未用到的组件,也就是懒加载组件(高阶组件)
lazy
接收一个函数,这个函数需要动态调用import()
,如:
const LazyChild = lazy(() => import('./child'));
复制代码
那么import('./child')
是一个怎样的类型呢?
实际上lazy
必须接受一个函数,并且需要返回一个Promise
, 并且需要resolve
一个default
一个React
组件,除此之外,lazy
必须要配合Suspense
一起使用
举个例子🌰:我加入了setTimeout
方便看到更好的效果:
import React, { Component, Suspense, lazy } from 'react';
import Child from './child'
import { Button, DotLoading } from 'antd-mobile';
const LazyChild = lazy(() => new Promise((res) => {
setTimeout(() => {
res({
default: () => <Child />
})
}, 1000)
}))
class Index extends Component{
constructor(props){
super(props)
this.state={
show: false
}
}
render(){
const { show } = this.state
return <div style={{padding: 20}}>
<Button color='primary' onClick={() => this.setState({ show: true })} >
渲染
</Button>
{
show && <Suspense fallback={<div><DotLoading color='primary' />加载中</div>}>
<LazyChild />
</Suspense>
}
</div>
}
}
export default Index;
复制代码
Child 文件:
import React, { useEffect } from 'react';
import img from './img.jpeg'
const Index = () => {
useEffect(() => {
console.log('照片渲染')
}, [])
return <div>
<img src={img} width={200} height={160} />
</div>
}
export default Index;
复制代码
效果:
img5.gif
Suspense
Suspense:让组件"等待"某个异步组件操作,直到该异步操作结束即可渲染。
与上面lazy
中的案例一样,两者需要配合使用,其中fallback
为等待时渲染的样式
Suspense
和lazy
可以用于等待照片、脚本和一些异步的情况。
Profiler
Profiler:这个组件用于性能检测,可以检测一次react
组件渲染时的性能开销
此组件有两个参数:
-
id
:标识Profiler
的唯一性 -
onRender
:回调函数,用于渲染完成,参数在下面讲解
举个栗子🌰:
import React, { Component, Profiler } from 'react';
export default Index;
复制代码
让我们来看看打印的是什么:
image.png
依此的含义:
-
id:
Profiler
树的id
-
phase:
mount
挂载,update
渲染 -
actualDuration:更新
committed
花费的渲染时间 -
baseDuration:渲染整颗子树需要的时间
-
startTime:更新开始渲染的时间
-
commitTime:更新
committed
的时间 -
interactions:本次更新的
interactions
的集合
需要注意的是,这个组件应该在需要的时候去使用,虽然Profiler
是一个轻量级的,但也会带来负担
StrictMode
StrictMode:严格模式,是一种用于突出显示应用程序中潜在问题的工具
与Fragment
一样,StrictMode
也不会出现在UI
层面,只是会检查和警告
可以看一下官方的示例:
import React from 'react';
function ExampleApplication() {
return (
<div>
<Header />
<React.StrictMode> <div>
<ComponentOne />
<ComponentTwo />
</div>
</React.StrictMode> <Footer />
</div>
);
}
复制代码
上述代码中只会对ComponentOne
和ComponentTwo
进行检查
主要有以下帮助:
-
识别具有不安全生命周期的组件[1]
-
关于旧版字符串引用 API 使用的警告[2]
-
关于不推荐使用 findDOMNode 的警告[3]
-
检测意外的副作用[4]
-
检测遗留上下文 API[5]
-
确保可重用状态[6]
工具类
crateElement
在之前讲的虚拟DOM[7]的时候已经详细的讲解过,我们在这里在在复习一遍
JSX
会被编译为React.createElement
的形式,然后被babel
编译
结构:
React.createElement(type, [props], [...children])
共有三个参数:
-
type
:原生组件的话是标签的字符串,如“div”
,如果是React
自定义组件,则会传入组件 -
[props]
:对象,dom
类中的属性
,组件
中的props
-
[...children]
:其他的参数,会依此排序
举个例子🌰:举个栗子🌰:
class Info extends React.Component {
render(){
return(
<div>
Hi!我是小杜杜
<p>欢迎</p>
<Children>我是子组件</Children>
</div>
)
}
}
复制代码
上述代码会被翻译为:
class Info extends React.Component {
render(){
return React.createElement(
'div',
null,
"Hi!我是小杜杜",
React.createElement('p', null, '欢迎'), // 原生标签
React.createElement(
Children, //自定义组件
null, // 属性
'我是子组件' //child文本内容
)
)
}
}
复制代码
注意点
-
JSX
的结构实际上和React.createElement
写法一致,只是用JSX
更加简单、方便 -
经过
React.createElement
的包裹,最终会形成$$typeof = Symbol(react.element)
对象,对象保存了react.element
的信息。
cloneElement
cloneElement
:克隆并返回一个新的React
元素,
结构:React.createElement(type, [props], [...children])
React.cloneElement()
几乎等同于:
<element.type {...element.props} {...props}>
{children}
</element.type>
复制代码
举个例子🌰:
import React from 'react';
const Child = () => {
const children = React.cloneElement(<div>大家好,我是小杜杜</div>, {name: '小杜杜'})
console.log(children)
return <div>{children}</div>
}
const Index = () => {
return <div style={{padding: 20}}>
<Child />
</div>
}
export default Index;
复制代码
打印下children
来看看:
image.png
其实是可以看到传递的name
的,也就是说可以通过React.cloneElement
方法去对组件进行一些赋能
createContext
createContext:相信大家对这个Api很熟悉,用于传递上下文。createContext
会创建一个Context
对象,用Provider
的value
来传递值,用Consumer
接受value
我们实现一个父传孙的小栗子🌰:
import React, { useState } from 'react';
const Content = React.createContext()
const Child = () => {
return <Content.Consumer>
{(value) => <Son {...value} />}
</Content.Consumer>
}
const Son = (props) => {
return <>
<div>大家好,我是{props.name}</div>
<div>幸运数字是:{props.number}</div>
</>
}
const Index = () => {
const [data, _] = useState({
name: '小杜杜',
number: 7
})
return <div style={{padding: 20}}>
<Content.Provider value={data}>
<Child />
</Content.Provider>
</div>
}
export default Index;
复制代码
效果:
image.png
注意:如果Consumer
上一级一直没有Provider
,则会应用defaultValue
作为value
。
只有当组件所处的树中没有匹配到 Provider
时,其 defaultValue
参数才会生效。
Children
Children: 提供处理this.props.children
不透明数据结构的实用程序.
那么什么是不透明
的数据呢?
先来看看下面的栗子🌰:
import React, { useEffect } from 'react';
const Child = ({children}) => {
console.log(children)
return children
}
const Index = () => {
return <div style={{padding: 20}}>
<Child>
<p>大家好,我是小杜杜</p>
<p>大家好,我是小杜杜</p>
<p>大家好,我是小杜杜</p>
<p>Hello~</p>
</Child>
</div>
}
export default Index;
复制代码
打印下children
看到:
image.png
我们可以看到每个节点都打印出来了,这种情况属于透明的
,但我们要是便利看看:
<Child>
{
[1,2,3].map((item) => <p key={item}>大家好,我是小杜杜</p>)
}
<p>Hello~</p>
</Child>
复制代码
image.png
却发现我们便利的三个元素被包了一层,像这种数据被称为不透明
,我们想要处理这种数据,就要以来React.Chilren
来解决
Children.map
Children.map
:遍历,并返回一个数组,针对上面的情况,我们可以通过这个方法将数据便会原先的
const Child = ({children}) => {
const res = React.Children.map(children, (item) => item)
console.log(res)
return res
}
复制代码
效果:
image.png
Children.forEach
Children.forEach
:与Children.map
类似,不同的是Children.forEach
并不会返回值,而是停留在遍历阶段
const Child = ({children}) => {
React.Children.forEach(children, (item) => console.log(item))
return children
}
复制代码
效果:
image.png
Children.count
Children.count:返回Child内的总个数,等于回调传递给map
或forEach
将被调用的次数。如:
const Child = ({children}) => {
const res = React.Children.count(children)
console.log(res) // 4
return children
}
复制代码
Children.only
Children.only:验证Child
是否只有一个元素,如果是,则正常返回,如果不是,则会报错。🤷♂不知道这个有啥用~
const Child = ({children}) => {
console.log(React.Children.only(children))
return children
}
复制代码
效果:只有一个时:
image.png
多个时:
Children.toArray
Children.toArray:以平面数组的形式返回children
不透明数据结构,每个子元素都分配有键。
如果你想在你的渲染方法中操作子元素的集合,特别是如果你想this.props.children
在传递它之前重新排序或切片,这很有用。
我们在原先的例子上在加一次来看看:
import React from 'react';
const Child = ({children}) => {
console.log(`原来数据:`, children)
const res = React.Children.toArray(children)
console.log(`扁平后的数据:`, res)
return res
}
const Index = () => {
return <div style={{padding: 20}}>
<Child>
{
[1,2,3].map((item) => [5, 6].map((ele) => <p key={`${item}-${ele}`}>大家好,我是小杜杜</p>))
}
<p>Hello~</p>
</Child>
</div>
}
export default Index;
复制代码
效果:
这里需要注意的是key
,经过Children.toArray
处理后,会给原本的key
添加前缀,以使得每个元素 key
的范围都限定在此函数入参数组的对象内。
createRef
createRef:创建一个ref
对象,获取节点信息,直接举个例子:
import React, { Component } from 'react';
class Index extends Component{
constructor(props){
super(props)
}
node = React.createRef()
componentDidMount(){
console.log(this.node)
}
render(){
return <div ref={this.node} > 节点信息 </div>
}
}
export default Index;
复制代码
效果:
image.png
这个有点鸡肋,因为我们可以直接从ref
上获取到值,没有必要通过createRef
去获取,像这样
import React, { Component } from 'react';
class Index extends Component{
constructor(props){
super(props)
}
node = null
componentDidMount(){
console.log(this.node)
}
render(){
return <div ref={(node) => this.node = node} > 节点信息 </div>
}
}
export default Index;
复制代码
createFactory
createFactory:返回一个生成给定类型的 React 元素的函数。
接受一个参数type
,这个type
与createElement
的type
一样,原生组件的话是标签的字符串,如“div”
,如果是React
自定义组件,则会传入组件
效果与createElement
一样,但这个说是遗留的,官方建议直接使用createElement
,并且在使用上也会给出警告
栗子🌰:
import React, { useEffect } from 'react';
const Child = React.createFactory(()=><div>createFactory</div>)
const Index = () => {
return <div style={{padding: 20}}>
大家好,我是小杜杜
<Child />
</div>
}
export default Index;
复制代码
image.png
isValidElement
isValidElement:用于验证是否是React元素,是的话就返回true
,否则返回false
,感觉这个Api
也不是特别有用,因为我们肯定知道是否是
栗子🌰:
console.log(React.isValidElement(<div>大家好,我是小杜杜</div>)) // true
console.log(React.isValidElement('大家好,我是小杜杜')) //false
复制代码
version
查看React的版本号:如:
console.log(React.version)
复制代码
版本:
image.png
我们可以看下在React
中的文件位置,在react
中有一个单独处理版本信息的位置:
packages/shared/ReactVersion.js
image.png
生命周期
React
的 生命周期主要有两个比较大的版本,分别是v16.0前
和v16.4
两个版本的生命周期,我们分别说下旧的和新的生命周期,做下对比~
v16.0前
image.png
从图中,总共分为四大阶段:Intialization(初始化)
、Mounting(挂载)
、Update(更新)
、Unmounting(卸载)
Intialization(初始化)
在初始化阶段,我们会用到 constructor()
这个构造函数,如:
constructor(props) {
super(props);
}
复制代码
-
super的作用
“ 用来调用基类的构造方法( constructor() ), 也将父组件的props注入给子组件,供子组件读取 (组件中props只读不可变``,state可变
) -
初始化操作,定义this.state的初始内容
-
只会执行一次
Mounting(挂载)
componentWillMount:在组件挂载到DOM前调用
-
这里面的调用的
this.setState
不会引起组件的重新渲染,也可以把写在这边的内容提到constructor()
,所以在项目中很少。 -
只会调用一次
render: 渲染
-
只要
props
和state
发生改变
(无两者的重传递和重赋值,论值是否有变化,都可以引起组件重新render),都会重新渲染render。 -
return
:是必须的,是一个React元素(UI,描述组件),不负责组件实际渲染工作,由React自身根据此元素去渲染出DOM。 -
render
是纯函数(Pure function:返回的结果只依赖与参数,执行过程中没有副作用),不能执行this.setState。
componentDidMount:组件挂载到DOM后调用
-
调用一次
Update(更新)
componentWillReceiveProps(nextProps):调用于props引起的组件更新过程中
-
nextProps
:父组件传给当前组件新的props -
可以用
nextProps
和this.props
来查明重传props是否发生改变(原因:不能保证父组件重传的props有变化) -
只要props发生变化就会,引起调用
**shouldComponentUpdate(nextProps, nextState)**:性能优化组件
-
nextProps:当前组件的this.props
-
nextState:当前组件的this.state
-
通过比较
nextProps
和nextState
,来判断当前组件是否有必要继续执行更新过程。返回false:表示停止更新,用于减少组件的不必要渲染,优化性能
返回true:继续执行更新
-
像
componentWillReceiveProps()
中执行了this.setState,更新了state,但在render
前(如shouldComponentUpdate,componentWillUpdate),this.state依然指向更新前的state,不然nextState及当前组件的this.state的对比就一直是true了
**componentWillUpdate(nextProps, nextState)**:组件更新前调用
-
在render方法前执行
-
由于组件更新就会调用,所以一般很少使用
-
render:重新渲染
**componentDidUpdate(prevProps, prevState)**:组件更新后被调用
-
prevProps:组件更新前的props
-
prevState:组件更新前的state
-
可以操作组件更新的DOM
Unmounting(卸载)
componentWillUnmount:组件被卸载前调用
-
可以在这里执行一些清理工作,比如清楚组件中使用的定时器,清楚componentDidMount中手动创建的DOM元素等,以避免引起内存泄漏
React v16.4
12b.png
与 v16.0的生命周期相比
新增了 getDerivedStateFromProps 和getSnapshotBeforeUpdate
取消了 componentWillMount、componentWillReceiveProps、componentWillUpdate
getDerivedStateFromProps
**getDerivedStateFromProps(prevProps, prevState)**:组件创建和更新时调用的方法
-
prevProps
:组件更新前的props -
prevState
:组件更新前的state
注意:在React v16.3中,在创建和更新时,只能是由父组件引发才会调用这个函数,在React v16.4改为无论是Mounting还是Updating,也无论是什么引起的Updating,全部都会调用。
有点类似于componentWillReceiveProps
,不同的是getDerivedStateFromProps
是一个静态函数,也就是这个函数不能通过this访问到class的属性,当然也不推荐使用
如果props传入的内容不需要影响到你的state,那么就需要返回一个null,这个返回值是必须的,所以尽量将其写到函数的末尾。
在组件创建时和更新时的render方法之前调用,它应该返回一个对象来更新状态,或者返回null来不更新任何内容。
getSnapshotBeforeUpdate
getSnapshotBeforeUpdate(prevProps,prevState)
:Updating时的函数,在render之后调用
-
prevProps
:组件更新前的props -
prevState
:组件更新前的state
可以读取,但无法使用DOM的时候,在组件可以在可能更改之前从DOM捕获一些信息(例如滚动位置)
返回的任何指都将作为参数传递给componentDidUpdate()
注意
在17.0的版本,官方彻底废除 componentWillMount
、componentWillReceiveProps
、componentWillUpdate
如果还想使用的话可以使用:UNSAFE_componentWillMount()
、UNSAFE_componentWillReceiveProps()
、UNSAFE_componentWillUpdate()
对了,如果在面试的时候可能会问道有关生命周期的问题,建议各位小伙伴,将以上的生命周期都可说一说,然后做个对比,这样的话,效果肯定不错~
react-hooks
react-hooks
是React 16.8
的产物,给函数式组件赋上了生命周期
,再经过三年多的时间,函数式组件
已经逐渐取代了类组件
,可以说是React
开发者必备的技术
同时在React v18
中又出现了一些hooks
,今天我们将一起详细的看看,确保你能迅速掌握~
React v16.8中的hooks
useState
useState:定义变量,可以理解为他是类组件中的this.state
使用:
const [state, setState] = useState(initialState);
复制代码
-
state
:目的是提供给UI
,作为渲染视图的数据源 -
setState
:改变 state 的函数,可以理解为this.setState
-
initialState
:初始默认值
在这里我介绍两种写法,直接看栗子🌰:
import React, { useState } from 'react';
import { Button } from 'antd-mobile';
const Index = () => {
const [ number, setNumber ] = useState(0)
return <div style={{padding: 20}}>
<div>数字:{number}</div>
<Button
color='primary'
onClick={() => {
setNumber(number + 1) //第一种
}}
>
点击加1
</Button>
<Button
color='primary'
style={{marginLeft: 8}}
onClick={() => {
setNumber((value) => value + 2) //第二种
}}
>
点击加2
</Button>
</div>
}
export default Index
复制代码
效果:
img5.gif
注意点
useState
有点类似于PureComponent
,会进行一个比较浅的比较,如果是对象的时候直接传入并不会更新,这点一定要切记,如:
import React, { useState } from 'react';
import { Button } from 'antd-mobile';
const Index = () => {
const [ state, setState ] = useState({number: 0})
return <div style={{padding: 20}}>
<div>数字:{state.number}</div>
<Button
color='primary'
onClick={() => {
state.number++
setState(state)
}}
>
点击
</Button>
</div>
}
export default Index
复制代码
img1.gif
useEffect
useEffect
:副作用,你可以理解为是类组件的生命周期,也是我们最常用的钩子
那么什么是副作用呢?副作用(Side Effect:是指 function 做了和本身运算返回值无关的事,如请求数据、修改全局变量,打印、数据获取、设置订阅以及手动更改 React
组件中的 DOM
都属于副作用操作都算是副作用
我们直接演示下它的用法栗子🌰:
不断执行
当useEffect
不设立第二个参数时,无论什么情况,都会执行
模拟初始化和卸载
我们可以利用useEffect
弄挂载
和卸载
阶段,通常我们用于监听addEventListener
和removeEventListener
的使用
import React, { useState, useEffect } from 'react';
import { Button } from 'antd-mobile';
const Child = () => {
useEffect(() => {
console.log('挂载')
return () => {
console.log('卸载')
}
}, [])
return <div>大家好,我是小杜杜</div>
}
const Index = () => {
const [ flag, setFlag ] = useState(false)
return <div style={{padding: 20}}>
<Button
color='primary'
onClick={() => {
setFlag(v => !v)
}}
>
{flag ? '卸载' : '挂载'}
</Button>
{flag && <Child />}
</div>
}
export default Index
复制代码
效果:
img1.gif
根据依赖值改变
我们可以设置useEffect
的第二个值来改变
import React, { useState, useEffect } from 'react';
import { Button } from 'antd-mobile';
const Index = () => {
const [ number, setNumber ] = useState(0)
const [ count, setCount ] = useState(0)
useEffect(() => {
console.log('count改变才会执行')
}, [count])
return <div style={{padding: 20}}>
<div>number: {number} count: {count}</div>
<Button
color='primary'
onClick={() => setNumber(v => v + 1)}
>
number点击加1
</Button>
<Button
color='primary'
style={{marginLeft: 8}}
onClick={() => setCount(v => v + 1)}
>
count点击加1
</Button>
</div>
}
export default Index
复制代码
效果:
useContent
useContent:上下文,类似于Context
:其本意就是设置全局共享数据,使所有组件可跨层级实现共享
useContent
的参数一般是由createContext
的创建,通过 CountContext.Provider
包裹的组件,才能通过 useContext
获取对应的值
举个例子🌰:
import React, { useState, createContext, useContext } from 'react';
import { Button } from 'antd-mobile';
const CountContext = createContext(-1)
const Child = () => {
const count = useContext(CountContext)
return <div style={{marginTop: 20}}>
子组件获取到的count: {count}
<Son />
</div>
}
const Son = () => {
const count = useContext(CountContext)
return <div style={{marginTop: 20}}>
孙组件获取到的count: {count}
</div>
}
const Index = () => {
const [ count, setCount ] = useState(0)
return <div style={{padding: 20}}>
<div>父组件:{count}</div>
<Button
color='primary'
onClick={() => setCount(v => v + 1)}
>
点击加1
</Button>
<CountContext.Provider value={count}>
<Child />
</CountContext.Provider>
</div>
}
export default Index
复制代码
效果:
useReducer
useReducer:它类似于redux
功能的api
结构:
const [state, dispatch] = useReducer(reducer, initialArg, init);
复制代码
-
state
:更新后的state
值 -
dispatch
:可以理解为和useState
的setState
一样的效果 -
reducer
:可以理解为redux
的reducer
-
initialArg
:初始值 -
init
:惰性初始化
直接来看看栗子🌰:
import React, { useReducer } from 'react';
import { Button } from 'antd-mobile';
const Index = () => {
const [count, dispatch] = useReducer((state, action)=> {
switch(action?.type){
case 'add':
return state + action?.payload;
case 'sub':
return state - action?.payload;
default:
return state;
}
}, 0);
return <div style={{padding: 20}}>
<div>count:{count}</div>
<Button
color='primary'
onClick={() => dispatch({type: 'add', payload: 1})}
>
加1
</Button>
<Button
color='primary'
style={{marginLeft: 8}}
onClick={() => dispatch({type: 'sub', payload: 1})}
>
减1
</Button>
</div>
}
export default Index
复制代码
效果:
img4.gif
useMemo
useMemo:与memo
的理念上差不多,都是判断是否满足当前的限定条件来决定是否执行callback
函数,而useMemo
的第二个参数是一个数组,通过这个数组来判定是否更新回掉函数
当一个父组件中调用了一个子组件的时候,父组件的 state 发生变化,会导致父组件更新,而子组件虽然没有发生改变,但也会进行更新。
简单的理解下,当一个页面内容非常复杂,模块非常多的时候,函数式组件会从头更新到尾,只要一处改变,所有的模块都会进行刷新,这种情况显然是没有必要的。
我们理想的状态是各个模块只进行自己的更新,不要相互去影响,那么此时用useMemo
是最佳的解决方案。
这里要尤其注意一点,只要父组件的状态更新,无论有没有对自组件进行操作,子组件都会进行更新,useMemo
就是为了防止这点而出现的
为了更好的理解useMemo
,我们来看下面一个小栗子🌰:
// usePow.ts
const Index = (list: number[]) => {
return list.map((item:number) => {
console.log(1)
return Math.pow(item, 2)
})
}
export default Index;
// index.tsx
import { Button } from 'antd-mobile';
import React,{ useState } from 'react';
import { usePow } from '@/components';
const Index:React.FC<any> = (props)=> {
const [flag, setFlag] = useState<boolean>(true)
const data = usePow([1, 2, 3])
return (
<div>
<div>数字:{JSON.stringify(data)}</div>
<Button color='primary' onClick={() => {setFlag(v => !v)}}>切换</Button>
<div>切换状态:{JSON.stringify(flag)}</div>
</div>
);
}
export default Index;
复制代码
我们简单的写了个 usePow
,我们通过 usePow
给所传入的数字平方, 用切换状态的按钮表示函数内部的状态,我们来看看此时的效果:
img2.gif
我们发现了一个问题,为什么点击切换按钮也会触发console.log(1)
呢?
这样明显增加了性能开销,我们的理想状态肯定不希望做无关的渲染,所以我们做自定义 hooks
的时候一定要注意,需要减少性能开销,我们为组件加入 useMemo
试试:
import { useMemo } from 'react';
const Index = (list: number[]) => {
return useMemo(() => list.map((item:number) => {
console.log(1)
return Math.pow(item, 2)
}), [])
}
export default Index;
复制代码
img3.gif
发现此时就已经解决了这个问题,不会在做相关的渲染了
useCallback
useCallback
与useMemo
极其类似,可以说是一模一样,唯一不同的是useMemo
返回的是函数运行的结果,而useCallback
返回的是函数
注意:这个函数是父组件传递子组件的一个函数,防止做无关的刷新,其次,这个组件必须配合memo
,否则不但不会提升性能,还有可能降低性能
import React, { useState, useCallback } from 'react';
import { Button } from 'antd-mobile';
const MockMemo: React.FC<any> = () => {
const [count,setCount] = useState(0)
const [show,setShow] = useState(true)
const add = useCallback(()=>{
setCount(count + 1)
},[count])
return (
<div>
<div style={{display: 'flex', justifyContent: 'flex-start'}}>
<TestButton title="普通点击" onClick={() => setCount(count + 1) }/>
<TestButton title="useCallback点击" onClick={add}/>
</div>
<div style={{marginTop: 20}}>count: {count}</div>
<Button onClick={() => {setShow(!show)}}> 切换</Button>
</div>
)
}
const TestButton = React.memo((props:any)=>{
console.log(props.title)
return <Button color='primary' onClick={props.onClick} style={props.title === 'useCallback点击' ? {
marginLeft: 20
} : undefined}>{props.title}</Button>
})
export default MockMemo;
复制代码
img2.gif
我们可以看到,当点击切换按钮的时候,没有经过 useCallback
封装的函数会再次刷新,而经过 useCallback
包裹的函数不会被再次刷新
有很多小伙伴有个误区,就是useCallback
不能单独使用,必须要配合memo
吗?
其实是这样的,你可以单独使用useCallback
,但只用useCallback
起不到优化的作用,反而会增加性能消耗
想之前讲的,React.memo
会通过浅比较里面的props
,如果没有memo
,那么使用的useCallback
也就毫无意义
因为useCallback
本身是需要开销的,所以反而会增加性能的消耗
useRef
useRef:可以获取当前元素的所有属性,并且返回一个可变的ref对象,并且这个对象只有current属性,可设置initialValue
结构:
const refContainer = useRef(initialValue);
复制代码
有许多小伙伴只知道useRef
可以获取对应元素的属性,但useRef
还具备一个功能,就是缓存数据
,接下来一起看看:
通过useRef获取对应的属性值
栗子🌰:
import React, { useState, useRef } from 'react';
const Index:React.FC<any> = () => {
const scrollRef = useRef<any>(null);
const [clientHeight, setClientHeight ] = useState<number>(0)
const [scrollTop, setScrollTop ] = useState<number>(0)
const [scrollHeight, setScrollHeight ] = useState<number>(0)
const onScroll = () => {
if(scrollRef?.current){
let clientHeight = scrollRef?.current.clientHeight; //可视区域高度
let scrollTop = scrollRef?.current.scrollTop; //滚动条滚动高度
let scrollHeight = scrollRef?.current.scrollHeight; //滚动内容高度
setClientHeight(clientHeight)
setScrollTop(scrollTop)
setScrollHeight(scrollHeight)
}
}
return (
<div >
<div >
<p>可视区域高度:{clientHeight}</p>
<p>滚动条滚动高度:{scrollTop}</p>
<p>滚动内容高度:{scrollHeight}</p>
</div>
<div style={{height: 200, overflowY: 'auto'}} ref={scrollRef} onScroll={onScroll} >
<div style={{height: 2000}}></div>
</div>
</div>
);
};
export default Index;
复制代码
效果:
从上述可知,我们可以通过useRef
来获取对应元素的相关属性,以此来做一些操作
缓存数据
react-redux
的源码中,在hooks推出后,react-redux
用大量的useMemo重做了Provide等核心模块,其中就是运用useRef来缓存数据,并且所运用的 useRef() 没有一个是绑定在dom元素上的,都是做数据缓存用的
可以简单的来看一下:
// 缓存数据
/* react-redux 用userRef 来缓存 merge之后的 props */
const lastChildProps = useRef()
// lastWrapperProps 用 useRef 来存放组件真正的 props信息
const lastWrapperProps = useRef(wrapperProps)
//是否储存props是否处于正在更新状态
const renderIsScheduled = useRef(false)
//更新数据
function captureWrapperProps(
lastWrapperProps,
lastChildProps,
renderIsScheduled,
wrapperProps,
actualChildProps,
childPropsFromStoreUpdate,
notifyNestedSubs
) {
lastWrapperProps.current = wrapperProps
lastChildProps.current = actualChildProps
renderIsScheduled.current = false
}
复制代码
我们看到 react-redux
用重新赋值的方法,改变了缓存的数据源,减少了不必要的更新,如过采取useState
势必会重新渲染。
有的时候我们需要使用useMemo、useCallbackApi,我们控制变量的值用useState 有可能会导致拿到的是旧值,并且如果他们更新会带来整个组件重新执行,这种情况下,我们使用useRef将会是一个非常不错的选择
useImperativeHandle
useImperativeHandle:可以让你在使用 ref
时自定义暴露给父组件的实例值
这个Api我觉得是十分有用的,建议掌握哦,来看看使用的场景:
在一个页面很复杂的时候,我们会将这个页面进行模块化,这样会分成很多个模块,有的时候我们需要在最外层的组件上
控制其他组件的方法,希望最外层的点击事件,同时执行子组件的事件
,这时就需要 useImperativeHandle 的帮助
结构:
useImperativeHandle(ref, createHandle, [deps])
复制代码
-
ref
:useRef
所创建的ref -
createHandle
:处理的函数,返回值作为暴露给父组件的 ref 对象。 -
deps
:依赖项,依赖项更改形成新的 ref 对象。
举个栗子🌰:
import React, { useState, useImperativeHandle, useRef } from 'react';
import { Button } from 'antd-mobile';
const Child = ({cRef}) => {
const [count, setCount] = useState(0)
useImperativeHandle(cRef, () => ({
add
}))
const add = () => {
setCount((v) => v + 1)
}
return <div style={{marginBottom: 20}}>
<p>点击次数:{count}</p>
<Button color='primary' onClick={() => add()}>加1</Button>
</div>
}
const Index = () => {
const ref = useRef(null)
return <div style={{padding: 20}}>
<div>大家好,我是小杜杜</div>
<div>注意:是在父组件上的按钮,控制子组件的加1哦~</div>
<Button
color='primary'
onClick={() => ref.current.add()}
>
父节点上的加1
</Button>
<Child cRef={ref} />
</div>
}
export default Index
复制代码
效果:
img5.gif
useLayoutEffect
useLayoutEffect:与useEffect
基本一致,不同的地方时,useLayoutEffect
是同步
要注意的是useLayoutEffect
在 DOM 更新之后,浏览器绘制之前,这样做的好处是可以更加方便的修改 DOM
,获取 DOM
信息,这样浏览器只会绘制一次,所以useLayoutEffect
在useEffect
之前执行
如果是useEffect
的话 ,useEffect
执行在浏览器绘制视图之后,如果在此时改变DOM
,有可能会导致浏览器再次回流
和重绘
。
除此之外useLayoutEffect
的 callback
中代码执行会阻塞浏览器绘制
举个例子🌰:
import React, { useState, useLayoutEffect, useEffect, useRef } from 'react';
import { Button } from 'antd-mobile';
const Index = () => {
const [count, setCount] = useState(0)
const time = useRef(null)
useEffect(()=>{
if(time.current){
console.log("useEffect:", performance.now() - time.current)
}
})
useLayoutEffect(()=>{
if(time.current){
console.log("useLayoutEffect:", performance.now() - time.current)
}
})
return <div style={{padding: 20}}>
<div>count: {count}</div>
<Button
color='primary'
onClick={() => {
setCount(v => v + 1)
time.current = performance.now()
}}
>
加1
</Button>
</div>
}
export default Index
复制代码
效果:
img6.gif
useDebugValue
useDebugValue:可用于在 React
开发者工具中显示自定义 hook 的标签
官方并不推荐你向每个自定义 Hook 添加 debug 值。当它作为共享库的一部分时才最有价值。
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
// ...
// 在开发者工具中的这个 Hook 旁边显示标签
// e.g. "FriendStatus: Online" useDebugValue(isOnline ? 'Online' : 'Offline');
return isOnline;
}
复制代码
React v18中的hooks
useSyncExternalStore
useSyncExternalStore:是一个推荐用于读取
和订阅外部数据源
的 hook
,其方式与选择性的 hydration
和时间切片等并发渲染功能兼容
结构:
const state = useSyncExternalStore(subscribe, getSnapshot[, getServerSnapshot])
复制代码
-
subscribe
: 订阅函数,用于注册一个回调函数,当存储值发生更改时被调用。此外,useSyncExternalStore
会通过带有记忆性的getSnapshot
来判别数据是否发生变化,如果发生变化,那么会强制更新
数据。 -
getSnapshot
: 返回当前存储值的函数。必须返回缓存的值。如果getSnapshot
连续多次调用,则必须返回相同的确切值,除非中间有存储值更新。 -
getServerSnapshot
:返回服务端(hydration
模式下)渲染期间使用的存储值的函数
举个栗子🌰:
import React, {useSyncExternalStore} from 'react';
import { combineReducers , createStore } from 'redux'
const reducer = (state=1,action) => {
switch (action.type){
case 'ADD':
return state + 1
case 'DEL':
return state - 1
default:
return state
}
}
/* 注册reducer,并创建store */
const rootReducer = combineReducers({ count: reducer })
const store = createStore(rootReducer,{ count: 1 })
const Index = () => {
// 订阅
const state = useSyncExternalStore(store.subscribe,() => store.getState().count)
return <div>
<div>{state}</div>
<div>
<button onClick={() => store.dispatch({ type:'ADD' })} >加1</button>
<button style={{marginLeft: 8}} onClick={() => store.dispatch({ type:'DEL' })} >减1</button>
</div>
</div>
}
export default Index
复制代码
效果:
img1.gif
从上述代码可以看出,当点击按钮后,会触发 store.subscribe
(订阅函数),执行getSnapshot
后得到新的count
,如果count
发生变化,则会触发更新
useTransition
useTransition:返回一个状态值表示过渡任务的等待状态,以及一个启动该过渡任务的函数。
那么什么是过渡任务?
在一些场景中,如:输入框、tab切换、按钮等,这些任务需要视图上立刻
做出响应,这些任务可以称之为立即更新的任务
但有的时候,更新任务并不是那么紧急,或者来说要去请求数据等,导致新的状态不能立更新,需要用一个loading...
的等待状态,这类任务就是过度任务
结构:
const [isPending, startTransition] = useTransition();
复制代码
-
isPending
:过渡状态的标志,为true
时是等待状态 -
startTransition
:可以将里面的任务变成过渡任务
大家可能对上面的描述存在着一些疑问,我们直接举个例子🌰来说明:
import React, { useState, useTransition } from 'react';
const Index = () => {
const [isPending, startTransition] = useTransition();
const [input, setInput] = useState('');
const [list, setList] = useState([]);
return <div>
<div>大家好:我是小杜杜~</div>
输入框: <input
value={input}
onChange={(e) => {
setInput(e.target.value);
startTransition(() => {
const res = [];
for (let i = 0; i < 2000; i++) {
res.push(e.target.value);
}
setList(res);
});
}} />
{isPending ? (
<div>加载中...</div>
) : (
list.map((item, index) => <div key={index}>{item}</div>)
)}
</div>
}
export default Index
复制代码
效果:
img3.gif
实际上,我们在Input
输入内容是,会进行增加,假设我们在startTransition
中请求一个接口,在接口请求的时候,isPending
会为true
,就会有一个loading
的状态,请求完之后,isPending
变为false
渲染列表
useDeferredValue
useDeferredValue:接受一个值,并返回该值的新副本,该副本将推迟到更紧急地更新之后。
如果当前渲染是一个紧急更新的结果,比如用户输入,React
将返回之前的值,然后在紧急渲染完成后渲染新的值。
也就是说useDeferredValue
可以让状态滞后派生
结构:
const deferredValue = useDeferredValue(value);
复制代码
-
value
:可变的值,如useState
创建的值 -
deferredValue
: 延时状态
这个感觉和useTransition
有点相似,还是以输入框的模式,举个栗子🌰:
import React, { useState, useDeferredValue } from 'react';
const getList = (key) => {
const arr = [];
for (let i = 0; i < 10000; i++) {
if (String(i).includes(key)) {
arr.push(<li key={i}>{i}</li>);
}
}
return arr;
};
const Index = () => {
const [value, setValue] = useState("");
const deferredValue = useDeferredValue(value);
console.log('value:', value);
console.log('deferredValue:',deferredValue);
return (
<div >
<div>
<div>大家好,我是小杜杜</div>
输入框:<input onChange={(e) => setValue(e.target.value)} />
</div>
<div>
<ul>{deferredValue ? getList(deferredValue) : null}</ul>
</div>
</div>
);
}
export default Index
复制代码
效果:
img4.gif
和useTransition做对比
根据上面两个示例我们看看useTransition
和useDeferredValue
做个对比看看
-
相同点:
useDeferredValue
和useTransition
一样,都是过渡更新任务 -
不同点:
useTransition
给的是一个状态,而useDeferredValue
给的是一个值
useInsertionEffect
useInsertionEffect:与 useEffect
一样,但它在所有 DOM 突变 之前同步触发。
我们来看看useInsertionEffect
对比于useEffect
和useLayoutEffect
在执行顺序上有什么区别,栗子🌰:
useEffect(()=>{
console.log('useEffect')
},[])
useLayoutEffect(()=>{
console.log('useLayoutEffect')
},[])
useInsertionEffect(()=>{
console.log('useInsertionEffect')
},[])
复制代码
image.png
可以看到在执行顺序上 useInsertionEffect
> useLayoutEffect
> useEffect
特别注意一点:seInsertionEffect
应仅限于 css-in-js 库作者使用。优先考虑使用 useEffect
或 useLayoutEffect
来替代。
模拟一下seInsertionEffect
的使用🌰:
import React, { useInsertionEffect } from 'react';
const Index = () => {
useInsertionEffect(()=>{
const style = document.createElement('style')
style.innerHTML = `
.css-in-js{
color: blue;
}
`
document.head.appendChild(style)
},[])
return (
<div>
<div className='css-in-js'>大家好,我是小杜杜</div>
</div>
);
}
export default Index
复制代码
效果:
image.png
useId
useId : 是一个用于生成横跨服务端和客户端的稳定的唯一 ID
的同时避免hydration
不匹配的 hook
。
这里牵扯到SSR
的问题,我打算之后在单独写一章,来详细讲讲,所以在这里就介绍一下使用即可
const id = useId();
复制代码
例子🌰:
import React, { useId } from 'react';
const Index = () => {
const id = useId()
return (
<div>
<div id={id} >
大家好,我是小杜杜
</div>
</div>
);
}
export default Index
复制代码
效果:
自定义hooks
自定义hooks
是在react-hooks
基础上的一个扩展,可以根据业务、需求去制定相应的hooks
,将常用的逻辑进行封装,从而具备复用性
关于自定义hooks
的内容可以看看我之前的文章:搞懂这12个Hooks,保证让你玩转React[8]
里面通过分析ahooks
源码,讲解了很多不错的自定义hooks
,如:useCreation
、useReactive
、useEventListener
等的实现,相信一定能够帮助到各位,感兴趣的可以支持下~
react-dom
react-dom:这个包提供了用户DOM的特定方法。这个包在React v18
中还是做了很大的改动,接下来我们逐个看看
createPortal
createPortal:在Portal
中提供了一种将子节点渲染到已 DOM 节点中的方式,该节点存在于 DOM 组件的层次结构之外。
也就是说createPortal
可以把当前组件或element
元素的子节点,渲染到组件之外的其他地方。
来看看createPortal(child, container)
的入参:
-
child
:任何可渲染的子元素 -
container
:是一个DOM
元素
看着概念可能并不是很好理解,我们来举个栗子🌰:
import React, { useState, useEffect, useRef } from 'react';
import ReactDom from 'react-dom'
const Child = ({children}) => {
const ref = useRef()
const [newDom, setNewDom] = useState()
useEffect(() => {
setNewDom(ReactDom.createPortal(children, ref.current))
}, [])
return <div>
<div ref={ref}>同级的节点</div>
<div>
这层的节点
{newDom}
</div>
</div>
}
const Index = () => {
return <div style={{padding: 20}}>
<Child>
<div>大家好,我是小杜杜</div>
</Child>
</div>
}
export default Index;
复制代码
要注意下Child
:
我们传入的children
被createPortal
包裹后,children
的节点位置会如何?
发现,我们处理的数newDom
的数据到了同级的节点处,那么这个Api
该如何应用呢?
我们可以处理一些顶层元素,如:Modal
弹框组件,Modal
组件在内部中书写,挂载到外层的容器(如body),此时这个Api
就非常有用
flushSync
flushSync:可以将回调函数中的更新任务,放到一个较高级的优先级中,适用于强制刷新,同时确保了DOM
会被立即更新
为了更好的理解,我们举个栗子🌰:
import { Button } from 'antd-mobile';
import React, { Component} from 'react';
import ReactDOM from 'react-dom'
class Index extends Component{
constructor(props){
super(props)
this.state={
number: 0
}
}
render(){
const { number } = this.state
console.log(number)
return <div style={{padding: 20}}>
<div>数字: {number}</div>
<Button
color='primary'
onClick={() => {
this.setState({ number: 1 })
this.setState({ number: 2 })
this.setState({ number: 3 })
}}
>
点击
</Button>
</div>
}
}
export default Index;
复制代码
我们看看点击按钮会打印出什么?
img6.gif
这个不难理解,因为this.setState
会进行批量更新,所以打印出的是3 接下来,我们用flushSync
处理下number: 2
来看看是什么效果:
onClick={() => {
this.setState({ number: 1 })
ReactDOM.flushSync(()=>{
this.setState({ number: 2 })
})
this.setState({ number: 3 })
}}
复制代码
img1.gif
可以发现flushSync
会优先执行,并且强制刷新,所以会改变number
值为2,然后1
和3
在被批量刷新,更新为3
render
render:这个是我们在react-dom
中最常用的Api,用于渲染一个react
元素
我们通常使用在根部,如:
ReactDOM.render(
< App / >,
document.getElementById('app')
)
复制代码
createRoot
在React v18
中,render
函数已经被createRoot
所替代
createRoot
会控制你传入的容器节点的内容。当调用 render 时,里面的任何现有 DOM 元素都会被替换。后面的调用使用 React 的 DOM diffing 算法进行有效更新。
并且createRoot
不修改容器节点(只修改容器的子节点)。可以在不覆盖现有子节点的情况下将组件插入现有 DOM 节点。
如:
import React, { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
const rootElement = document.getElementById('root');
const root = createRoot(rootElement);
root.render(
<StrictMode>
<Main />
</StrictMode>
);
复制代码
hydrate
hydrate
:服务端渲染用hydrate
与 render()
相同,但它用于在 ReactDOMServer
渲染的容器中对 HTML 的内容进行 hydrate 操作。
hydrate(element, container[, callback])
复制代码
hydrateRoot()
hydrate
在React v18
也被替代为hydrateRoot()
hydrateRoot(container, element[, options])
复制代码
unmountComponentAtNode
unmountComponentAtNode:从 DOM 中卸载组件,会将其事件处理器(event handlers)和 state 一并清除。如果指定容器上没有对应已挂载的组件,这个函数什么也不会做。如果组件被移除将会返回 true
,如果没有组件可被移除将会返回 false
。
举个栗子🌰:
import { Button } from 'antd-mobile';
import React, { Component} from 'react';
import ReactDOM from 'react-dom'
const Child = () => {
return <div>大家好,我是小杜杜</div>
}
class Index extends Component{
constructor(props){
super(props)
this.state={
number: 0
}
}
node = null
componentDidMount(){
ReactDOM.render(<Child/>, this.node) // 创建一个容器
}
render(){
const { number } = this.state
console.log(number)
return <div style={{padding: 20}}>
<div ref={(node) => this.node = node}></div>
<Button
color='primary'
onClick={() => {
const res = ReactDOM.unmountComponentAtNode(this.node)
console.log(res)
}}
>
卸载
</Button>
</div>
}
}
export default Index;
复制代码
效果:
img2.gif
root.unmount()
unmountComponentAtNode
同样在React 18
中被替代了,替换成了createRoot
中的unmount()
方法
const root = createRoot(container);
root.render(element);
root.unmount()
复制代码
findDOMNode
findDOMNode:用于访问组件DOM
元素节点(应急方案),官方推荐使用ref
需要注意的是:
-
findDOMNode
只能用到挂载
的组件上 -
findDOMNode
只能用于类组件,不能用于函数式组件 -
如果组件渲染为
null
或者为false
,那么findDOMNode
返回的值也是null
-
如果是多个子节点
Fragment
的情况,findDOMNode
会返回第一个非空子节点对应的 DOM 节点。 -
在严格模式下这个方法已经被
弃用
举个例子🌰:
import { Button } from 'antd-mobile';
import React, { Component} from 'react';
import ReactDOM from 'react-dom'
class Index extends Component{
render(){
return <div style={{padding: 20}}>
<div>大家好,我是小杜杜</div>
<Button
color='primary'
onClick={() => {
console.log(ReactDOM.findDOMNode(this))
}}
>
获取容器
</Button>
</div>
}
}
export default Index;
复制代码
效果:
unstable_batchedUpdates
unstable_batchedUpdates :可用于手动批量更新state,可以指定多个setState
合并为一个更新请求
那么这块手动合并,用在什么情况下呢?来看看下面的场景:
import { Button } from 'antd-mobile';
import React, { Component} from 'react';
import ReactDOM from 'react-dom'
class Index extends Component{
constructor(props){
super(props)
this.state={
number: 0
}
}
render(){
const { number } = this.state
return <div style={{padding: 20}}>
<div>数字: {number}</div>
<Button
color='primary'
onClick={() => {
this.setState({ number: this.state.number + 1 })
console.log(this.state.number)
this.setState({ number: this.state.number + 1 })
console.log(this.state.number)
this.setState({ number: this.state.number + 1 })
console.log(this.state.number)
}}
>
点击
</Button>
</div>
}
}
export default Index
复制代码
当我们点击按钮后,三个打印会打印出什么?
此时的场景只会执行一次,并且渲染一次,渲染时为1
那么我们打破React
的机制,比如说使用setTimeout
绕过,再来看看会打印出什么:
<Button
color='primary'
onClick={() => {
setTimeout(() => {
this.setState({ number: this.state.number + 1 })
console.log(this.state.number)
this.setState({ number: this.state.number + 1 })
console.log(this.state.number)
this.setState({ number: this.state.number + 1 })
console.log(this.state.number)
}, 100)
}}
>
点击
</Button>
复制代码
此时就会这样:
image.png
因为绕过了事件机制,此时就会渲染3次,并且渲染的结果为3
那么我们现在想在setTimeout
实现React
的事件机制该怎么办?就需要用到unstable_batchedUpdates
来解决这类问题
<Button
color='primary'
onClick={() => {
setTimeout(() => {
ReactDOM.unstable_batchedUpdates(() => {
this.setState({ number: this.state.number + 1 })
console.log(this.state.number)
this.setState({ number: this.state.number + 1 })
console.log(this.state.number)
this.setState({ number: this.state.number + 1 })
console.log(this.state.number)
})
}, 100)
}}
>
点击
</Button>
复制代码
效果:
img4.gif
最后
参考文档
-
react 官方文档[9]
-
「React 进阶」 React 全部 Hooks 使用大全 (包含 React v18 版本 )[10]
总结
本文基本总结了React
的所有Api
,如果有没写到的,或者是Api
用法没写全的,请在下方评论区留言,尽量把这篇文章打造成最全的~
主要包扩组件类
、工具类
、生命周期
、react-hooks
、react-dom
五大模块的内容,如果你能耐心的看完,相信你对React
一定有了更深的理解,同时建议初学者亲自尝试一遍,看看这些Api
怎么用,如何用~