最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

一文吃透React v18全部Api

2023-01-13 15:05 作者:要寵你上天  | 我要投稿

作者:小杜杜

https://juejin.cn/post/7124486630483689485

俗話說的好,工欲善其事必先利其器,什么意思呢?就是說你想玩轉(zhuǎn)React就必須知道React有什么,無論是否運用到,首先都要知道,提升思維廣度~

其實React官方提供了很多Api,只是這些Api我們并不常用,所以往往會忽略它們,但在一些特定的場景下,這些Api也會起到關鍵性的作用,所以今天就逐個盤點一下,說說它們的使用方法和使用場景。

當然這些Api并不需要全部掌握,只需要知道有這個知識點就好了~

本文將會全面總結(jié)所有的ReactApi,包含組件類、工具類生命周期、react-hooksreact-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ū)指出~

附上一張今天的學習圖譜~

全面解讀ReactApi

組件類

Component

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

文件位置packages/react/src/ReactBaseClasses.js

可以看出Component進行一些初始化的工作,updater保存著更新組件的方法

PureComponent

PureComponent:會對propsstate進行淺比較,跳過不必要的更新,提高組件性能。

可以說PureComponentComponent基本完全一致,但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通過自帶的propsstate的淺比較實現(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),可以看出flagChild之間沒有任何關系,那么在切換狀態(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.memoPureComponent的區(qū)別:

  • 服務對象不同:PureComponent?服務與類組件,React.memo既可以服務于類組件,也可以服務與函數(shù)式組件,useMemo服務于函數(shù)式組件(后續(xù)講到)

  • 針對的對象不同:PureComponent針對的是propsstate,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為等待時渲染的樣式

Suspenselazy可以用于等待照片、腳本和一些異步的情況。

Profiler

Profiler:這個組件用于性能檢測,可以檢測一次react組件渲染時的性能開銷

此組件有兩個參數(shù):

  • id:標識Profiler的唯一性

  • onRender:回調(diào)函數(shù),用于渲染完成,參數(shù)在下面講解

舉個栗子??:

import?React,?{?Component,?Profiler?}?from?'react';



export?default?Index;

讓我們來看看打印的是什么:

依此的含義:

  • idProfiler樹的id

  • phasemount掛載,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>
??);
}

上述代碼中只會對ComponentOneComponentTwo進行檢查

主要有以下幫助:

  • 識別具有不安全生命周期的組件

  • 關于舊版字符串引用 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對象,用Providervalue來傳遞值,用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)傳遞給mapforEach將被調(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,這個typecreateElementtype一樣,原生組件的話是標簽的字符串,如“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: 渲染

  • 只要propsstate發(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

  • 可以用nextPropsthis.props來查明重傳props是否發(fā)生改變(原因:不能保證父組件重傳的props有變化)

  • 只要props發(fā)生變化就會,引起調(diào)用

**shouldComponentUpdate(nextProps, nextState)**:性能優(yōu)化組件

  • nextProps:當前組件的this.props

  • nextState:當前組件的this.state

  • 通過比較nextPropsnextState,來判斷當前組件是否有必要繼續(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:組件更新前的props

  • prevState:組件更新前的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:組件更新前的props

  • prevState:組件更新前的state

可以讀取,但無法使用DOM的時候,在組件可以在可能更改之前從DOM捕獲一些信息(例如滾動位置)

返回的任何指都將作為參數(shù)傳遞給componentDidUpdate()

注意

在17.0的版本,官方徹底廢除?componentWillMount、componentWillReceiveProps、componentWillUpdate

如果還想使用的話可以使用:UNSAFE_componentWillMount()、UNSAFE_componentWillReceiveProps()、UNSAFE_componentWillUpdate()

對了,如果在面試的時候可能會問道有關生命周期的問題,建議各位小伙伴,將以上的生命周期都可說一說,然后做個對比,這樣的話,效果肯定不錯~

react-hooks

react-hooksReact 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)聽addEventListenerremoveEventListener的使用

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:可以理解為和useStatesetState一樣的效果

  • reducer:可以理解為reduxreducer

  • 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

useCallbackuseMemo極其類似,可以說是一模一樣,唯一不同的是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])

  • refuseRef所創(chuàng)建的ref

  • createHandle:處理的函數(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?信息,這樣瀏覽器只會繪制一次,所以useLayoutEffectuseEffect之前執(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ù)上面兩個示例我們看看useTransitionuseDeferredValue做個對比看看

  • 相同點:useDeferredValueuseTransition一樣,都是過渡更新任務

  • 不同點:useTransition給的是一個狀態(tài),而useDeferredValue給的是一個值

useInsertionEffect

useInsertionEffect:與?useEffect一樣,但它在所有 DOM 突變 之前同步觸發(fā)。

我們來看看useInsertionEffect對比于useEffectuseLayoutEffect在執(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:

我們傳入的childrencreatePortal包裹后,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,然后13在被批量刷新,更新為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()

hydrateReact 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>?

效果:


一文吃透React v18全部Api的評論 (共 條)

分享到微博請遵守國家法律
平安县| 登封市| 翼城县| 绥宁县| 安丘市| 合川市| 宜良县| 出国| 溆浦县| 兴国县| 永清县| 揭阳市| 固原市| 西丰县| 石门县| 中西区| 东莞市| 门源| 城口县| 华宁县| 南投市| 嵊泗县| 山阳县| 布拖县| 金塔县| 孝感市| 邢台市| 徐州市| 麟游县| 治多县| 垫江县| 增城市| 富蕴县| 黄浦区| 阿拉善盟| 无锡市| 三明市| 汪清县| 泰顺县| 阳东县| 嵊州市|