一文喫透 React v18 全部 Api
作者:小杜杜
https://juejin.cn/post/7124486630483689485
俗話說的好,工欲善其事必先利其器,什麼意思呢?就是說你想玩轉React
就必須知道React
有什麼,無論是否運用到,首先都要知道,提升思維廣度~
其實React
官方提供了很多Api
,只是這些Api
我們並不常用,所以往往會忽略它們,但在一些特定的場景下,這些Api
也會起到關鍵性的作用,所以今天就逐個盤點一下,說說它們的使用方法和使用場景。
當然這些Api
並不需要全部掌握,只需要知道有這個知識點就好了~
本文將會全面總結所有的React
Api,包含組件類
、工具類
、生命週期
、react-hooks
、react-dom
五大模塊,並配帶示例,幫助大家更好的掌握,大家可以邊嗑瓜子邊閱讀,如有不全、不對的地方歡迎在評論區指出~
由於本文過長,建議點贊
+收藏
, 在正式開始前,我抽取了一些問題,一起來看看:
-
1.
React v18
中對react-dom
做了那些改動,增加了那些新的hooks
? -
2.
useRef
除了獲取元素的節點信息,還能做什麼? -
- 爲什麼會有
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