一文喫透 React v18 全部 Api
作者:小杜杜
https://juejin.cn/post/7124486630483689485
俗話說的好,工欲善其事必先利其器,什麼意思呢?就是說你想玩轉React就必須知道React有什麼,無論是否運用到,首先都要知道,提升思維廣度~
其實React官方提供了很多Api,只是這些Api我們並不常用,所以往往會忽略它們,但在一些特定的場景下,這些Api也會起到關鍵性的作用,所以今天就逐個盤點一下,說說它們的使用方法和使用場景。
當然這些Api並不需要全部掌握,只需要知道有這個知識點就好了~
本文將會全面總結所有的ReactApi,包含組件類、工具類、生命週期、react-hooks、react-dom五大模塊,並配帶示例,幫助大家更好的掌握,大家可以邊嗑瓜子邊閱讀,如有不全、不對的地方歡迎在評論區指出~
由於本文過長,建議點贊 +收藏, 在正式開始前,我抽取了一些問題,一起來看看:
-
1.
React v18中對react-dom做了那些改動,增加了那些新的hooks? -
2.
useRef除了獲取元素的節點信息,還能做什麼? -
- 爲什麼會有
Children.map? 它與不同的遍歷有和不同
- 爲什麼會有
-
- 類組件的生命週期在不同的版本是怎樣變化的?
-
- 子元素如何渲染到父元素上面的?
-
...
其實問題很多,看完這篇文章後,相信一定能幫你解答的非常清楚,還請各位小夥伴多多支持一下
前言
寫這篇文章的主要目的有:
提升知識廣度,要想深入
React就必須全面瞭解React,首先要學會用,要知道,如果連知道都不知道,談何深入?
React v18對react-dom的改動還是比較大的,並且新增了五個hooks, 逐一盤點一下,看看做了那些改動這個專欄實際上是循序漸進的,相互之間都有一定的關聯,同時要想看懂,也需要有一定的
React基礎,對剛開始學習React的小夥伴可能並不是太友好,所以特地總結一番,用最簡單的示例,幫你掌握這些 Api對之後的源碼有幫助,所以本篇文章將會全面解讀
React Api的使用方法,和場景,如有不足,還希望在評論區指出~
附上一張今天的學習圖譜~
組件類
Component
在 React 中提供兩種形式,一種是類組件,另一種是函數式組件,而在類組件組件中需要使用Component繼承,這個組件沒有什麼好講的,我們可以看看源碼:
文件位置packages/react/src/ReactBaseClasses.js
可以看出Component進行一些初始化的工作,updater保存着更新組件的方法
PureComponent
PureComponent:會對props和state進行淺比較,跳過不必要的更新,提高組件性能。
可以說PureComponent和Component基本完全一致,但PureComponent會淺比較,也就是較少render渲染的次數,所以PureComponent一般用於性能優化
那麼什麼是淺比較,舉個🌰:
import { PureComponent } from 'react';
import { Button } from 'antd-mobile';
class Index extends PureComponent{
constructor(props){
super(props)
this.state={
data:{
number:0
}
}
}
render(){
const { data } = this.state
return <div style={{padding: 20}}>
<div> 數字: { data.number }</div>
<Button
color="primary"
onClick={() => {
const { data } = this.state
data.number++
this.setState({ data })
}}
>數字加1</Button>
</div>
}
}
export default Index;
效果:
可以發現,當我們點擊按鈕的時候,數字並沒有刷新,這是因爲PureComponent會比較兩次的data對象,它會認爲這種寫法並沒有改變原先的data, 所以不會改變
我們只需要:
this.setState({ data: {...data} })
這樣就可以解決這個問題了
與 shouldComponentUpdate 的關係如何
在生命週期中有一個shouldComponentUpdate()函數,那麼它能改變PureComponent嗎?
其實是可以的,shouldComponentUpdate()如果被定義,就會對新舊 props、state 進行 shallowEqual 比較,新舊一旦不一致,便會觸發 update。
也可以這麼理解:PureComponent通過自帶的props和state的淺比較實現了shouldComponentUpdate(),這點Component並不具備
PureComponent可能會因深層的數據不一致而產生錯誤的否定判斷,從而導致shouldComponentUpdate結果返回 false,界面得不到更新,要謹慎使用
memo
memo:結合了pureComponent純組件和 componentShouldUpdate功能,會對傳入的 props 進行一次對比,然後根據第二個函數返回值來進一步判斷哪些 props 需要更新
要注意memo是一個高階組件,函數式組件和類組件都可以使用。
memo接收兩個參數:
-
第一個參數:組件本身,也就是要優化的組件
-
第二個參數:(pre, next) => boolean,
pre:之前的數據,next:現在的數據,返回一個布爾值,若爲true則不更新,爲false更新
性能優化
接下來,我們先看這樣一個🌰:
import React, { Component } from 'react';
import { Button } from 'antd-mobile';
const Child = () => {
return <div>
{console.log('子組件渲染')}
大家好,我是小杜杜~
</div>
}
class Index extends Component{
constructor(props){
super(props)
this.state={
flag: true
}
}
render(){
const { flag } = this.state
return <div style={{padding: 20}}>
<Child/>
<Button
color="primary"
onClick={() => this.setState({ flag: !flag })}
>狀態切換{JSON.stringify(flag)}</Button>
</div>
}
}
export default Index;
在上述代碼中,我們設置一個子組件,也就是Child和一個按鈕,按鈕的效果是切換 flag 的狀態,可以看出flag和Child之間沒有任何關係,那麼在切換狀態的時候,Child會刷新嗎?
直接看看效果:
可以看出,在我們切換狀態的時候,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;
效果:
當數字小於 7,纔會出發Child的更新,通過返回的布爾值來控制
memo 的注意事項
React.memo與PureComponent的區別:
-
服務對象不同:
PureComponent服務與類組件,React.memo既可以服務於類組件,也可以服務與函數式組件,useMemo服務於函數式組件(後續講到) -
針對的對象不同:
PureComponent針對的是props和state,React.memo只能針對props來決定是否渲染
這裏還有個小的注意點:memo的第二個參數的返回值與shouldComponentUpdate的返回值是相反的,經常會弄混,還要多多注意
-
memo: 返回true組件不渲染 , 返回false組件重新渲染。 -
shouldComponentUpdate: 返回true組件渲染 , 返回false組件不渲染。
forwardRef
forwardRef:引用傳遞,是一種通過組件向子組件自動傳遞引用ref的技術。對於應用者的大多數組件來說沒什麼作用,但對於一些重複使用的組件,可能有用。
聽完介紹是不是感覺雲裏霧裏的,官方對forwardRef的介紹也很少,我們來看看轉發的問題
在React中,React不允許ref通過props傳遞,因爲ref是組件中固定存在的,在組件調和的過程中,會被特殊處理,而forwardRef就是爲了解決這件事而誕生的,讓ref可以通過props傳遞
舉個栗子🌰:父組件想要獲取孫組件上的信息,我們直接用ref傳遞會怎樣:
接下來看看利用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;
效果:
如此以來就解決了不能在react組件中傳遞ref的問題,至於複用的組件可能會用到,目前也沒思路用forwardRef幹嘛,就當熟悉吧~
Fragment
在React中,組件是不允許返回多個節點的,如:
return <p>我是小杜杜</p>
<p>React</p>
<p>Vue</p>
我們想要解決這種情況需要給爲此套一個容器元素,如<div></div>
return <div>
<p>我是小杜杜</p>
<p>React</p>
<p>Vue</p>
</div>
但這樣做,無疑會多增加一個節點,所以在16.0後,官方推出了Fragment碎片概念,能夠讓一個組件返回多個元素,React.Fragment 等價於<></>
return <React.Fragment>
<p>我是小杜杜</p>
<p>React</p>
<p>Vue</p>
</React.Fragment>
可以看到React.Fragment實際上是沒有節點的
另外,react中支持數組的返回,像這樣:
return [
<p key='1'>我是小杜杜</p>,
<p key='2'>React</p>,
<p key='3'>Vue</p>
]
還有我們在進行數組遍歷的時候,React都會在底層處理,在外部嵌套一個<React.Fragment />
Fragment 與 <></>的不同
我們都知道<></>是<Fragment></Fragment>的簡寫,從原則上來說是一致的,那麼你知道他們又什麼不同嗎?
實際上,Fragment 這個組件可以賦值 key,也就是索引,<></>不能賦值,應用在遍歷數組上,有感興趣的同學可以試一試~
lazy
lazy:允許你定義一個動態加載組件,這樣有助於縮減 bundle 的體積,並延遲加載在初次渲染時未用到的組件,也就是懶加載組件(高階組件)
lazy接收一個函數,這個函數需要動態調用import(), 如:
const LazyChild = lazy(() => import('./child'));
那麼import('./child')是一個怎樣的類型呢?
實際上lazy必須接受一個函數,並且需要返回一個Promise, 並且需要resolve一個default一個React組件,除此之外,lazy必須要配合Suspense一起使用
舉個例子🌰:我加入了setTimeout方便看到更好的效果:
import React, { Component, Suspense, lazy } from 'react';
import Child from './child'
import { Button, DotLoading } from 'antd-mobile';
const LazyChild = lazy(() => new Promise((res) => {
setTimeout(() => {
res({
default: () => <Child />
})
}, 1000)
}))
class Index extends Component{
constructor(props){
super(props)
this.state={
show: false
}
}
render(){
const { show } = this.state
return <div style={{padding: 20}}>
<Button color='primary' onClick={() => this.setState({ show: true })} >
渲染
</Button>
{
show && <Suspense fallback={<div><DotLoading color='primary' />加載中</div>}>
<LazyChild />
</Suspense>
}
</div>
}
}
export default Index;
Child 文件:
import React, { useEffect } from 'react';
import img from './img.jpeg'
const Index = () => {
useEffect(() => {
console.log('照片渲染')
}, [])
return <div>
<img src={img} width={200} height={160} />
</div>
}
export default Index;
效果:
Suspense
Suspense:讓組件 "等待" 某個異步組件操作,直到該異步操作結束即可渲染。
與上面lazy中的案例一樣,兩者需要配合使用,其中fallback爲等待時渲染的樣式
Suspense和lazy可以用於等待照片、腳本和一些異步的情況。
Profiler
Profiler:這個組件用於性能檢測,可以檢測一次react組件渲染時的性能開銷
此組件有兩個參數:
-
id:標識Profiler的唯一性 -
onRender:回調函數,用於渲染完成,參數在下面講解
舉個栗子🌰:
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也不會出現在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
-
確保可重用狀態
工具類
crateElement
JSX會被編譯爲React.createElement的形式,然後被babel編譯
結構:
React.createElement(type, [props], [...children])共有三個參數:
-
type:原生組件的話是標籤的字符串,如“div”,如果是React自定義組件,則會傳入組件 -
[props]:對象,dom類中的屬性,組件中的props -
[...children]:其他的參數,會依此排序
舉個例子🌰: 舉個栗子🌰:
class Info extends React.Component {
render(){
return(
<div>
Hi!我是小杜杜
<p>歡迎</p>
<Children>我是子組件</Children>
</div>
)
}
}
上述代碼會被翻譯爲:
class Info extends React.Component {
render(){
return React.createElement(
'div',
null,
"Hi!我是小杜杜",
React.createElement('p', null, '歡迎'), // 原生標籤
React.createElement(
Children, //自定義組件
null, // 屬性
'我是子組件' //child文本內容
)
)
}
}
注意點
-
JSX的結構實際上和React.createElement寫法一致,只是用JSX更加簡單、方便 -
經過
React.createElement的包裹,最終會形成$$typeof = Symbol(react.element)對象,對象保存了react.element的信息。
cloneElement
cloneElement:克隆並返回一個新的React元素,
結構: React.createElement(type, [props], [...children])
React.cloneElement()幾乎等同於:
<element.type {...element.props} {...props}>
{children}
</element.type>
舉個例子🌰:
import React from 'react';
const Child = () => {
const children = React.cloneElement(<div>大家好,我是小杜杜</div>, {name: '小杜杜'})
console.log(children)
return <div>{children}</div>
}
const Index = () => {
return <div style={{padding: 20}}>
<Child />
</div>
}
export default Index;
打印下children來看看:
其實是可以看到傳遞的name的,也就是說可以通過React.cloneElement方法去對組件進行一些賦能
createContext
createContext:相信大家對這個 Api 很熟悉,用於傳遞上下文。createContext會創建一個Context對象,用Provider的value來傳遞值,用Consumer接受value
我們實現一個父傳孫的小栗子🌰:
import React, { useState } from 'react';
const Content = React.createContext()
const Child = () => {
return <Content.Consumer>
{(value) => <Son {...value} />}
</Content.Consumer>
}
const Son = (props) => {
return <>
<div>大家好,我是{props.name}</div>
<div>幸運數字是:{props.number}</div>
</>
}
const Index = () => {
const [data, _] = useState({
name: '小杜杜',
number: 7
})
return <div style={{padding: 20}}>
<Content.Provider value={data}>
<Child />
</Content.Provider>
</div>
}
export default Index;
效果:
注意:如果Consumer上一級一直沒有Provider, 則會應用defaultValue作爲value。
只有當組件所處的樹中沒有匹配到 Provider 時,其 defaultValue 參數纔會生效。
Children
Children: 提供處理this.props.children不透明數據結構的實用程序.
那麼什麼是不透明的數據呢?
先來看看下面的栗子🌰:
import React, { useEffect } from 'react';
const Child = ({children}) => {
console.log(children)
return children
}
const Index = () => {
return <div style={{padding: 20}}>
<Child>
<p>大家好,我是小杜杜</p>
<p>大家好,我是小杜杜</p>
<p>大家好,我是小杜杜</p>
<p>Hello~</p>
</Child>
</div>
}
export default Index;
打印下children看到:
我們可以看到每個節點都打印出來了,這種情況屬於透明的,但我們要是便利看看:
<Child>
{
[1,2,3].map((item) => <p key={item}>大家好,我是小杜杜</p>)
}
<p>Hello~</p>
</Child>
卻發現我們便利的三個元素被包了一層,像這種數據被稱爲不透明,我們想要處理這種數據,就要以來React.Chilren 來解決
Children.map
Children.map:遍歷,並返回一個數組,針對上面的情況,我們可以通過這個方法將數據便會原先的
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 內的總個數,等於回調傳遞給map或forEach將被調用的次數。如:
const Child = ({children}) => {
const res = React.Children.count(children)
console.log(res) // 4
return children
}
Children.only
Children.only:驗證Child是否只有一個元素,如果是,則正常返回,如果不是,則會報錯。🤷♂不知道這個有啥用~
const Child = ({children}) => {
console.log(React.Children.only(children))
return children
}
效果: 只有一個時:
多個時:
Children.toArray
Children.toArray:以平面數組的形式返回children不透明數據結構,每個子元素都分配有鍵。
如果你想在你的渲染方法中操作子元素的集合,特別是如果你想this.props.children在傳遞它之前重新排序或切片,這很有用。
我們在原先的例子上在加一次來看看:
import React from 'react';
const Child = ({children}) => {
console.log(`原來數據:`, children)
const res = React.Children.toArray(children)
console.log(`扁平後的數據:`, res)
return res
}
const Index = () => {
return <div style={{padding: 20}}>
<Child>
{
[1,2,3].map((item) => [5, 6].map((ele) => <p key={`${item}-${ele}`}>大家好,我是小杜杜</p>))
}
<p>Hello~</p>
</Child>
</div>
}
export default Index;
效果:
這裏需要注意的是key, 經過Children.toArray處理後,會給原本的key添加前綴,以使得每個元素 key 的範圍都限定在此函數入參數組的對象內。
createRef
createRef:創建一個ref對象,獲取節點信息,直接舉個例子:
import React, { Component } from 'react';
class Index extends Component{
constructor(props){
super(props)
}
node = React.createRef()
componentDidMount(){
console.log(this.node)
}
render(){
return <div ref={this.node} > 節點信息 </div>
}
}
export default Index;
效果:
這個有點雞肋,因爲我們可以直接從ref上獲取到值,沒有必要通過createRef去獲取,像這樣
import React, { Component } from 'react';
class Index extends Component{
constructor(props){
super(props)
}
node = null
componentDidMount(){
console.log(this.node)
}
render(){
return <div ref={(node) => this.node = node} > 節點信息 </div>
}
}
export default Index;
createFactory
createFactory:返回一個生成給定類型的 React 元素的函數。
接受一個參數type,這個type與createElement的type一樣,原生組件的話是標籤的字符串,如“div”,如果是React自定義組件,則會傳入組件
效果與createElement一樣,但這個說是遺留的,官方建議直接使用createElement,並且在使用上也會給出警告
栗子🌰:
import React, { useEffect } from 'react';
const Child = React.createFactory(()=><div>createFactory</div>)
const Index = () => {
return <div style={{padding: 20}}>
大家好,我是小杜杜
<Child />
</div>
}
export default Index;
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() 這個構造函數,如:
constructor(props) {
super(props);
}
-
super的作用“用來調用基類的構造方法 ( constructor() ), 也將父組件的 props 注入給子組件,供子組件讀取 (組件中props只讀不可變``,state可變) -
初始化操作,定義 this.state 的初始內容
-
只會執行一次
Mounting(掛載)
componentWillMount:在組件掛載到 DOM 前調用
-
這裏面的調用的
this.setState不會引起組件的重新渲染,也可以把寫在這邊的內容提到constructor(),所以在項目中很少。 -
只會調用一次
render: 渲染
-
只要
props和state發生改變(無兩者的重傳遞和重賦值,論值是否有變化,都可以引起組件重新 render),都會重新渲染 render。 -
return:是必須的,是一個 React 元素(UI,描述組件),不負責組件實際渲染工作,由 React 自身根據此元素去渲染出 DOM。 -
render是純函數(Pure function:返回的結果只依賴與參數,執行過程中沒有副作用),不能執行 this.setState。
componentDidMount:組件掛載到 DOM 後調用
- 調用一次
Update(更新)
componentWillReceiveProps(nextProps): 調用於 props 引起的組件更新過程中
-
nextProps:父組件傳給當前組件新的 props -
可以用
nextProps和this.props來查明重傳 props 是否發生改變(原因:不能保證父組件重傳的 props 有變化) -
只要 props 發生變化就會,引起調用
shouldComponentUpdate(nextProps, nextState):性能優化組件
-
nextProps:當前組件的 this.props
-
nextState:當前組件的 this.state
-
通過比較
nextProps和nextState, 來判斷當前組件是否有必要繼續執行更新過程。返回 false:表示停止更新,用於減少組件的不必要渲染,優化性能
返回 true:繼續執行更新
-
像
componentWillReceiveProps()中執行了 this.setState,更新了 state,但在render前 (如 shouldComponentUpdate,componentWillUpdate),this.state 依然指向更新前的 state,不然 nextState 及當前組件的 this.state 的對比就一直是 true 了
componentWillUpdate(nextProps, nextState):組件更新前調用
-
在 render 方法前執行
-
由於組件更新就會調用,所以一般很少使用
-
render:重新渲染
componentDidUpdate(prevProps, prevState):組件更新後被調用
-
prevProps:組件更新前的 props
-
prevState:組件更新前的 state
-
可以操作組件更新的 DOM
Unmounting(卸載)
componentWillUnmount:組件被卸載前調用
- 可以在這裏執行一些清理工作,比如清楚組件中使用的定時器,清楚 componentDidMount 中手動創建的 DOM 元素等,以避免引起內存泄漏
React v16.4
與 v16.0 的生命週期相比
新增了 getDerivedStateFromProps 和 getSnapshotBeforeUpdate
取消了 componentWillMount、componentWillReceiveProps、componentWillUpdate
getDerivedStateFromProps
getDerivedStateFromProps(prevProps, prevState):組件創建和更新時調用的方法
-
prevProps:組件更新前的 props -
prevState:組件更新前的 state
注意:在 React v16.3 中,在創建和更新時,只能是由父組件引發纔會調用這個函數,在 React v16.4 改爲無論是 Mounting 還是 Updating,也無論是什麼引起的 Updating,全部都會調用。
有點類似於componentWillReceiveProps,不同的是getDerivedStateFromProps是一個靜態函數,也就是這個函數不能通過 this 訪問到 class 的屬性,當然也不推薦使用
如果 props 傳入的內容不需要影響到你的 state,那麼就需要返回一個 null,這個返回值是必須的,所以儘量將其寫到函數的末尾。
在組件創建時和更新時的 render 方法之前調用,它應該返回一個對象來更新狀態,或者返回 null 來不更新任何內容。
getSnapshotBeforeUpdate
getSnapshotBeforeUpdate(prevProps,prevState):Updating 時的函數,在 render 之後調用
-
prevProps:組件更新前的 props -
prevState:組件更新前的 state
可以讀取,但無法使用 DOM 的時候,在組件可以在可能更改之前從 DOM 捕獲一些信息(例如滾動位置)
返回的任何指都將作爲參數傳遞給componentDidUpdate()
注意
在 17.0 的版本,官方徹底廢除 componentWillMount、componentWillReceiveProps、componentWillUpdate
如果還想使用的話可以使用:UNSAFE_componentWillMount()、UNSAFE_componentWillReceiveProps()、UNSAFE_componentWillUpdate()
對了,如果在面試的時候可能會問道有關生命週期的問題,建議各位小夥伴,將以上的生命週期都可說一說,然後做個對比,這樣的話,效果肯定不錯~
react-hooks
react-hooks是React 16.8的產物,給函數式組件賦上了生命週期,再經過三年多的時間,函數式組件已經逐漸取代了類組件,可以說是React開發者必備的技術
同時在React v18中又出現了一些hooks,今天我們將一起詳細的看看,確保你能迅速掌握~
React v16.8 中的 hooks
useState
useState:定義變量,可以理解爲他是類組件中的this.state
使用:
const [state, setState] = useState(initialState);
-
state:目的是提供給UI,作爲渲染視圖的數據源 -
setState:改變 state 的函數,可以理解爲this.setState -
initialState:初始默認值
在這裏我介紹兩種寫法,直接看栗子🌰:
import React, { useState } from 'react';
import { Button } from 'antd-mobile';
const Index = () => {
const [ number, setNumber ] = useState(0)
return <div style={{padding: 20}}>
<div>數字:{number}</div>
<Button
color='primary'
onClick={() => {
setNumber(number + 1) //第一種
}}
>
點擊加1
</Button>
<Button
color='primary'
style={{marginLeft: 8}}
onClick={() => {
setNumber((value) => value + 2) //第二種
}}
>
點擊加2
</Button>
</div>
}
export default Index
效果:
注意點
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
useEffect
useEffect:副作用,你可以理解爲是類組件的生命週期,也是我們最常用的鉤子
那麼什麼是副作用呢? 副作用(Side Effect:是指 function 做了和本身運算返回值無關的事,如請求數據、修改全局變量,打印、數據獲取、設置訂閱以及手動更改 React 組件中的 DOM 都屬於副作用操作都算是副作用
我們直接演示下它的用法栗子🌰:
不斷執行
當useEffect不設立第二個參數時,無論什麼情況,都會執行
模擬初始化和卸載
我們可以利用useEffect弄掛載和卸載階段,通常我們用於監聽addEventListener和removeEventListener的使用
import React, { useState, useEffect } from 'react';
import { Button } from 'antd-mobile';
const Child = () => {
useEffect(() => {
console.log('掛載')
return () => {
console.log('卸載')
}
}, [])
return <div>大家好,我是小杜杜</div>
}
const Index = () => {
const [ flag, setFlag ] = useState(false)
return <div style={{padding: 20}}>
<Button
color='primary'
onClick={() => {
setFlag(v => !v)
}}
>
{flag ? '卸載' : '掛載'}
</Button>
{flag && <Child />}
</div>
}
export default Index
效果:
根據依賴值改變
我們可以設置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
效果:
useContext
useContext:上下文,類似於Context:其本意就是設置全局共享數據,使所有組件可跨層級實現共享
useContext的參數一般是由createContext的創建,通過 CountContext.Provider 包裹的組件,才能通過 useContext 獲取對應的值
舉個例子🌰:
import React, { useState, createContext, useContext } from 'react';
import { Button } from 'antd-mobile';
const CountContext = createContext(-1)
const Child = () => {
const count = useContext(CountContext)
return <div style={{marginTop: 20}}>
子組件獲取到的count: {count}
<Son />
</div>
}
const Son = () => {
const count = useContext(CountContext)
return <div style={{marginTop: 20}}>
孫組件獲取到的count: {count}
</div>
}
const Index = () => {
const [ count, setCount ] = useState(0)
return <div style={{padding: 20}}>
<div>父組件:{count}</div>
<Button
color='primary'
onClick={() => setCount(v => v + 1)}
>
點擊加1
</Button>
<CountContext.Provider value={count}>
<Child />
</CountContext.Provider>
</div>
}
export default Index
效果:
useReducer
useReducer:它類似於redux功能的 api
結構:
const [state, dispatch] = useReducer(reducer, initialArg, init);
-
state:更新後的state值 -
dispatch:可以理解爲和useState的setState一樣的效果 -
reducer:可以理解爲redux的reducer -
initialArg:初始值 -
init:惰性初始化
直接來看看栗子🌰:
import React, { useReducer } from 'react';
import { Button } from 'antd-mobile';
const Index = () => {
const [count, dispatch] = useReducer((state, action)=> {
switch(action?.type){
case 'add':
return state + action?.payload;
case 'sub':
return state - action?.payload;
default:
return state;
}
}, 0);
return <div style={{padding: 20}}>
<div>count:{count}</div>
<Button
color='primary'
onClick={() => dispatch({type: 'add', payload: 1})}
>
加1
</Button>
<Button
color='primary'
style={{marginLeft: 8}}
onClick={() => dispatch({type: 'sub', payload: 1})}
>
減1
</Button>
</div>
}
export default Index
效果:
useMemo
useMemo: 與memo的理念上差不多,都是判斷是否滿足當前的限定條件來決定是否執行callback函數,而useMemo的第二個參數是一個數組,通過這個數組來判定是否更新回掉函數
當一個父組件中調用了一個子組件的時候,父組件的 state 發生變化,會導致父組件更新,而子組件雖然沒有發生改變,但也會進行更新。
簡單的理解下,當一個頁面內容非常複雜,模塊非常多的時候,函數式組件會從頭更新到尾,只要一處改變,所有的模塊都會進行刷新,這種情況顯然是沒有必要的。
我們理想的狀態是各個模塊只進行自己的更新,不要相互去影響,那麼此時用useMemo是最佳的解決方案。
這裏要尤其注意一點,只要父組件的狀態更新,無論有沒有對自組件進行操作,子組件都會進行更新,useMemo就是爲了防止這點而出現的
爲了更好的理解useMemo,我們來看下面一個小栗子🌰:
// usePow.ts
const Index = (list: number[]) => {
return list.map((item:number) => {
console.log(1)
return Math.pow(item, 2)
})
}
export default Index;
// index.tsx
import { Button } from 'antd-mobile';
import React,{ useState } from 'react';
import { usePow } from '@/components';
const Index:React.FC<any> = (props)=> {
const [flag, setFlag] = useState<boolean>(true)
const data = usePow([1, 2, 3])
return (
<div>
<div>數字:{JSON.stringify(data)}</div>
<Button color='primary' onClick={() => {setFlag(v => !v)}}>切換</Button>
<div>切換狀態:{JSON.stringify(flag)}</div>
</div>
);
}
export default Index;
我們簡單的寫了個 usePow,我們通過 usePow 給所傳入的數字平方, 用切換狀態的按鈕表示函數內部的狀態,我們來看看此時的效果:
我們發現了一個問題,爲什麼點擊切換按鈕也會觸發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;
發現此時就已經解決了這個問題,不會在做相關的渲染了
useCallback
useCallback與useMemo極其類似, 可以說是一模一樣,唯一不同的是useMemo返回的是函數運行的結果,而useCallback返回的是函數
注意:這個函數是父組件傳遞子組件的一個函數,防止做無關的刷新,其次,這個組件必須配合memo, 否則不但不會提升性能,還有可能降低性能
import React, { useState, useCallback } from 'react';
import { Button } from 'antd-mobile';
const MockMemo: React.FC<any> = () => {
const [count,setCount] = useState(0)
const [show,setShow] = useState(true)
const add = useCallback(()=>{
setCount(count + 1)
},[count])
return (
<div>
<div style={{display: 'flex', justifyContent: 'flex-start'}}>
<TestButton title="普通點擊" onClick={() => setCount(count + 1) }/>
<TestButton title="useCallback點擊" onClick={add}/>
</div>
<div style={{marginTop: 20}}>count: {count}</div>
<Button onClick={() => {setShow(!show)}}> 切換</Button>
</div>
)
}
const TestButton = React.memo((props:any)=>{
console.log(props.title)
return <Button color='primary' onClick={props.onClick} style={props.title === 'useCallback點擊' ? {
marginLeft: 20
} : undefined}>{props.title}</Button>
})
export default MockMemo;
我們可以看到,當點擊切換按鈕的時候,沒有經過 useCallback封裝的函數會再次刷新,而經過 useCallback包裹的函數不會被再次刷新
有很多小夥伴有個誤區,就是useCallback不能單獨使用,必須要配合memo嗎?
其實是這樣的,你可以單獨使用useCallback,但只用useCallback起不到優化的作用,反而會增加性能消耗
想之前講的,React.memo會通過淺比較裏面的props,如果沒有memo,那麼使用的useCallback也就毫無意義
因爲useCallback本身是需要開銷的,所以反而會增加性能的消耗
useRef
useRef: 可以獲取當前元素的所有屬性,並且返回一個可變的 ref 對象,並且這個對象只有 current 屬性,可設置initialValue
結構:
const refContainer = useRef(initialValue);
有許多小夥伴只知道useRef可以獲取對應元素的屬性,但useRef還具備一個功能,就是緩存數據,接下來一起看看:
通過 useRef 獲取對應的屬性值
栗子🌰:
import React, { useState, useRef } from 'react';
const Index:React.FC<any> = () => {
const scrollRef = useRef<any>(null);
const [clientHeight, setClientHeight ] = useState<number>(0)
const [scrollTop, setScrollTop ] = useState<number>(0)
const [scrollHeight, setScrollHeight ] = useState<number>(0)
const onScroll = () => {
if(scrollRef?.current){
let clientHeight = scrollRef?.current.clientHeight; //可視區域高度
let scrollTop = scrollRef?.current.scrollTop; //滾動條滾動高度
let scrollHeight = scrollRef?.current.scrollHeight; //滾動內容高度
setClientHeight(clientHeight)
setScrollTop(scrollTop)
setScrollHeight(scrollHeight)
}
}
return (
<div >
<div >
<p>可視區域高度:{clientHeight}</p>
<p>滾動條滾動高度:{scrollTop}</p>
<p>滾動內容高度:{scrollHeight}</p>
</div>
<div style={{height: 200, overflowY: 'auto'}} ref={scrollRef} onScroll={onScroll} >
<div style={{height: 2000}}></div>
</div>
</div>
);
};
export default Index;
效果:
從上述可知,我們可以通過useRef來獲取對應元素的相關屬性,以此來做一些操作
緩存數據
react-redux的源碼中,在 hooks 推出後,react-redux用大量的 useMemo 重做了 Provide 等核心模塊,其中就是運用 useRef 來緩存數據,並且所運用的 useRef() 沒有一個是綁定在 dom 元素上的,都是做數據緩存用的
可以簡單的來看一下:
// 緩存數據
/* react-redux 用userRef 來緩存 merge之後的 props */
const lastChildProps = useRef()
// lastWrapperProps 用 useRef 來存放組件真正的 props信息
const lastWrapperProps = useRef(wrapperProps)
//是否儲存props是否處於正在更新狀態
const renderIsScheduled = useRef(false)
//更新數據
function captureWrapperProps(
lastWrapperProps,
lastChildProps,
renderIsScheduled,
wrapperProps,
actualChildProps,
childPropsFromStoreUpdate,
notifyNestedSubs
) {
lastWrapperProps.current = wrapperProps
lastChildProps.current = actualChildProps
renderIsScheduled.current = false
}
我們看到 react-redux 用重新賦值的方法,改變了緩存的數據源,減少了不必要的更新,如過採取useState勢必會重新渲染。
有的時候我們需要使用 useMemo、useCallbackApi,我們控制變量的值用 useState 有可能會導致拿到的是舊值,並且如果他們更新會帶來整個組件重新執行,這種情況下,我們使用 useRef 將會是一個非常不錯的選擇
useImperativeHandle
useImperativeHandle:可以讓你在使用 ref 時自定義暴露給父組件的實例值
這個 Api 我覺得是十分有用的,建議掌握哦,來看看使用的場景:
在一個頁面很複雜的時候,我們會將這個頁面進行模塊化,這樣會分成很多個模塊,有的時候我們需要在最外層的組件上控制其他組件的方法,希望最外層的點擊事件,同時執行子組件的事件,這時就需要 useImperativeHandle 的幫助
結構:
useImperativeHandle(ref, createHandle, [deps])
-
ref:useRef所創建的 ref -
createHandle:處理的函數,返回值作爲暴露給父組件的 ref 對象。 -
deps:依賴項,依賴項更改形成新的 ref 對象。
舉個栗子🌰:
import React, { useState, useImperativeHandle, useRef } from 'react';
import { Button } from 'antd-mobile';
const Child = ({cRef}) => {
const [count, setCount] = useState(0)
useImperativeHandle(cRef, () => ({
add
}))
const add = () => {
setCount((v) => v + 1)
}
return <div style={{marginBottom: 20}}>
<p>點擊次數:{count}</p>
<Button color='primary' onClick={() => add()}>加1</Button>
</div>
}
const Index = () => {
const ref = useRef(null)
return <div style={{padding: 20}}>
<div>大家好,我是小杜杜</div>
<div>注意:是在父組件上的按鈕,控制子組件的加1哦~</div>
<Button
color='primary'
onClick={() => ref.current.add()}
>
父節點上的加1
</Button>
<Child cRef={ref} />
</div>
}
export default Index
效果:
useLayoutEffect
useLayoutEffect: 與useEffect基本一致,不同的地方時,useLayoutEffect是同步
要注意的是useLayoutEffect在 DOM 更新之後,瀏覽器繪製之前,這樣做的好處是可以更加方便的修改 DOM,獲取 DOM 信息, 這樣瀏覽器只會繪製一次,所以useLayoutEffect在useEffect之前執行
如果是useEffect的話 ,useEffect 執行在瀏覽器繪製視圖之後,如果在此時改變DOM,有可能會導致瀏覽器再次迴流和重繪。
除此之外useLayoutEffect的 callback 中代碼執行會阻塞瀏覽器繪製
舉個例子🌰:
import React, { useState, useLayoutEffect, useEffect, useRef } from 'react';
import { Button } from 'antd-mobile';
const Index = () => {
const [count, setCount] = useState(0)
const time = useRef(null)
useEffect(()=>{
if(time.current){
console.log("useEffect:", performance.now() - time.current)
}
})
useLayoutEffect(()=>{
if(time.current){
console.log("useLayoutEffect:", performance.now() - time.current)
}
})
return <div style={{padding: 20}}>
<div>count: {count}</div>
<Button
color='primary'
onClick={() => {
setCount(v => v + 1)
time.current = performance.now()
}}
>
加1
</Button>
</div>
}
export default Index
效果:
useDebugValue
useDebugValue:可用於在 React 開發者工具中顯示自定義 hook 的標籤
官方並不推薦你向每個自定義 Hook 添加 debug 值。當它作爲共享庫的一部分時才最有價值。
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
// ...
// 在開發者工具中的這個 Hook 旁邊顯示標籤
// e.g. "FriendStatus: Online" useDebugValue(isOnline ? 'Online' : 'Offline');
return isOnline;
}
React v18 中的 hooks
useSyncExternalStore
useSyncExternalStore: 是一個推薦用於讀取和訂閱外部數據源的 hook,其方式與選擇性的 hydration 和時間切片等併發渲染功能兼容
結構:
const state = useSyncExternalStore(subscribe, getSnapshot[, getServerSnapshot])
-
subscribe: 訂閱函數,用於註冊一個回調函數,當存儲值發生更改時被調用。此外,useSyncExternalStore會通過帶有記憶性的getSnapshot來判別數據是否發生變化,如果發生變化,那麼會強制更新數據。 -
getSnapshot: 返回當前存儲值的函數。必須返回緩存的值。如果getSnapshot連續多次調用,則必須返回相同的確切值,除非中間有存儲值更新。 -
getServerSnapshot:返回服務端 (hydration模式下) 渲染期間使用的存儲值的函數
舉個栗子🌰:
import React, {useSyncExternalStore} from 'react';
import { combineReducers , createStore } from 'redux'
const reducer = (state=1,action) => {
switch (action.type){
case 'ADD':
return state + 1
case 'DEL':
return state - 1
default:
return state
}
}
/* 註冊reducer,並創建store */
const rootReducer = combineReducers({ count: reducer })
const store = createStore(rootReducer,{ count: 1 })
const Index = () => {
// 訂閱
const state = useSyncExternalStore(store.subscribe,() => store.getState().count)
return <div>
<div>{state}</div>
<div>
<button onClick={() => store.dispatch({ type:'ADD' })} >加1</button>
<button style={{marginLeft: 8}} onClick={() => store.dispatch({ type:'DEL' })} >減1</button>
</div>
</div>
}
export default Index
效果:
從上述代碼可以看出,當點擊按鈕後,會觸發 store.subscribe(訂閱函數),執行getSnapshot後得到新的count,如果count發生變化,則會觸發更新
useTransition
useTransition:返回一個狀態值表示過渡任務的等待狀態,以及一個啓動該過渡任務的函數。
那麼什麼是過渡任務?
在一些場景中,如:輸入框、tab 切換、按鈕等,這些任務需要視圖上立刻做出響應,這些任務可以稱之爲立即更新的任務
但有的時候,更新任務並不是那麼緊急,或者來說要去請求數據等,導致新的狀態不能立更新,需要用一個loading...的等待狀態,這類任務就是過度任務
結構:
const [isPending, startTransition] = useTransition();
-
isPending:過渡狀態的標誌,爲true時是等待狀態 -
startTransition:可以將裏面的任務變成過渡任務
大家可能對上面的描述存在着一些疑問,我們直接舉個例子🌰來說明:
import React, { useState, useTransition } from 'react';
const Index = () => {
const [isPending, startTransition] = useTransition();
const [input, setInput] = useState('');
const [list, setList] = useState([]);
return <div>
<div>大家好:我是小杜杜~</div>
輸入框: <input
value={input}
onChange={(e) => {
setInput(e.target.value);
startTransition(() => {
const res = [];
for (let i = 0; i < 2000; i++) {
res.push(e.target.value);
}
setList(res);
});
}} />
{isPending ? (
<div>加載中...</div>
) : (
list.map((item, index) => <div key={index}>{item}</div>)
)}
</div>
}
export default Index
效果:
實際上,我們在Input輸入內容是,會進行增加,假設我們在startTransition中請求一個接口,在接口請求的時候,isPending會爲true,就會有一個loading的狀態,請求完之後,isPending變爲false渲染列表
useDeferredValue
useDeferredValue:接受一個值,並返回該值的新副本,該副本將推遲到更緊急地更新之後。
如果當前渲染是一個緊急更新的結果,比如用戶輸入,React 將返回之前的值,然後在緊急渲染完成後渲染新的值。
也就是說useDeferredValue可以讓狀態滯後派生
結構:
const deferredValue = useDeferredValue(value);
-
value:可變的值,如useState創建的值 -
deferredValue: 延時狀態
這個感覺和useTransition有點相似,還是以輸入框的模式,舉個栗子🌰:
import React, { useState, useDeferredValue } from 'react';
const getList = (key) => {
const arr = [];
for (let i = 0; i < 10000; i++) {
if (String(i).includes(key)) {
arr.push(<li key={i}>{i}</li>);
}
}
return arr;
};
const Index = () => {
const [value, setValue] = useState("");
const deferredValue = useDeferredValue(value);
console.log('value:', value);
console.log('deferredValue:',deferredValue);
return (
<div >
<div>
<div>大家好,我是小杜杜</div>
輸入框:<input onChange={(e) => setValue(e.target.value)} />
</div>
<div>
<ul>{deferredValue ? getList(deferredValue) : null}</ul>
</div>
</div>
);
}
export default Index
效果:
和 useTransition 做對比
根據上面兩個示例我們看看useTransition和useDeferredValue做個對比看看
-
相同點:
useDeferredValue和useTransition一樣,都是過渡更新任務 -
不同點:
useTransition給的是一個狀態,而useDeferredValue給的是一個值
useInsertionEffect
useInsertionEffect:與 useEffect一樣,但它在所有 DOM 突變 之前同步觸發。
我們來看看useInsertionEffect對比於useEffect和useLayoutEffect在執行順序上有什麼區別,栗子🌰:
useEffect(()=>{
console.log('useEffect')
},[])
useLayoutEffect(()=>{
console.log('useLayoutEffect')
},[])
useInsertionEffect(()=>{
console.log('useInsertionEffect')
},[])
可以看到在執行順序上 useInsertionEffect > useLayoutEffect > useEffect
特別注意一點:useInsertionEffect 應僅限於 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
效果:
useId
useId : 是一個用於生成橫跨服務端和客戶端的穩定的唯一 ID 的同時避免hydration 不匹配的 hook。
這裏牽扯到SSR的問題,我打算之後在單獨寫一章,來詳細講講,所以在這裏就介紹一下使用即可
const id = useId();
例子🌰:
import React, { useId } from 'react';
const Index = () => {
const id = useId()
return (
<div>
<div id={id} >
大家好,我是小杜杜
</div>
</div>
);
}
export default Index
效果:
自定義 hooks
自定義hooks是在react-hooks基礎上的一個擴展,可以根據業務、需求去制定相應的hooks, 將常用的邏輯進行封裝,從而具備複用性
react-dom
react-dom:這個包提供了用戶 DOM 的特定方法。這個包在React v18中還是做了很大的改動,接下來我們逐個看看
createPortal
createPortal:在Portal中提供了一種將子節點渲染到已 DOM 節點中的方式,該節點存在於 DOM 組件的層次結構之外。
也就是說createPortal可以把當前組件或element元素的子節點,渲染到組件之外的其他地方。
來看看createPortal(child, container)的入參:
-
child:任何可渲染的子元素 -
container:是一個DOM元素
看着概念可能並不是很好理解,我們來舉個栗子🌰:
import React, { useState, useEffect, useRef } from 'react';
import ReactDom from 'react-dom'
const Child = ({children}) => {
const ref = useRef()
const [newDom, setNewDom] = useState()
useEffect(() => {
setNewDom(ReactDom.createPortal(children, ref.current))
}, [])
return <div>
<div ref={ref}>同級的節點</div>
<div>
這層的節點
{newDom}
</div>
</div>
}
const Index = () => {
return <div style={{padding: 20}}>
<Child>
<div>大家好,我是小杜杜</div>
</Child>
</div>
}
export default Index;
要注意下Child:
children被createPortal包裹後,children的節點位置會如何?
newDom的數據到了同級的節點處,那麼這個Api該如何應用呢?
我們可以處理一些頂層元素,如:Modal彈框組件,Modal組件在內部中書寫,掛載到外層的容器(如 body),此時這個Api就非常有用
flushSync
flushSync:可以將回調函數中的更新任務,放到一個較高級的優先級中,適用於強制刷新,同時確保了DOM會被立即更新
爲了更好的理解,我們舉個栗子🌰:
import { Button } from 'antd-mobile';
import React, { Component} from 'react';
import ReactDOM from 'react-dom'
class Index extends Component{
constructor(props){
super(props)
this.state={
number: 0
}
}
render(){
const { number } = this.state
console.log(number)
return <div style={{padding: 20}}>
<div>數字: {number}</div>
<Button
color='primary'
onClick={() => {
this.setState({ number: 1 })
this.setState({ number: 2 })
this.setState({ number: 3 })
}}
>
點擊
</Button>
</div>
}
}
export default Index;
我們看看點擊按鈕會打印出什麼?
這個不難理解,因爲this.setState會進行批量更新,所以打印出的是 3 接下來,我們用flushSync處理下number: 2 來看看是什麼效果:
onClick={() => {
this.setState({ number: 1 })
ReactDOM.flushSync(()=>{
this.setState({ number: 2 })
})
this.setState({ number: 3 })
}}
可以發現flushSync會優先執行,並且強制刷新,所以會改變number值爲 2,然後1和3在被批量刷新,更新爲 3
render
render:這個是我們在react-dom中最常用的 Api,用於渲染一個react元素
我們通常使用在根部,如:
ReactDOM.render(
< App / >,
document.getElementById('app')
)
createRoot
在React v18中,render函數已經被createRoot所替代
createRoot會控制你傳入的容器節點的內容。當調用 render 時,裏面的任何現有 DOM 元素都會被替換。後面的調用使用 React 的 DOM diffing 算法進行有效更新。
並且createRoot不修改容器節點(只修改容器的子節點)。可以在不覆蓋現有子節點的情況下將組件插入現有 DOM 節點。
如:
import React, { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
const rootElement = document.getElementById('root');
const root = createRoot(rootElement);
root.render(
<StrictMode>
<Main />
</StrictMode>
);
hydrate
hydrate:服務端渲染用hydrate與 render()相同,但它用於在 ReactDOMServer 渲染的容器中對 HTML 的內容進行 hydrate 操作。
hydrate(element, container[, callback])
hydrateRoot()
hydrate在React v18也被替代爲hydrateRoot()
hydrateRoot(container, element[, options])
unmountComponentAtNode
unmountComponentAtNode:從 DOM 中卸載組件,會將其事件處理器(event handlers)和 state 一併清除。如果指定容器上沒有對應已掛載的組件,這個函數什麼也不會做。如果組件被移除將會返回 true,如果沒有組件可被移除將會返回 false。
舉個栗子🌰:
import { Button } from 'antd-mobile';
import React, { Component} from 'react';
import ReactDOM from 'react-dom'
const Child = () => {
return <div>大家好,我是小杜杜</div>
}
class Index extends Component{
constructor(props){
super(props)
this.state={
number: 0
}
}
node = null
componentDidMount(){
ReactDOM.render(<Child/>, this.node) // 創建一個容器
}
render(){
const { number } = this.state
console.log(number)
return <div style={{padding: 20}}>
<div ref={(node) => this.node = node}></div>
<Button
color='primary'
onClick={() => {
const res = ReactDOM.unmountComponentAtNode(this.node)
console.log(res)
}}
>
卸載
</Button>
</div>
}
}
export default Index;
效果:
root.unmount()
unmountComponentAtNode 同樣在React 18中被替代了,替換成了createRoot中的unmount()方法
const root = createRoot(container);
root.render(element);
root.unmount()
findDOMNode
findDOMNode:用於訪問組件DOM元素節點(應急方案),官方推薦使用ref
需要注意的是:
-
findDOMNode只能用到掛載的組件上 -
findDOMNode只能用於類組件,不能用於函數式組件 -
如果組件渲染爲
null或者爲false,那麼findDOMNode返回的值也是null -
如果是多個子節點
Fragment的情況,findDOMNode會返回第一個非空子節點對應的 DOM 節點。 -
在嚴格模式下這個方法已經被
棄用舉個例子🌰:
import { Button } from 'antd-mobile';
import React, { Component} from 'react';
import ReactDOM from 'react-dom'
class Index extends Component{
render(){
return <div style={{padding: 20}}>
<div>大家好,我是小杜杜</div>
<Button
color='primary'
onClick={() => {
console.log(ReactDOM.findDOMNode(this))
}}
>
獲取容器
</Button>
</div>
}
}
export default Index;
效果:
unstable_batchedUpdates
unstable_batchedUpdates : 可用於手動批量更新 state,可以指定多個setState合併爲一個更新請求
那麼這塊手動合併,用在什麼情況下呢?來看看下面的場景:
import { Button } from 'antd-mobile';
import React, { Component} from 'react';
import ReactDOM from 'react-dom'
class Index extends Component{
constructor(props){
super(props)
this.state={
number: 0
}
}
render(){
const { number } = this.state
return <div style={{padding: 20}}>
<div>數字: {number}</div>
<Button
color='primary'
onClick={() => {
this.setState({ number: this.state.number + 1 })
console.log(this.state.number)
this.setState({ number: this.state.number + 1 })
console.log(this.state.number)
this.setState({ number: this.state.number + 1 })
console.log(this.state.number)
}}
>
點擊
</Button>
</div>
}
}
export default Index
當我們點擊按鈕後,三個打印會打印出什麼?
那麼我們打破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 次,並且渲染的結果爲 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>
效果:
最後
參考文檔
-
react 官方文檔 (https://reactjs.org/docs/react-api.html")
-
「React 進階」 React 全部 Hooks 使用大全 (包含 React v18 版本 )(https://juejin.cn/post/7118937685653192735)
總結
本文基本總結了React的所有Api,如果有沒寫到的,或者是Api用法沒寫全的,請在下方評論區留言,儘量把這篇文章打造成最全的~
主要包擴組件類、工具類、生命週期、react-hooks、react-dom五大模塊的內容,如果你能耐心的看完,相信你對React一定有了更深的理解,同時建議初學者親自嘗試一遍,看看這些Api怎麼用,如何用~
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/5lRizD9xAbOylv04cygnhg