一文吃透React v18全部Api
作者:小杜杜
https://juejin.cn/post/7124486630483689485
俗話說的好,工欲善其事必先利其器,什么意思呢?就是說你想玩轉(zhuǎn)React
就必須知道React
有什么,無論是否運用到,首先都要知道,提升思維廣度~
其實React
官方提供了很多Api
,只是這些Api
我們并不常用,所以往往會忽略它們,但在一些特定的場景下,這些Api
也會起到關鍵性的作用,所以今天就逐個盤點一下,說說它們的使用方法和使用場景。
當然這些Api
并不需要全部掌握,只需要知道有這個知識點就好了~
本文將會全面總結(jié)所有的React
Api,包含組件類
、工具類
、生命周期
、react-hooks
、react-dom
五大模塊,并配帶示例,幫助大家更好的掌握,大家可以邊嗑瓜子邊閱讀,如有不全、不對的地方歡迎在評論區(qū)指出~
由于本文過長,建議點贊
?+收藏
, 在正式開始前,我抽取了一些問題,一起來看看:
1.
React v18
中對react-dom
做了那些改動,增加了那些新的hooks
?2.
useRef
除了獲取元素的節(jié)點信息,還能做什么?3.為什么會有
Children.map
?它與不同的遍歷有和不同4.類組件的生命周期在不同的版本是怎樣變化的?
5.子元素如何渲染到父元素上面的?
...
其實問題很多,看完這篇文章后,相信一定能幫你解答的非常清楚,還請各位小伙伴多多支持一下
前言
寫這篇文章的主要目的有:
提升知識廣度,要想深入
React
就必須全面了解React
,首先要學會用,要知道,如果連知道都不知道,談何深入?
React v18
?對?react-dom
的改動還是比較大的,并且新增了五個hooks
,逐一盤點一下,看看做了那些改動這個專欄實際上是循序漸進的,相互之間都有一定的關聯(lián),同時要想看懂,也需要有一定的
React
基礎,對剛開始學習React
的小伙伴可能并不是太友好,所以特地總結(jié)一番,用最簡單的示例,幫你掌握這些Api對之后的源碼有幫助,所以本篇文章將會全面解讀
React Api
的使用方法,和場景,如有不足,還希望在評論區(qū)指出~
附上一張今天的學習圖譜~

組件類
Component
在React中提供兩種形式,一種是類組件
,另一種是函數(shù)式組件
,而在類組件
組件中需要使用Component
繼承,這個組件沒有什么好講的,我們可以看看源碼:
文件位置packages/react/src/ReactBaseClasses.js

可以看出Component
進行一些初始化的工作,updater
保存著更新組件的方法
PureComponent
PureComponent:會對props
和state
進行淺比較,跳過不必要的更新,提高組件性能。
可以說PureComponent
和Component
基本完全一致,但PureComponent
會淺比較,也就是較少render
渲染的次數(shù),所以PureComponent
一般用于性能優(yōu)化
那么什么是淺比較,舉個??:
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>?數(shù)字:?{?data.number??}</div>
??????????<Button
????????????color="primary"?
????????????onClick={()?=>?{
????????????????const?{?data?}?=?this.state
????????????????data.number++
????????????????this.setState({?data?})
????????????}}
??????????>數(shù)字加1</Button>
??????</div>
??}
}
export?default?Index;
效果:

可以發(fā)現(xiàn),當我們點擊按鈕的時候,數(shù)字并沒有刷新,這是因為PureComponent
會比較兩次的data
對象,它會認為這種寫法并沒有改變原先的data
,所以不會改變
我們只需要:
this.setState({?data:?{...data}?})
這樣就可以解決這個問題了
與shouldComponentUpdate的關系如何
在生命周期中有一個shouldComponentUpdate()
函數(shù),那么它能改變PureComponent
嗎?
其實是可以的,shouldComponentUpdate()
如果被定義,就會對新舊?props
、state
?進行?shallowEqual
?比較,新舊一旦不一致,便會觸發(fā)?update
。
也可以這么理解:PureComponent
通過自帶的props
和state
的淺比較實現(xiàn)了shouldComponentUpdate()
,這點Component
并不具備
PureComponent
可能會因深層的數(shù)據(jù)不一致而產(chǎn)生錯誤的否定判斷,從而導致shouldComponentUpdate
結(jié)果返回false,界面得不到更新,要謹慎使用
memo
memo:結(jié)合了pureComponent純組件
和?componentShouldUpdate
功能,會對傳入的props進行一次對比,然后根據(jù)第二個函數(shù)返回值來進一步判斷哪些props需要更新
要注意memo
是一個高階組件,函數(shù)式組件
和類組件
都可以使用。
memo
接收兩個參數(shù):
第一個參數(shù):組件本身,也就是要優(yōu)化的組件
第二個參數(shù):(pre, next) => boolean,?
pre
:之前的數(shù)據(jù),?next
:現(xiàn)在的數(shù)據(jù),返回一個布爾值
,若為?true
則不更新,為false
更新
性能優(yōu)化
接下來,我們先看這樣一個??:
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?})}
??????????>狀態(tài)切換{JSON.stringify(flag)}</Button>
??????</div>
??}
}
export?default?Index;
在上述代碼中,我們設置一個子組件,也就是Child
和一個按鈕,按鈕的效果是切換?flag
?的狀態(tài),可以看出flag
和Child
之間沒有任何關系,那么在切換狀態(tài)的時候,Child
會刷新嗎?
直接看看效果:

可以看出,在我們切換狀態(tài)的時候,Child
實際上也會刷新,我們肯定不希望組件做無關的刷新,那么我們加上memo
來看看的效果:
const?HOCChild?=?memo(Child,?(pre,?next)?=>?{
????return?true
})
效果:

可以看出,加上memo
后,Child
不會再做無關的渲染,從而達到性能優(yōu)化
的作用
第二個參數(shù)的作用
栗子??:
import?React,?{?Component,?memo?}?from?'react';
import?{?Button?}?from?'antd-mobile';
const?Child?=?({?number?})?=>?{
????return?<div>
????????{console.log('子組件渲染')}
????????大家好,我是小杜杜~
????????<p>傳遞的數(shù)字:{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})}
??????????>狀態(tài)切換{JSON.stringify(flag)}</Button>
????????<Button
????????????color="primary"
????????????style={{marginLeft:?8}}?
????????????onClick={()?=>?this.setState({?number:?number?+?1})}
????????>數(shù)字加一:{number}</Button>
??????</div>
??}
}
export?default?Index;
效果:

當數(shù)字小于7,才會出發(fā)Child
的更新,通過返回的布爾值來控制
memo的注意事項
React.memo
與PureComponent
的區(qū)別:
服務對象不同:
PureComponent
?服務與類組件,React.memo
既可以服務于類組件,也可以服務與函數(shù)式組件,useMemo
服務于函數(shù)式組件(后續(xù)講到)針對的對象不同:
PureComponent
針對的是props
和state
,React.memo
只能針對props
來決定是否渲染
這里還有個小的注意點:memo
的第二個參數(shù)的返回值與shouldComponentUpdate
的返回值是相反的,經(jīng)常會弄混,還要多多注意
memo
:返回?true
?組件不渲染 , 返回?false
?組件重新渲染。shouldComponentUpdate
: 返回?true
?組件渲染 , 返回?false
?組件不渲染。
forwardRef
forwardRef
:引用傳遞,是一種通過組件向子組件自動傳遞引用ref
的技術。對于應用者的大多數(shù)組件來說沒什么作用,但對于一些重復使用的組件,可能有用。
聽完介紹是不是感覺云里霧里的,官方對forwardRef
的介紹也很少,我們來看看轉(zhuǎn)發(fā)的問題
在React
中,React
不允許ref
通過props
傳遞,因為ref
是組件中固定存在的,在組件調(diào)和的過程中,會被特殊處理,而forwardRef
就是為了解決這件事而誕生的,讓ref
可以通過props
傳遞
舉個栗子??:父組件想要獲取孫組件上的信息,我們直接用ref
傳遞會怎樣:

接下來看看利用forwardRef
來轉(zhuǎn)發(fā)下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;
效果:

如此以來就解決了不能在react
組件中傳遞ref
的問題,至于復用的組件可能會用到,目前也沒思路用forwardRef
干嘛,就當熟悉吧~
Fragment
在React
中,組件是不允許返回多個節(jié)點的,如:
????return?<p>我是小杜杜</p>
???????????<p>React</p>
???????????<p>Vue</p>
我們想要解決這種情況需要給為此套一個容器元素,如<div></div>
????return?<div>
???????<p>我是小杜杜</p>
???????<p>React</p>
???????<p>Vue</p>
????</div>
但這樣做,無疑會多增加一個節(jié)點,所以在16.0
后,官方推出了Fragment
碎片概念,能夠讓一個組件返回多個元素,React.Fragment 等價于<></>
????return?<React.Fragment>?
???????<p>我是小杜杜</p>
???????<p>React</p>
???????<p>Vue</p>
????</React.Fragment>
可以看到React.Fragment
實際上是沒有節(jié)點的

另外,react
中支持數(shù)組的返回,像這樣:
????return?[
????????<p?key='1'>我是小杜杜</p>,
???????<p?key='2'>React</p>,
???????<p?key='3'>Vue</p>
????]
還有我們在進行數(shù)組遍歷的時候,React
都會在底層處理,在外部嵌套一個<React.Fragment />
Fragment 與?<></>
的不同
我們都知道<></>
是<Fragment></Fragment>
的簡寫,從原則上來說是一致的,那么你知道他們又什么不同嗎?
實際上,Fragment
?這個組件可以賦值?key
,也就是索引,<></>
不能賦值,應用在遍歷數(shù)組上,有感興趣的同學可以試一試~
lazy
lazy:允許你定義一個動態(tài)加載組件,這樣有助于縮減 bundle 的體積,并延遲加載在初次渲染時未用到的組件,也就是懶加載組件(高階組件)
lazy
接收一個函數(shù),這個函數(shù)需要動態(tài)調(diào)用import()
,如:
const?LazyChild?=?lazy(()?=>?import('./child'));
那么import('./child')
是一個怎樣的類型呢?
實際上lazy
必須接受一個函數(shù),并且需要返回一個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;
效果:

Suspense
Suspense:讓組件"等待"某個異步組件操作,直到該異步操作結(jié)束即可渲染。
與上面lazy
中的案例一樣,兩者需要配合使用,其中fallback
為等待時渲染的樣式
Suspense
和lazy
可以用于等待照片、腳本和一些異步的情況。
Profiler
Profiler:這個組件用于性能檢測,可以檢測一次react
組件渲染時的性能開銷
此組件有兩個參數(shù):
id
:標識Profiler
的唯一性onRender
:回調(diào)函數(shù),用于渲染完成,參數(shù)在下面講解
舉個栗子??:
import?React,?{?Component,?Profiler?}?from?'react';
export?default?Index;
讓我們來看看打印的是什么:

依此的含義:
id:
Profiler
樹的id
phase:
mount
掛載,update
渲染actualDuration:更新?
committed
?花費的渲染時間baseDuration:渲染整顆子樹需要的時間
startTime:更新開始渲染的時間
commitTime:更新?
committed
?的時間interactions:本次更新的?
interactions
?的集合
需要注意的是,這個組件應該在需要的時候去使用,雖然Profiler
是一個輕量級的,但也會帶來負擔
StrictMode
StrictMode:嚴格模式,是一種用于突出顯示應用程序中潛在問題的工具
與Fragment
一樣,StrictMode
也不會出現(xiàn)在UI
層面,只是會檢查和警告
可以看一下官方的示例:
import?React?from?'react';
function?ExampleApplication()?{
??return?(
????<div>
??????<Header?/>
??????<React.StrictMode>????????<div>
??????????<ComponentOne?/>
??????????<ComponentTwo?/>
????????</div>
??????</React.StrictMode>??????<Footer?/>
????</div>
??);
}
上述代碼中只會對ComponentOne
和ComponentTwo
進行檢查
主要有以下幫助:
識別具有不安全生命周期的組件
關于舊版字符串引用 API 使用的警告
關于不推薦使用 findDOMNode 的警告
檢測意外的副作用
檢測遺留上下文 API
確保可重用狀態(tài)
工具類
crateElement
JSX
會被編譯為React.createElement
的形式,然后被babel
編譯
結(jié)構(gòu):
React.createElement(type, [props], [...children])
共有三個參數(shù):
type
:原生組件的話是標簽的字符串,如“div”
,如果是React
自定義組件,則會傳入組件[props]
:對象,dom
類中的屬性
,組件
中的props
[...children]
:其他的參數(shù),會依此排序
舉個例子??: 舉個栗子??:
????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文本內(nèi)容
????????????????)
????????????)
????????}
????}
注意點
JSX
的結(jié)構(gòu)實際上和React.createElement
寫法一致,只是用JSX
更加簡單、方便經(jīng)過
React.createElement
的包裹,最終會形成?$$typeof = Symbol(react.element)
對象,對象保存了react.element
的信息。
cloneElement
cloneElement
:克隆并返回一個新的React
元素,
結(jié)構(gòu):?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
來看看:

其實是可以看到傳遞的name
的,也就是說可以通過React.cloneElement
方法去對組件進行一些賦能
createContext
createContext:相信大家對這個Api很熟悉,用于傳遞上下文。createContext
會創(chuàng)建一個Context
對象,用Provider
的value
來傳遞值,用Consumer
接受value
我們實現(xiàn)一個父傳孫的小栗子??:
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>幸運數(shù)字是:{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;
效果:

注意:如果Consumer
上一級一直沒有Provider
,則會應用defaultValue
作為value
。
只有當組件所處的樹中沒有匹配到?Provider
?時,其?defaultValue
?參數(shù)才會生效。
Children
Children: 提供處理this.props.children
不透明數(shù)據(jù)結(jié)構(gòu)的實用程序.
那么什么是不透明
的數(shù)據(jù)呢?
先來看看下面的栗子??:
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
看到:

我們可以看到每個節(jié)點都打印出來了,這種情況屬于透明的
,但我們要是便利看看:
<Child>
??????{
????????[1,2,3].map((item)?=>?<p?key={item}>大家好,我是小杜杜</p>)
??????}
??<p>Hello~</p>
</Child>

卻發(fā)現(xiàn)我們便利的三個元素被包了一層,像這種數(shù)據(jù)被稱為不透明
,我們想要處理這種數(shù)據(jù),就要以來React.Chilren
?來解決
Children.map
Children.map
:遍歷,并返回一個數(shù)組,針對上面的情況,我們可以通過這個方法將數(shù)據(jù)便會原先的
const?Child?=?({children})?=>?{
??const?res?=?React.Children.map(children,?(item)?=>?item)
??console.log(res)
??return?res
}
效果:

Children.forEach
Children.forEach
:與Children.map
類似,不同的是Children.forEach
并不會返回值,而是停留在遍歷階段
const?Child?=?({children})?=>?{
??React.Children.forEach(children,?(item)?=>?console.log(item))
??return?children
}
效果:

Children.count
Children.count:返回Child內(nèi)的總個數(shù),等于回調(diào)傳遞給map
或forEach
將被調(diào)用的次數(shù)。如:
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
}
效果: 只有一個時:

多個時:

Children.toArray
Children.toArray:以平面數(shù)組的形式返回children
不透明數(shù)據(jù)結(jié)構(gòu),每個子元素都分配有鍵。
如果你想在你的渲染方法中操作子元素的集合,特別是如果你想this.props.children
在傳遞它之前重新排序或切片,這很有用。
我們在原先的例子上在加一次來看看:
import?React?from?'react';
const?Child?=?({children})?=>?{
??console.log(`原來數(shù)據(jù):`,?children)
??const?res?=?React.Children.toArray(children)
??console.log(`扁平后的數(shù)據(jù):`,?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
,經(jīng)過Children.toArray
處理后,會給原本的key
添加前綴,以使得每個元素?key
?的范圍都限定在此函數(shù)入?yún)?shù)組的對象內(nèi)。
createRef
createRef:創(chuàng)建一個ref
對象,獲取節(jié)點信息,直接舉個例子:
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}?>?節(jié)點信息?</div>
??}
}
export?default?Index;
效果:
這個有點雞肋,因為我們可以直接從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}?>?節(jié)點信息?</div>
??}
}
export?default?Index;
createFactory
createFactory:返回一個生成給定類型的 React 元素的函數(shù)。
接受一個參數(shù)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;
isValidElement
isValidElement:用于驗證是否是React元素,是的話就返回true
,否則返回false
,感覺這個Api
也不是特別有用,因為我們肯定知道是否是
栗子??:
????console.log(React.isValidElement(<div>大家好,我是小杜杜</div>))?//?true
????console.log(React.isValidElement('大家好,我是小杜杜'))?//false
version
查看React的版本號:如:
console.log(React.version)
版本:
我們可以看下在React
中的文件位置,在react
中有一個單獨處理版本信息的位置:
packages/shared/ReactVersion.js
生命周期
React
?的 生命周期主要有兩個比較大的版本,分別是v16.0前
和v16.4
兩個版本的生命周期,我們分別說下舊的和新的生命周期,做下對比~
v16.0前
從圖中,總共分為四大階段:Intialization(初始化)
、Mounting(掛載)
、Update(更新)
、Unmounting(卸載)
Intialization(初始化)
在初始化階段,我們會用到?constructor()
?這個構(gòu)造函數(shù),如:
constructor(props)?{
??super(props);
}
super的作用
“ 用來調(diào)用基類的構(gòu)造方法( constructor() ), 也將父組件的props注入給子組件,供子組件讀取 (組件中props只讀不可變``,state可變
)初始化操作,定義this.state的初始內(nèi)容
只會執(zhí)行一次
Mounting(掛載)
componentWillMount:在組件掛載到DOM前調(diào)用
這里面的調(diào)用的
this.setState
不會引起組件的重新渲染,也可以把寫在這邊的內(nèi)容提到constructor()
,所以在項目中很少。只會調(diào)用一次
render: 渲染
只要
props
和state
發(fā)生改變
(無兩者的重傳遞和重賦值,論值是否有變化,都可以引起組件重新render),都會重新渲染render。return
:是必須的,是一個React元素(UI,描述組件),不負責組件實際渲染工作,由React自身根據(jù)此元素去渲染出DOM。render
是純函數(shù)(Pure function:返回的結(jié)果只依賴與參數(shù),執(zhí)行過程中沒有副作用),不能執(zhí)行this.setState。
componentDidMount:組件掛載到DOM后調(diào)用
調(diào)用一次
Update(更新)
componentWillReceiveProps(nextProps):調(diào)用于props引起的組件更新過程中
nextProps
:父組件傳給當前組件新的props可以用
nextProps
和this.props
來查明重傳props是否發(fā)生改變(原因:不能保證父組件重傳的props有變化)只要props發(fā)生變化就會,引起調(diào)用
**shouldComponentUpdate(nextProps, nextState)**:性能優(yōu)化組件
nextProps:當前組件的this.props
nextState:當前組件的this.state
通過比較
nextProps
和nextState
,來判斷當前組件是否有必要繼續(xù)執(zhí)行更新過程。返回false:表示停止更新,用于減少組件的不必要渲染,優(yōu)化性能
返回true:繼續(xù)執(zhí)行更新
像
componentWillReceiveProps()
中執(zhí)行了this.setState,更新了state,但在render
前(如shouldComponentUpdate,componentWillUpdate),this.state依然指向更新前的state,不然nextState及當前組件的this.state的對比就一直是true了
**componentWillUpdate(nextProps, nextState)**:組件更新前調(diào)用
在render方法前執(zhí)行
由于組件更新就會調(diào)用,所以一般很少使用
render:重新渲染
**componentDidUpdate(prevProps, prevState)**:組件更新后被調(diào)用
prevProps:組件更新前的props
prevState:組件更新前的state
可以操作組件更新的DOM
Unmounting(卸載)
componentWillUnmount:組件被卸載前調(diào)用
可以在這里執(zhí)行一些清理工作,比如清楚組件中使用的定時器,清楚componentDidMount中手動創(chuàng)建的DOM元素等,以避免引起內(nèi)存泄漏
React v16.4
與 v16.0的生命周期相比
新增了?getDerivedStateFromProps?和getSnapshotBeforeUpdate
取消了?componentWillMount、componentWillReceiveProps、componentWillUpdate
getDerivedStateFromProps
**getDerivedStateFromProps(prevProps, prevState)**:組件創(chuàng)建和更新時調(diào)用的方法
prevProps
:組件更新前的propsprevState
:組件更新前的state
注意:在React v16.3中,在創(chuàng)建和更新時,只能是由父組件引發(fā)才會調(diào)用這個函數(shù),在React v16.4改為無論是Mounting還是Updating,也無論是什么引起的Updating,全部都會調(diào)用。
有點類似于componentWillReceiveProps
,不同的是getDerivedStateFromProps
是一個靜態(tài)函數(shù),也就是這個函數(shù)不能通過this訪問到class的屬性,當然也不推薦使用
如果props傳入的內(nèi)容不需要影響到你的state,那么就需要返回一個null,這個返回值是必須的,所以盡量將其寫到函數(shù)的末尾。
在組件創(chuàng)建時和更新時的render方法之前調(diào)用,它應該返回一個對象來更新狀態(tài),或者返回null來不更新任何內(nèi)容。
getSnapshotBeforeUpdate
getSnapshotBeforeUpdate(prevProps,prevState)
:Updating時的函數(shù),在render之后調(diào)用
prevProps
:組件更新前的propsprevState
:組件更新前的state
可以讀取,但無法使用DOM的時候,在組件可以在可能更改之前從DOM捕獲一些信息(例如滾動位置)
返回的任何指都將作為參數(shù)傳遞給componentDidUpdate()
注意
在17.0的版本,官方徹底廢除?componentWillMount
、componentWillReceiveProps
、componentWillUpdate
如果還想使用的話可以使用:UNSAFE_componentWillMount()
、UNSAFE_componentWillReceiveProps()
、UNSAFE_componentWillUpdate()
對了,如果在面試的時候可能會問道有關生命周期的問題,建議各位小伙伴,將以上的生命周期都可說一說,然后做個對比,這樣的話,效果肯定不錯~
react-hooks
react-hooks
是React 16.8
的產(chǎn)物,給函數(shù)式組件賦上了生命周期
,再經(jīng)過三年多的時間,函數(shù)式組件
已經(jīng)逐漸取代了類組件
,可以說是React
開發(fā)者必備的技術
同時在React v18
中又出現(xiàn)了一些hooks
,今天我們將一起詳細的看看,確保你能迅速掌握~
React v16.8中的hooks
useState
useState:定義變量,可以理解為他是類組件中的this.state
使用:
const?[state,?setState]?=?useState(initialState);
state
:目的是提供給?UI
,作為渲染視圖的數(shù)據(jù)源setState
:改變 state 的函數(shù),可以理解為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>數(shù)字:{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
效果:
注意點
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>數(shù)字:{state.number}</div>
????<Button
??????color='primary'
??????onClick={()?=>?{
????????state.number++
????????setState(state)
??????}}
????>
??????點擊
????</Button>
??</div>
}
?
export?default?Index
useEffect
useEffect
:副作用,你可以理解為是類組件的生命周期,也是我們最常用的鉤子
那么什么是副作用呢??副作用(Side Effect:是指 function 做了和本身運算返回值無關的事,如請求數(shù)據(jù)、修改全局變量,打印、數(shù)據(jù)獲取、設置訂閱以及手動更改?React
?組件中的?DOM
?都屬于副作用操作都算是副作用
我們直接演示下它的用法栗子??:
不斷執(zhí)行
當useEffect
不設立第二個參數(shù)時,無論什么情況,都會執(zhí)行
模擬初始化和卸載
我們可以利用useEffect
弄掛載
和卸載
階段,通常我們用于監(jiān)聽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
效果:
根據(jù)依賴值改變
我們可以設置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改變才會執(zhí)行')
??},?[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
效果:
useContext
useContext:上下文,類似于Context
:其本意就是設置全局共享數(shù)據(jù),使所有組件可跨層級實現(xiàn)共享
useContext
的參數(shù)一般是由createContext
的創(chuàng)建,通過?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
結(jié)構(gòu):
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
效果:
useMemo
useMemo:與memo
的理念上差不多,都是判斷是否滿足當前的限定條件來決定是否執(zhí)行callback
函數(shù),而useMemo
的第二個參數(shù)是一個數(shù)組,通過這個數(shù)組來判定是否更新回掉函數(shù)
當一個父組件中調(diào)用了一個子組件的時候,父組件的 state 發(fā)生變化,會導致父組件更新,而子組件雖然沒有發(fā)生改變,但也會進行更新。
簡單的理解下,當一個頁面內(nèi)容非常復雜,模塊非常多的時候,函數(shù)式組件會從頭更新到尾,只要一處改變,所有的模塊都會進行刷新,這種情況顯然是沒有必要的。
我們理想的狀態(tài)是各個模塊只進行自己的更新,不要相互去影響,那么此時用useMemo
是最佳的解決方案。
這里要尤其注意一點,只要父組件的狀態(tài)更新,無論有沒有對自組件進行操作,子組件都會進行更新,useMemo
就是為了防止這點而出現(xiàn)的
為了更好的理解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>數(shù)字:{JSON.stringify(data)}</div>
??????<Button?color='primary'?onClick={()?=>?{setFlag(v?=>?!v)}}>切換</Button>
???????<div>切換狀態(tài):{JSON.stringify(flag)}</div>
????</div>
??);
}
export?default?Index;
我們簡單的寫了個?usePow
,我們通過?usePow
?給所傳入的數(shù)字平方, 用切換狀態(tài)的按鈕表示函數(shù)內(nèi)部的狀態(tài),我們來看看此時的效果:
我們發(fā)現(xiàn)了一個問題,為什么點擊切換按鈕也會觸發(fā)console.log(1)
呢?
這樣明顯增加了性能開銷,我們的理想狀態(tài)肯定不希望做無關的渲染,所以我們做自定義?hooks
的時候一定要注意,需要減少性能開銷,我們?yōu)榻M件加入?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;
發(fā)現(xiàn)此時就已經(jīng)解決了這個問題,不會在做相關的渲染了
useCallback
useCallback
與useMemo
極其類似,可以說是一模一樣,唯一不同的是useMemo
返回的是函數(shù)運行的結(jié)果,而useCallback
返回的是函數(shù)
注意:這個函數(shù)是父組件傳遞子組件的一個函數(shù),防止做無關的刷新,其次,這個組件必須配合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;

我們可以看到,當點擊切換按鈕的時候,沒有經(jīng)過?useCallback
封裝的函數(shù)會再次刷新,而經(jīng)過?useCallback
包裹的函數(shù)不會被再次刷新
有很多小伙伴有個誤區(qū),就是useCallback
不能單獨使用,必須要配合memo
嗎?
其實是這樣的,你可以單獨使用useCallback
,但只用useCallback
起不到優(yōu)化的作用,反而會增加性能消耗
想之前講的,React.memo
會通過淺比較里面的props
,如果沒有memo
,那么使用的useCallback
也就毫無意義
因為useCallback
本身是需要開銷的,所以反而會增加性能的消耗
useRef
useRef: 可以獲取當前元素的所有屬性,并且返回一個可變的ref對象,并且這個對象只有current屬性,可設置initialValue
結(jié)構(gòu):
const refContainer = useRef(initialValue);
有許多小伙伴只知道useRef
可以獲取對應元素的屬性,但useRef
還具備一個功能,就是緩存數(shù)據(jù)
,接下來一起看看:
通過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;?//可視區(qū)域高度
??????let?scrollTop??=?scrollRef?.current.scrollTop;??//滾動條滾動高度
??????let?scrollHeight?=?scrollRef?.current.scrollHeight;?//滾動內(nèi)容高度
??????setClientHeight(clientHeight)
??????setScrollTop(scrollTop)
??????setScrollHeight(scrollHeight)
????}
??}
??return?(
????<div?>
??????<div?>
????????<p>可視區(qū)域高度:{clientHeight}</p>
????????<p>滾動條滾動高度:{scrollTop}</p>
????????<p>滾動內(nèi)容高度:{scrollHeight}</p>
??????</div>
??????<div?style={{height:?200,?overflowY:?'auto'}}?ref={scrollRef}?onScroll={onScroll}?>
????????<div?style={{height:?2000}}></div>
??????</div>
????</div>
??);
};
export?default?Index;
效果:

從上述可知,我們可以通過useRef
來獲取對應元素的相關屬性,以此來做一些操作
緩存數(shù)據(jù)
react-redux
的源碼中,在hooks推出后,react-redux
用大量的useMemo重做了Provide等核心模塊,其中就是運用useRef來緩存數(shù)據(jù),并且所運用的?useRef()?沒有一個是綁定在dom元素上的,都是做數(shù)據(jù)緩存用的
可以簡單的來看一下:
????//?緩存數(shù)據(jù)
????/*?react-redux?用userRef?來緩存?merge之后的?props?*/?
????const?lastChildProps?=?useRef()?
????
????//?lastWrapperProps?用?useRef?來存放組件真正的?props信息?
????const?lastWrapperProps?=?useRef(wrapperProps)?
????
????//是否儲存props是否處于正在更新狀態(tài)?
????const?renderIsScheduled?=?useRef(false)
????//更新數(shù)據(jù)
????function?captureWrapperProps(?
????????lastWrapperProps,?
????????lastChildProps,?
????????renderIsScheduled,?
????????wrapperProps,?
????????actualChildProps,?
????????childPropsFromStoreUpdate,?
????????notifyNestedSubs?
????)?{?
????????lastWrapperProps.current?=?wrapperProps?
????????lastChildProps.current?=?actualChildProps?
????????renderIsScheduled.current?=?false?
???}
我們看到?react-redux
?用重新賦值的方法,改變了緩存的數(shù)據(jù)源,減少了不必要的更新,如過采取useState
勢必會重新渲染。
有的時候我們需要使用useMemo、useCallbackApi,我們控制變量的值用useState?有可能會導致拿到的是舊值,并且如果他們更新會帶來整個組件重新執(zhí)行,這種情況下,我們使用useRef將會是一個非常不錯的選擇
useImperativeHandle
useImperativeHandle:可以讓你在使用?ref
?時自定義暴露給父組件的實例值
這個Api我覺得是十分有用的,建議掌握哦,來看看使用的場景:
在一個頁面很復雜的時候,我們會將這個頁面進行模塊化,這樣會分成很多個模塊,有的時候我們需要在最外層的組件上
控制其他組件的方法,希望最外層的點擊事件,同時執(zhí)行子組件的事件
,這時就需要 useImperativeHandle 的幫助
結(jié)構(gòu):
useImperativeHandle(ref,?createHandle,?[deps])
ref
:useRef
所創(chuàng)建的refcreateHandle
:處理的函數(shù),返回值作為暴露給父組件的 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>點擊次數(shù):{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()}
????>
??????父節(jié)點上的加1
????</Button>
????<Child?cRef={ref}?/>
??</div>
}
?
export?default?Index
效果:

useLayoutEffect
useLayoutEffect: 與useEffect
基本一致,不同的地方時,useLayoutEffect
是同步
要注意的是useLayoutEffect
在 DOM 更新之后,瀏覽器繪制之前,這樣做的好處是可以更加方便的修改?DOM
,獲取?DOM
?信息,這樣瀏覽器只會繪制一次,所以useLayoutEffect
在useEffect
之前執(zhí)行
如果是useEffect
的話 ,useEffect
?執(zhí)行在瀏覽器繪制視圖之后,如果在此時改變DOM
,有可能會導致瀏覽器再次回流
和重繪
。
除此之外useLayoutEffect
的?callback
?中代碼執(zhí)行會阻塞瀏覽器繪制
舉個例子??:
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
效果:

useDebugValue
useDebugValue:可用于在?React
?開發(fā)者工具中顯示自定義 hook 的標簽
官方并不推薦你向每個自定義 Hook 添加 debug 值。當它作為共享庫的一部分時才最有價值。
function?useFriendStatus(friendID)?{
??const?[isOnline,?setIsOnline]?=?useState(null);
??//?...
??//?在開發(fā)者工具中的這個?Hook?旁邊顯示標簽??
??//?e.g.?"FriendStatus:?Online"??useDebugValue(isOnline???'Online'?:?'Offline');
??return?isOnline;
}
React v18中的hooks
useSyncExternalStore
useSyncExternalStore:是一個推薦用于讀取
和訂閱外部數(shù)據(jù)源
的?hook
,其方式與選擇性的?hydration
?和時間切片等并發(fā)渲染功能兼容
結(jié)構(gòu):
const state = useSyncExternalStore(subscribe, getSnapshot[, getServerSnapshot])
subscribe
: 訂閱函數(shù),用于注冊一個回調(diào)函數(shù),當存儲值發(fā)生更改時被調(diào)用。此外,?useSyncExternalStore
?會通過帶有記憶性的?getSnapshot
?來判別數(shù)據(jù)是否發(fā)生變化,如果發(fā)生變化,那么會強制更新
數(shù)據(jù)。getSnapshot
: 返回當前存儲值的函數(shù)。必須返回緩存的值。如果?getSnapshot
?連續(xù)多次調(diào)用,則必須返回相同的確切值,除非中間有存儲值更新。getServerSnapshot
:返回服務端(hydration
模式下)渲染期間使用的存儲值的函數(shù)
舉個栗子??:
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,并創(chuàng)建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
效果:

從上述代碼可以看出,當點擊按鈕后,會觸發(fā)?store.subscribe
(訂閱函數(shù)),執(zhí)行getSnapshot
后得到新的count
,如果count
發(fā)生變化,則會觸發(fā)更新
useTransition
useTransition:返回一個狀態(tài)值表示過渡任務的等待狀態(tài),以及一個啟動該過渡任務的函數(shù)。
那么什么是過渡任務?
在一些場景中,如:輸入框、tab切換、按鈕等,這些任務需要視圖上立刻
做出響應,這些任務可以稱之為立即更新的任務
但有的時候,更新任務并不是那么緊急,或者來說要去請求數(shù)據(jù)等,導致新的狀態(tài)不能立更新,需要用一個loading...
的等待狀態(tài),這類任務就是過度任務
結(jié)構(gòu):
const [isPending, startTransition] = useTransition();
isPending
:過渡狀態(tài)的標志,為true
時是等待狀態(tài)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
效果:

實際上,我們在Input
輸入內(nèi)容是,會進行增加,假設我們在startTransition
中請求一個接口,在接口請求的時候,isPending
會為true
,就會有一個loading
的狀態(tài),請求完之后,isPending
變?yōu)?code>false渲染列表
useDeferredValue
useDeferredValue:接受一個值,并返回該值的新副本,該副本將推遲到更緊急地更新之后。
如果當前渲染是一個緊急更新的結(jié)果,比如用戶輸入,React
?將返回之前的值,然后在緊急渲染完成后渲染新的值。
也就是說useDeferredValue
可以讓狀態(tài)滯后派生
結(jié)構(gòu):
const?deferredValue?=?useDeferredValue(value);
value
:可變的值,如useState
創(chuàng)建的值deferredValue
: 延時狀態(tài)
這個感覺和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
效果:

和useTransition做對比
根據(jù)上面兩個示例我們看看useTransition
和useDeferredValue
做個對比看看
相同點:
useDeferredValue
和useTransition
一樣,都是過渡更新任務不同點:
useTransition
給的是一個狀態(tài),而useDeferredValue
給的是一個值
useInsertionEffect
useInsertionEffect:與?useEffect
一樣,但它在所有 DOM 突變 之前同步觸發(fā)。
我們來看看useInsertionEffect
對比于useEffect
和useLayoutEffect
在執(zhí)行順序上有什么區(qū)別,栗子??:
??useEffect(()=>{
????console.log('useEffect')
??},[])
??useLayoutEffect(()=>{
????console.log('useLayoutEffect')
??},[])
??useInsertionEffect(()=>{
????console.log('useInsertionEffect')
??},[])

可以看到在執(zhí)行順序上?useInsertionEffect
?>?useLayoutEffect
?>?useEffect
特別注意一點:useInsertionEffect
?應僅限于?css-in-js?庫作者使用。優(yōu)先考慮使用?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
效果:

useId
useId?: 是一個用于生成橫跨服務端和客戶端的穩(wěn)定的唯一 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
基礎上的一個擴展,可以根據(jù)業(yè)務、需求去制定相應的hooks
,將常用的邏輯進行封裝,從而具備復用性
react-dom
react-dom:這個包提供了用戶DOM的特定方法。這個包在React v18
中還是做了很大的改動,接下來我們逐個看看
createPortal
createPortal:在Portal
中提供了一種將子節(jié)點渲染到已 DOM 節(jié)點中的方式,該節(jié)點存在于 DOM 組件的層次結(jié)構(gòu)之外。
也就是說createPortal
可以把當前組件或element
元素的子節(jié)點,渲染到組件之外的其他地方。
來看看createPortal(child, container)
的入?yún)ⅲ?/p>
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}>同級的節(jié)點</div>
????<div>
??????這層的節(jié)點
??????{newDom}
????</div>
??</div>
}
const?Index?=?()?=>?{
??return?<div?style={{padding:?20}}>
????<Child>
??????<div>大家好,我是小杜杜</div>
????</Child>
??</div>
}
export?default?Index;
要注意下Child
:

我們傳入的children
被createPortal
包裹后,children
的節(jié)點位置會如何?

發(fā)現(xiàn),我們處理的數(shù)newDom
的數(shù)據(jù)到了同級的節(jié)點處,那么這個Api
該如何應用呢?
我們可以處理一些頂層元素,如:Modal
彈框組件,Modal
組件在內(nèi)部中書寫,掛載到外層的容器(如body),此時這個Api
就非常有用
flushSync
flushSync:可以將回調(diào)函數(shù)中的更新任務,放到一個較高級的優(yōu)先級中,適用于強制刷新,同時確保了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>數(shù)字:?{number}</div>?
??????<Button
????????color='primary'
????????onClick={()?=>?{
??????????this.setState({?number:?1??})
??????????this.setState({?number:?2??})
??????????this.setState({?number:?3??})
????????}}
??????>
????????點擊?
??????</Button>????
????</div>
??}
}
export?default?Index;
我們看看點擊按鈕會打印出什么?

這個不難理解,因為this.setState
會進行批量更新,所以打印出的是3 接下來,我們用flushSync
處理下number: 2
?來看看是什么效果:
????onClick={()?=>?{
??????this.setState({?number:?1??})
??????ReactDOM.flushSync(()=>{
????????this.setState({?number:?2??})
??????})
??????this.setState({?number:?3??})
????}}

可以發(fā)現(xiàn)flushSync
會優(yōu)先執(zhí)行,并且強制刷新,所以會改變number
值為2,然后1
和3
在被批量刷新,更新為3
render
render:這個是我們在react-dom
中最常用的Api,用于渲染一個react
元素
我們通常使用在根部,如:
ReactDOM.render(?
????<?App?/?>,?
????document.getElementById('app')
)
createRoot
在React v18
中,render
函數(shù)已經(jīng)被createRoot
所替代
createRoot
會控制你傳入的容器節(jié)點的內(nèi)容。當調(diào)用 render 時,里面的任何現(xiàn)有 DOM 元素都會被替換。后面的調(diào)用使用 React 的 DOM diffing 算法進行有效更新。
并且createRoot
不修改容器節(jié)點(只修改容器的子節(jié)點)。可以在不覆蓋現(xiàn)有子節(jié)點的情況下將組件插入現(xiàn)有 DOM 節(jié)點。
如:
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 的內(nèi)容進行 hydrate 操作。
hydrate(element,?container[,?callback])
hydrateRoot()
hydrate
在React v18
也被替代為hydrateRoot()
hydrateRoot(container,?element[,?options])
unmountComponentAtNode
unmountComponentAtNode:從 DOM 中卸載組件,會將其事件處理器(event handlers)和 state 一并清除。如果指定容器上沒有對應已掛載的組件,這個函數(shù)什么也不會做。如果組件被移除將會返回?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)?//?創(chuàng)建一個容器
??}
??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;
效果:

root.unmount()
unmountComponentAtNode
?同樣在React 18
中被替代了,替換成了createRoot
中的unmount()
方法
const?root?=?createRoot(container);
root.render(element);
root.unmount()
findDOMNode
findDOMNode:用于訪問組件DOM
元素節(jié)點(應急方案),官方推薦使用ref
需要注意的是:
findDOMNode
只能用到掛載
的組件上findDOMNode
只能用于類組件,不能用于函數(shù)式組件如果組件渲染為
null
或者為false
,那么findDOMNode
返回的值也是null
如果是多個子節(jié)點
Fragment
的情況,findDOMNode
會返回第一個非空子節(jié)點對應的 DOM 節(jié)點。在嚴格模式下這個方法已經(jīng)被
棄用
?舉個例子??:
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>數(shù)字:?{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
當我們點擊按鈕后,三個打印會打印出什么?

此時的場景只會執(zhí)行一次,并且渲染一次,渲染時為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>??
此時就會這樣:

因為繞過了事件機制,此時就會渲染3次,并且渲染的結(jié)果為3
那么我們現(xiàn)在想在setTimeout
實現(xiàn)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>?
效果:
