大家好, 俗话说的好,工欲善其事必先利其器,什么意思呢?就是说你想玩转React
就必须知道React
有什么,无论是否运用到,首先都要知道,提升思维广度~
其实React
官方提供了很多Api
,只是这些Api
我们并不常用,所以往往会忽略它们,但在一些特定的场景下,这些Api
也会起到关键性的作用,所以今天就逐个盘点一下,说说它们的使用方法和使用场景。
当然这些Api
并不需要全部掌握,只需要知道有这个知识点就好了~
本文将会全面总结所有的React
Api,包含组件类
、工具类
、生命周期
、react-hooks
、react-dom
五大模块,并配带示例,帮助大家更好的掌握,大家可以边嗑瓜子边阅读,如有不全、不对的地方欢迎在评论区指出~
由于本文过长,建议点赞
+收藏
, 在正式开始前,我抽取了一些问题,一起来看看:
React v18
中对react-dom
做了那些改动,增加了那些新的hooks
?useRef
除了获取元素的节点信息,还能做什么?Children.map
?它与不同的遍历有和不同其实问题很多,看完这篇文章后,相信一定能帮你解答的非常清楚,还请各位小伙伴多多支持一下
在之前的几篇文章中,有自定义
hooks
、HOC
、虚拟DOM
和diff算法
,多多少少都有React官方Api做为基础条件,我的这个专栏的目的就是对React
的深入,就是希望对React
有一个全面的提升写这篇文章的主要目的有:
- 提升知识广度,要想深入
React
就必须全面了解React
,首先要学会用,要知道,如果连知道都不知道,谈何深入?React v18
对react-dom
的改动还是比较大的,并且新增了五个hooks
,逐一盘点一下,看看做了那些改动- 这个专栏实际上是循序渐进的,相互之间都有一定的关联,同时要想看懂,也需要有一定的
React
基础,对刚开始学习React
的小伙伴可能并不是太友好,所以特地总结一番,用最简单的示例,帮你掌握这些Api- 对之后的源码有帮助,所以本篇文章将会全面解读
React Api
的使用方法,和场景,如有不足,还希望在评论区指出~
附上一张今天的学习图谱~
全面解读ReactApi.png
在React中提供两种形式,一种是类组件
,另一种是函数式组件
,而在类组件
组件中需要使用Component
继承,这个组件没有什么好讲的,我们可以看看源码:
文件位置packages/react/src/ReactBaseClasses.js
image.png
可以看出Component
进行一些初始化的工作,updater
保存着更新组件的方法
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()
函数,那么它能改变PureComponent
吗?
其实是可以的,shouldComponentUpdate()
如果被定义,就会对新旧 props
、state
进行 shallowEqual
比较,新旧一旦不一致,便会触发 update
。
也可以这么理解:PureComponent
通过自带的props
和state
的浅比较实现了shouldComponentUpdate()
,这点Component
并不具备
PureComponent
可能会因深层的数据不一致而产生错误的否定判断,从而导致shouldComponentUpdate
结果返回false,界面得不到更新,要谨慎使用
memo:结合了pureComponent纯组件
和 componentShouldUpdate
功能,会对传入的props进行一次对比,然后根据第二个函数返回值来进一步判断哪些props需要更新
要注意memo
是一个高阶组件,函数式组件
和类组件
都可以使用。
memo
接收两个参数:
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
的更新,通过返回的布尔值来控制
React.memo
与PureComponent
的区别:
PureComponent
服务与类组件,React.memo
既可以服务于类组件,也可以服务与函数式组件,useMemo
服务于函数式组件(后续讲到)PureComponent
针对的是props
和state
,React.memo
只能针对props
来决定是否渲染这里还有个小的注意点:memo
的第二个参数的返回值与shouldComponentUpdate
的返回值是相反的,经常会弄混,还要多多注意
memo
:返回 true
组件不渲染 , 返回 false
组件重新渲染。shouldComponentUpdate
: 返回 true
组件渲染 , 返回 false
组件不渲染。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
干嘛,就当熟悉吧~
在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
这个组件可以赋值 key
,也就是索引,<></>
不能赋值,应用在遍历数组上,有感兴趣的同学可以试一试~
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:让组件"等待"某个异步组件操作,直到该异步操作结束即可渲染。
与上面lazy
中的案例一样,两者需要配合使用,其中fallback
为等待时渲染的样式
Suspense
和lazy
可以用于等待照片、脚本和一些异步的情况。
Profiler:这个组件用于性能检测,可以检测一次react
组件渲染时的性能开销
此组件有两个参数:
id
:标识Profiler
的唯一性onRender
:回调函数,用于渲染完成,参数在下面讲解举个栗子:
import React, { Component, Profiler } from 'react';
export default Index;
复制代码
让我们来看看打印的是什么:
image.png
依此的含义:
Profiler
树的id
mount
挂载,update
渲染committed
花费的渲染时间committed
的时间interactions
的集合需要注意的是,这个组件应该在需要的时候去使用,虽然Profiler
是一个轻量级的,但也会带来负担
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
进行检查
主要有以下帮助:
在之前讲的虚拟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
:克隆并返回一个新的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:相信大家对这个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: 提供处理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
:遍历,并返回一个数组,针对上面的情况,我们可以通过这个方法将数据便会原先的
const Child = ({children}) => {
const res = React.Children.map(children, (item) => item)
console.log(res)
return res
}
复制代码
效果:
image.png
Children.forEach
:与Children.map
类似,不同的是Children.forEach
并不会返回值,而是停留在遍历阶段
const Child = ({children}) => {
React.Children.forEach(children, (item) => console.log(item))
return children
}
复制代码
效果:
image.png
Children.count:返回Child内的总个数,等于回调传递给map
或forEach
将被调用的次数。如:
const Child = ({children}) => {
const res = React.Children.count(children)
console.log(res) // 4
return children
}
复制代码
Children.only:验证Child
是否只有一个元素,如果是,则正常返回,如果不是,则会报错。♂不知道这个有啥用~
const Child = ({children}) => {
console.log(React.Children.only(children))
return children
}
复制代码
效果:只有一个时:
image.png
多个时:
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:创建一个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:返回一个生成给定类型的 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:用于验证是否是React元素,是的话就返回true
,否则返回false
,感觉这个Api
也不是特别有用,因为我们肯定知道是否是
栗子:
console.log(React.isValidElement(<div>大家好,我是小杜杜</div>)) // true
console.log(React.isValidElement('大家好,我是小杜杜')) //false
复制代码
查看React的版本号:如:
console.log(React.version)
复制代码
版本:
image.png
我们可以看下在React
中的文件位置,在react
中有一个单独处理版本信息的位置:
packages/shared/ReactVersion.js
image.png
React
的 生命周期主要有两个比较大的版本,分别是v16.0前
和v16.4
两个版本的生命周期,我们分别说下旧的和新的生命周期,做下对比~
image.png
从图中,总共分为四大阶段:Intialization(初始化)
、Mounting(挂载)
、Update(更新)
、Unmounting(卸载)
在初始化阶段,我们会用到 constructor()
这个构造函数,如:
constructor(props) {
super(props);
}
复制代码
super的作用
“ 用来调用基类的构造方法( constructor() ), 也将父组件的props注入给子组件,供子组件读取 (组件中props只读不可变``,state可变
)componentWillMount:在组件挂载到DOM前调用
this.setState
不会引起组件的重新渲染,也可以把写在这边的内容提到constructor()
,所以在项目中很少。render: 渲染
props
和state
发生改变
(无两者的重传递和重赋值,论值是否有变化,都可以引起组件重新render),都会重新渲染render。return
:是必须的,是一个React元素(UI,描述组件),不负责组件实际渲染工作,由React自身根据此元素去渲染出DOM。render
是纯函数(Pure function:返回的结果只依赖与参数,执行过程中没有副作用),不能执行this.setState。componentDidMount:组件挂载到DOM后调用
componentWillReceiveProps(nextProps):调用于props引起的组件更新过程中
nextProps
:父组件传给当前组件新的propsnextProps
和this.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)**:组件更新前调用
**componentDidUpdate(prevProps, prevState)**:组件更新后被调用
componentWillUnmount:组件被卸载前调用
12b.png
与 v16.0的生命周期相比
新增了 getDerivedStateFromProps 和getSnapshotBeforeUpdate
取消了 componentWillMount、componentWillReceiveProps、componentWillUpdate
**getDerivedStateFromProps(prevProps, prevState)**:组件创建和更新时调用的方法
prevProps
:组件更新前的propsprevState
:组件更新前的state注意:在React v16.3中,在创建和更新时,只能是由父组件引发才会调用这个函数,在React v16.4改为无论是Mounting还是Updating,也无论是什么引起的Updating,全部都会调用。
有点类似于componentWillReceiveProps
,不同的是getDerivedStateFromProps
是一个静态函数,也就是这个函数不能通过this访问到class的属性,当然也不推荐使用
如果props传入的内容不需要影响到你的state,那么就需要返回一个null,这个返回值是必须的,所以尽量将其写到函数的末尾。
在组件创建时和更新时的render方法之前调用,它应该返回一个对象来更新状态,或者返回null来不更新任何内容。
getSnapshotBeforeUpdate(prevProps,prevState)
:Updating时的函数,在render之后调用
prevProps
:组件更新前的propsprevState
:组件更新前的state可以读取,但无法使用DOM的时候,在组件可以在可能更改之前从DOM捕获一些信息(例如滚动位置)
返回的任何指都将作为参数传递给componentDidUpdate()
在17.0的版本,官方彻底废除 componentWillMount
、componentWillReceiveProps
、componentWillUpdate
如果还想使用的话可以使用:UNSAFE_componentWillMount()
、UNSAFE_componentWillReceiveProps()
、UNSAFE_componentWillUpdate()
对了,如果在面试的时候可能会问道有关生命周期的问题,建议各位小伙伴,将以上的生命周期都可说一说,然后做个对比,这样的话,效果肯定不错~
react-hooks
是React 16.8
的产物,给函数式组件赋上了生命周期
,再经过三年多的时间,函数式组件
已经逐渐取代了类组件
,可以说是React
开发者必备的技术
同时在React v18
中又出现了一些hooks
,今天我们将一起详细的看看,确保你能迅速掌握~
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
:副作用,你可以理解为是类组件的生命周期,也是我们最常用的钩子
那么什么是副作用呢?副作用(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:上下文,类似于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:它类似于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:与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
与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:可以获取当前元素的所有属性,并且返回一个可变的ref对象,并且这个对象只有current属性,可设置initialValue
结构:
const refContainer = useRef(initialValue);
复制代码
有许多小伙伴只知道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:可以让你在使用 ref
时自定义暴露给父组件的实例值
这个Api我觉得是十分有用的,建议掌握哦,来看看使用的场景:
在一个页面很复杂的时候,我们会将这个页面进行模块化,这样会分成很多个模块,有的时候我们需要在最外层的组件上
控制其他组件的方法,希望最外层的点击事件,同时执行子组件的事件
,这时就需要 useImperativeHandle 的帮助
结构:
useImperativeHandle(ref, createHandle, [deps])
复制代码
ref
:useRef
所创建的refcreateHandle
:处理的函数,返回值作为暴露给父组件的 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:与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:可用于在 React
开发者工具中显示自定义 hook 的标签
官方并不推荐你向每个自定义 Hook 添加 debug 值。当它作为共享库的一部分时才最有价值。
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
// ...
// 在开发者工具中的这个 Hook 旁边显示标签
// e.g. "FriendStatus: Online" useDebugValue(isOnline ? 'Online' : 'Offline');
return isOnline;
}
复制代码
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:返回一个状态值表示过渡任务的等待状态,以及一个启动该过渡任务的函数。
那么什么是过渡任务?
在一些场景中,如:输入框、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:接受一个值,并返回该值的新副本,该副本将推迟到更紧急地更新之后。
如果当前渲染是一个紧急更新的结果,比如用户输入,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
和useDeferredValue
做个对比看看
useDeferredValue
和useTransition
一样,都是过渡更新任务useTransition
给的是一个状态,而useDeferredValue
给的是一个值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 : 是一个用于生成横跨服务端和客户端的稳定的唯一 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
是在react-hooks
基础上的一个扩展,可以根据业务、需求去制定相应的hooks
,将常用的逻辑进行封装,从而具备复用性
关于自定义hooks
的内容可以看看我之前的文章:搞懂这12个Hooks,保证让你玩转React[8]
里面通过分析ahooks
源码,讲解了很多不错的自定义hooks
,如:useCreation
、useReactive
、useEventListener
等的实现,相信一定能够帮助到各位,感兴趣的可以支持下~
react-dom:这个包提供了用户DOM的特定方法。这个包在React v18
中还是做了很大的改动,接下来我们逐个看看
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:可以将回调函数中的更新任务,放到一个较高级的优先级中,适用于强制刷新,同时确保了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:这个是我们在react-dom
中最常用的Api,用于渲染一个react
元素
我们通常使用在根部,如:
ReactDOM.render(
< App / >,
document.getElementById('app')
)
复制代码
在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
与 render()
相同,但它用于在 ReactDOMServer
渲染的容器中对 HTML 的内容进行 hydrate 操作。
hydrate(element, container[, callback])
复制代码
hydrate
在React v18
也被替代为hydrateRoot()
hydrateRoot(container, element[, options])
复制代码
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
unmountComponentAtNode
同样在React 18
中被替代了,替换成了createRoot
中的unmount()
方法
const root = createRoot(container);
root.render(element);
root.unmount()
复制代码
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 :可用于手动批量更新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
的所有Api
,如果有没写到的,或者是Api
用法没写全的,请在下方评论区留言,尽量把这篇文章打造成最全的~
主要包扩组件类
、工具类
、生命周期
、react-hooks
、react-dom
五大模块的内容,如果你能耐心的看完,相信你对React
一定有了更深的理解,同时建议初学者亲自尝试一遍,看看这些Api
怎么用,如何用~
至此,签约计划的三篇文章就写完了,说实话,太累了,写出硬文确实比较难,还是希望大家多多关注这个专栏,后续将会带来更好,更全,更容易理解的React
文章,还请各位小伙伴多多支持,点赞
+ 收藏
哦~
本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/oxQCbznh_zqTEXDPEhlY3A
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。