一文喫透 React v18 全部 Api

作者:小杜杜

https://juejin.cn/post/7124486630483689485

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

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

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

本文將會全面總結所有的ReactApi,包含組件類工具類生命週期react-hooksreact-dom五大模塊,並配帶示例,幫助大家更好的掌握,大家可以邊嗑瓜子邊閱讀,如有不全、不對的地方歡迎在評論區指出~

由於本文過長,建議點贊 +收藏, 在正式開始前,我抽取了一些問題,一起來看看:

其實問題很多,看完這篇文章後,相信一定能幫你解答的非常清楚,還請各位小夥伴多多支持一下

前言

寫這篇文章的主要目的有:

  • 提升知識廣度,要想深入React就必須全面瞭解React,首先要學會用,要知道,如果連知道都不知道,談何深入?

  • React v18react-dom的改動還是比較大的,並且新增了五個hooks, 逐一盤點一下,看看做了那些改動

  • 這個專欄實際上是循序漸進的,相互之間都有一定的關聯,同時要想看懂,也需要有一定的React基礎,對剛開始學習React的小夥伴可能並不是太友好,所以特地總結一番,用最簡單的示例,幫你掌握這些 Api

  • 對之後的源碼有幫助,所以本篇文章將會全面解讀React Api的使用方法,和場景,如有不足,還希望在評論區指出~

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

全面解讀 ReactApi

組件類

Component

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

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

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

PureComponent

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

可以說PureComponentComponent基本完全一致,但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()如果被定義,就會對新舊 propsstate 進行 shallowEqual 比較,新舊一旦不一致,便會觸發 update

也可以這麼理解:PureComponent通過自帶的propsstate的淺比較實現了shouldComponentUpdate(),這點Component並不具備

PureComponent可能會因深層的數據不一致而產生錯誤的否定判斷,從而導致shouldComponentUpdate結果返回 false,界面得不到更新,要謹慎使用

memo

memo:結合了pureComponent純組件componentShouldUpdate功能,會對傳入的 props 進行一次對比,然後根據第二個函數返回值來進一步判斷哪些 props 需要更新

要注意memo是一個高階組件函數式組件類組件都可以使用。

memo接收兩個參數:

性能優化

接下來,我們先看這樣一個🌰:

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 的狀態,可以看出flagChild之間沒有任何關係,那麼在切換狀態的時候,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.memoPureComponent的區別:

這裏還有個小的注意點:memo的第二個參數的返回值與shouldComponentUpdate的返回值是相反的,經常會弄混,還要多多注意

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爲等待時渲染的樣式

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

Profiler

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

此組件有兩個參數:

舉個栗子🌰:

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



export default Index;

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

依此的含義:

需要注意的是,這個組件應該在需要的時候去使用,雖然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>
  );
}

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

主要有以下幫助:

工具類

crateElement

JSX會被編譯爲React.createElement的形式,然後被babel編譯

結構:

React.createElement(type, [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文本內容
                )
            )
        }
    }

注意點

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對象,用Providervalue來傳遞值,用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 內的總個數,等於回調傳遞給mapforEach將被調用的次數。如:

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,這個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() 這個構造函數,如:

constructor(props) {
  super(props);
}

Mounting(掛載)

componentWillMount:在組件掛載到 DOM 前調用

render: 渲染

componentDidMount:組件掛載到 DOM 後調用

Update(更新)

componentWillReceiveProps(nextProps): 調用於 props 引起的組件更新過程中

shouldComponentUpdate(nextProps, nextState):性能優化組件

componentWillUpdate(nextProps, nextState):組件更新前調用

componentDidUpdate(prevProps, prevState):組件更新後被調用

Unmounting(卸載)

componentWillUnmount:組件被卸載前調用

React v16.4

與 v16.0 的生命週期相比

新增了 getDerivedStateFromPropsgetSnapshotBeforeUpdate

取消了 componentWillMountcomponentWillReceivePropscomponentWillUpdate

getDerivedStateFromProps

getDerivedStateFromProps(prevProps, prevState):組件創建和更新時調用的方法

注意:在 React v16.3 中,在創建和更新時,只能是由父組件引發纔會調用這個函數,在 React v16.4 改爲無論是 Mounting 還是 Updating,也無論是什麼引起的 Updating,全部都會調用。

有點類似於componentWillReceiveProps,不同的是getDerivedStateFromProps是一個靜態函數,也就是這個函數不能通過 this 訪問到 class 的屬性,當然也不推薦使用

如果 props 傳入的內容不需要影響到你的 state,那麼就需要返回一個 null,這個返回值是必須的,所以儘量將其寫到函數的末尾。

在組件創建時和更新時的 render 方法之前調用,它應該返回一個對象來更新狀態,或者返回 null 來不更新任何內容。

getSnapshotBeforeUpdate

getSnapshotBeforeUpdate(prevProps,prevState):Updating 時的函數,在 render 之後調用

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

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

注意

在 17.0 的版本,官方徹底廢除 componentWillMountcomponentWillReceivePropscomponentWillUpdate

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

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

react-hooks

react-hooksReact 16.8的產物,給函數式組件賦上了生命週期,再經過三年多的時間,函數式組件已經逐漸取代了類組件,可以說是React開發者必備的技術

同時在React v18中又出現了一些hooks,今天我們將一起詳細的看看,確保你能迅速掌握~

React v16.8 中的 hooks

useState

useState:定義變量,可以理解爲他是類組件中的this.state

使用:

const [state, setState] = useState(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掛載卸載階段,通常我們用於監聽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

效果:

根據依賴值改變

我們可以設置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);

直接來看看栗子🌰:

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

useCallbackuseMemo極其類似, 可以說是一模一樣,唯一不同的是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勢必會重新渲染。

有的時候我們需要使用 useMemouseCallbackApi,我們控制變量的值用 useState 有可能會導致拿到的是舊值,並且如果他們更新會帶來整個組件重新執行,這種情況下,我們使用 useRef 將會是一個非常不錯的選擇

useImperativeHandle

useImperativeHandle:可以讓你在使用 ref 時自定義暴露給父組件的實例值

這個 Api 我覺得是十分有用的,建議掌握哦,來看看使用的場景:

在一個頁面很複雜的時候,我們會將這個頁面進行模塊化,這樣會分成很多個模塊,有的時候我們需要在最外層的組件上控制其他組件的方法,希望最外層的點擊事件,同時執行子組件的事件,這時就需要 useImperativeHandle 的幫助

結構:

useImperativeHandle(ref, createHandle, [deps])

舉個栗子🌰:

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 信息, 這樣瀏覽器只會繪製一次,所以useLayoutEffectuseEffect之前執行

如果是useEffect的話 ,useEffect 執行在瀏覽器繪製視圖之後,如果在此時改變DOM,有可能會導致瀏覽器再次迴流重繪

除此之外useLayoutEffectcallback 中代碼執行會阻塞瀏覽器繪製

舉個例子🌰:

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])

舉個栗子🌰:

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();

大家可能對上面的描述存在着一些疑問,我們直接舉個例子🌰來說明:

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);

這個感覺和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 做對比

根據上面兩個示例我們看看useTransitionuseDeferredValue做個對比看看

useInsertionEffect

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

我們來看看useInsertionEffect對比於useEffectuseLayoutEffect在執行順序上有什麼區別,栗子🌰:

  useEffect(()=>{
    console.log('useEffect')
  },[])

  useLayoutEffect(()=>{
    console.log('useLayoutEffect')
  },[])

  useInsertionEffect(()=>{
    console.log('useInsertionEffect')
  },[])

可以看到在執行順序上 useInsertionEffect > useLayoutEffect > useEffect

特別注意一點:useInsertionEffect 應僅限於 css-in-js 庫作者使用。優先考慮使用 useEffectuseLayoutEffect 來替代。

模擬一下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)的入參:

看着概念可能並不是很好理解,我們來舉個栗子🌰:

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:

我們傳入的childrencreatePortal包裹後,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,然後13在被批量刷新,更新爲 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:服務端渲染用hydraterender()相同,但它用於在 ReactDOMServer 渲染的容器中對 HTML 的內容進行 hydrate 操作。

hydrate(element, container[, callback])

hydrateRoot()

hydrateReact 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

需要注意的是:

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

當我們點擊按鈕後,三個打印會打印出什麼?

此時的場景只會執行一次,並且渲染一次,渲染時爲 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 次,並且渲染的結果爲 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的所有Api,如果有沒寫到的,或者是Api用法沒寫全的,請在下方評論區留言,儘量把這篇文章打造成最全的~

主要包擴組件類工具類生命週期react-hooksreact-dom五大模塊的內容,如果你能耐心的看完,相信你對React一定有了更深的理解,同時建議初學者親自嘗試一遍,看看這些Api怎麼用,如何用~

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/5lRizD9xAbOylv04cygnhg