「React 深入」一文吃透React v18 全部 Api(1.3w+ 字)

发表于 2年以前  | 总阅读数:581 次

大家好, 俗话说的好,工欲善其事必先利其器,什么意思呢?就是说你想玩转React就必须知道React有什么,无论是否运用到,首先都要知道,提升思维广度~

其实React官方提供了很多Api,只是这些Api我们并不常用,所以往往会忽略它们,但在一些特定的场景下,这些Api也会起到关键性的作用,所以今天就逐个盘点一下,说说它们的使用方法和使用场景。

当然这些Api并不需要全部掌握,只需要知道有这个知识点就好了~

本文将会全面总结所有的ReactApi,包含组件类工具类生命周期react-hooksreact-dom五大模块,并配带示例,帮助大家更好的掌握,大家可以边嗑瓜子边阅读,如有不全、不对的地方欢迎在评论区指出~

由于本文过长,建议点赞 +收藏, 在正式开始前,我抽取了一些问题,一起来看看:

  • 1.React v18中对react-dom做了那些改动,增加了那些新的hooks?
  • 2.useRef除了获取元素的节点信息,还能做什么?
  • 3.为什么会有Children.map?它与不同的遍历有和不同
  • 4.类组件的生命周期在不同的版本是怎样变化的?
  • 5.子元素如何渲染到父元素上面的?
  • ...

其实问题很多,看完这篇文章后,相信一定能帮你解答的非常清楚,还请各位小伙伴多多支持一下

前言

在之前的几篇文章中,有自定义hooksHOC虚拟DOMdiff算法,多多少少都有React官方Api做为基础条件,我的这个专栏的目的就是对React的深入,就是希望对React有一个全面的提升

写这篇文章的主要目的有:

  • 提升知识广度,要想深入React就必须全面了解React,首先要学会用,要知道,如果连知道都不知道,谈何深入?
  • React v18react-dom的改动还是比较大的,并且新增了五个hooks,逐一盘点一下,看看做了那些改动
  • 这个专栏实际上是循序渐进的,相互之间都有一定的关联,同时要想看懂,也需要有一定的React基础,对刚开始学习React的小伙伴可能并不是太友好,所以特地总结一番,用最简单的示例,帮你掌握这些Api
  • 对之后的源码有帮助,所以本篇文章将会全面解读React Api的使用方法,和场景,如有不足,还希望在评论区指出~

附上一张今天的学习图谱~

全面解读ReactApi.png

组件类

Component

在React中提供两种形式,一种是类组件,另一种是函数式组件,而在类组件组件中需要使用Component继承,这个组件没有什么好讲的,我们可以看看源码:

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

image.png

可以看出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;
复制代码

效果:

img1.gif

可以发现,当我们点击按钮的时候,数字并没有刷新,这是因为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接收两个参数:

  • 第一个参数:组件本身,也就是要优化的组件
  • 第二个参数:(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 的状态,可以看出flagChild之间没有任何关系,那么在切换状态的时候,Child会刷新吗?

直接看看效果:

img2.gif

可以看出,在我们切换状态的时候,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;
复制代码

效果:

img4.gif

当数字小于7,才会出发Child的更新,通过返回的布尔值来控制

memo的注意事项

React.memoPureComponent的区别:

  • 服务对象不同:PureComponent 服务与类组件,React.memo既可以服务于类组件,也可以服务与函数式组件,useMemo服务于函数式组件(后续讲到)
  • 针对的对象不同:PureComponent针对的是propsstateReact.memo只能针对props来决定是否渲染

这里还有个小的注意点:memo的第二个参数的返回值与shouldComponentUpdate的返回值是相反的,经常会弄混,还要多多注意

  • memo:返回 true 组件不渲染 , 返回 false 组件重新渲染。
  • shouldComponentUpdate: 返回 true 组件渲染 , 返回 false 组件不渲染。

forwardRef

forwardRef:引用传递,是一种通过组件向子组件自动传递引用ref的技术。对于应用者的大多数组件来说没什么作用,但对于一些重复使用的组件,可能有用。

听完介绍是不是感觉云里雾里的,官方对forwardRef的介绍也很少,我们来看看转发的问题

React中,React不允许ref通过props传递,因为ref是组件中固定存在的,在组件调和的过程中,会被特殊处理,而forwardRef就是为了解决这件事而诞生的,让ref可以通过props传递

举个栗子:父组件想要获取孙组件上的信息,我们直接用ref传递会怎样:

企业微信截图_0bc3e73d-e6b4-45a9-bd66-c43cd8bbea11.png

接下来看看利用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;
复制代码

效果:

企业微信截图_c7a4aa75-362d-4bba-8c83-ded0b4dce01b.png

如此以来就解决了不能在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;
复制代码

效果:

img5.gif

Suspense

Suspense:让组件"等待"某个异步组件操作,直到该异步操作结束即可渲染。

与上面lazy中的案例一样,两者需要配合使用,其中fallback为等待时渲染的样式

Suspenselazy可以用于等待照片、脚本和一些异步的情况。

Profiler

Profiler:这个组件用于性能检测,可以检测一次react组件渲染时的性能开销

此组件有两个参数:

  • id:标识Profiler的唯一性
  • onRender:回调函数,用于渲染完成,参数在下面讲解

举个栗子:

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



export default Index;
复制代码

让我们来看看打印的是什么:

image.png

依此的含义:

  • idProfiler树的id
  • phasemount挂载,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>
  );
}
复制代码

上述代码中只会对ComponentOneComponentTwo进行检查

主要有以下帮助:

  • 识别具有不安全生命周期的组件[1]
  • 关于旧版字符串引用 API 使用的警告[2]
  • 关于不推荐使用 findDOMNode 的警告[3]
  • 检测意外的副作用[4]
  • 检测遗留上下文 API[5]
  • 确保可重用状态[6]

工具类

crateElement

在之前讲的虚拟DOM[7]的时候已经详细的讲解过,我们在这里在在复习一遍

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来看看:

image.png其实是可以看到传递的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;
复制代码

效果:

image.png

注意:如果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看到:

image.png

我们可以看到每个节点都打印出来了,这种情况属于透明的,但我们要是便利看看:

<Child>
      {
        [1,2,3].map((item) => <p key={item}>大家好,我是小杜杜</p>)
      }
  <p>Hello~</p>
</Child>
复制代码

image.png

却发现我们便利的三个元素被包了一层,像这种数据被称为不透明,我们想要处理这种数据,就要以来React.Chilren 来解决

Children.map

Children.map:遍历,并返回一个数组,针对上面的情况,我们可以通过这个方法将数据便会原先的

const Child = ({children}) => {
  const res = React.Children.map(children, (item) => item)
  console.log(res)
  return res
}
复制代码

效果:

image.png

Children.forEach

Children.forEach:与Children.map类似,不同的是Children.forEach并不会返回值,而是停留在遍历阶段

const Child = ({children}) => {
  React.Children.forEach(children, (item) => console.log(item))
  return children
}
复制代码

效果:

image.png

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
}
复制代码

效果:只有一个时:

image.png

多个时:

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;
复制代码

效果:

image.png

这个有点鸡肋,因为我们可以直接从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;
复制代码

image.png

isValidElement

isValidElement:用于验证是否是React元素,是的话就返回true,否则返回false,感觉这个Api也不是特别有用,因为我们肯定知道是否是

栗子:

    console.log(React.isValidElement(<div>大家好,我是小杜杜</div>)) // true
    console.log(React.isValidElement('大家好,我是小杜杜')) //false
复制代码

version

查看React的版本号:如:

console.log(React.version)
复制代码

版本:

image.png

我们可以看下在React中的文件位置,在react中有一个单独处理版本信息的位置:

packages/shared/ReactVersion.js

image.png

生命周期

React 的 生命周期主要有两个比较大的版本,分别是v16.0前v16.4两个版本的生命周期,我们分别说下旧的和新的生命周期,做下对比~

v16.0前

image.png

从图中,总共分为四大阶段:Intialization(初始化)Mounting(挂载)Update(更新)Unmounting(卸载)

Intialization(初始化)

在初始化阶段,我们会用到 constructor() 这个构造函数,如:

constructor(props) {
  super(props);
}
复制代码
  • super的作用“ 用来调用基类的构造方法( constructor() ), 也将父组件的props注入给子组件,供子组件读取 (组件中props只读不可变``,state可变)
  • 初始化操作,定义this.state的初始内容
  • 只会执行一次

Mounting(挂载)

componentWillMount:在组件挂载到DOM前调用

  • 这里面的调用的this.setState不会引起组件的重新渲染,也可以把写在这边的内容提到constructor(),所以在项目中很少。
  • 只会调用一次

render: 渲染

  • 只要propsstate发生改变(无两者的重传递和重赋值,论值是否有变化,都可以引起组件重新render),都会重新渲染render。
  • return:是必须的,是一个React元素(UI,描述组件),不负责组件实际渲染工作,由React自身根据此元素去渲染出DOM。
  • render纯函数(Pure function:返回的结果只依赖与参数,执行过程中没有副作用),不能执行this.setState。

componentDidMount:组件挂载到DOM后调用

  • 调用一次

Update(更新)

componentWillReceiveProps(nextProps):调用于props引起的组件更新过程中

  • nextProps:父组件传给当前组件新的props
  • 可以用nextPropsthis.props来查明重传props是否发生改变(原因:不能保证父组件重传的props有变化)
  • 只要props发生变化就会,引起调用

**shouldComponentUpdate(nextProps, nextState)**:性能优化组件

  • nextProps:当前组件的this.props

  • nextState:当前组件的this.state

  • 通过比较nextPropsnextState,来判断当前组件是否有必要继续执行更新过程。

    返回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

12b.png

与 v16.0的生命周期相比

新增了 getDerivedStateFromPropsgetSnapshotBeforeUpdate

取消了 componentWillMountcomponentWillReceivePropscomponentWillUpdate

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的版本,官方彻底废除 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);
复制代码
  • 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
复制代码

效果:

img5.gif

注意点

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
复制代码

img1.gif

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
复制代码

效果:

img1.gif

根据依赖值改变

我们可以设置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
复制代码

效果:

useContent

useContent:上下文,类似于Context:其本意就是设置全局共享数据,使所有组件可跨层级实现共享

useContent的参数一般是由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:可以理解为和useStatesetState一样的效果
  • reducer:可以理解为reduxreducer
  • initialArg:初始值
  • init:惰性初始化

直接来看看栗子:

import React, { useReducer } from 'react';
import { Button } from 'antd-mobile';

const Index = () => {

  const [count, dispatch] = useReducer((state, action)=> {
    switch(action?.type){
      case 'add':
        return state + action?.payload;
      case 'sub':
        return state - action?.payload;
      default:
        return state;
    }
  }, 0);

  return <div style={{padding: 20}}>
    <div>count:{count}</div>
    <Button
      color='primary'
      onClick={() => dispatch({type: 'add', payload: 1})}
    >
      加1
    </Button>
    <Button
      color='primary'
      style={{marginLeft: 8}}
      onClick={() => dispatch({type: 'sub', payload: 1})}
    >
      减1
    </Button>
  </div>
}

export default Index
复制代码

效果:

img4.gif

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 给所传入的数字平方, 用切换状态的按钮表示函数内部的状态,我们来看看此时的效果:

img2.gif

我们发现了一个问题,为什么点击切换按钮也会触发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;
复制代码

img3.gif

发现此时就已经解决了这个问题,不会在做相关的渲染了

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;
复制代码

img2.gif

我们可以看到,当点击切换按钮的时候,没有经过 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])
复制代码
  • refuseRef所创建的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
复制代码

效果:

img5.gif

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
复制代码

效果:

img6.gif

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
复制代码

效果:

img1.gif

从上述代码可以看出,当点击按钮后,会触发 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
复制代码

效果:

img3.gif

实际上,我们在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
复制代码

效果:

img4.gif

和useTransition做对比

根据上面两个示例我们看看useTransitionuseDeferredValue做个对比看看

  • 相同点:useDeferredValueuseTransition一样,都是过渡更新任务
  • 不同点:useTransition给的是一个状态,而useDeferredValue给的是一个值

useInsertionEffect

useInsertionEffect:与 useEffect一样,但它在所有 DOM 突变 之前同步触发。

我们来看看useInsertionEffect对比于useEffectuseLayoutEffect在执行顺序上有什么区别,栗子:

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

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

  useInsertionEffect(()=>{
    console.log('useInsertionEffect')
  },[])
复制代码

image.png

可以看到在执行顺序上 useInsertionEffect > useLayoutEffect > useEffect

特别注意一点:seInsertionEffect 应仅限于 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
复制代码

效果:

image.png

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,将常用的逻辑进行封装,从而具备复用性

关于自定义hooks的内容可以看看我之前的文章:搞懂这12个Hooks,保证让你玩转React[8]

里面通过分析ahooks源码,讲解了很多不错的自定义hooks,如:useCreationuseReactiveuseEventListener等的实现,相信一定能够帮助到各位,感兴趣的可以支持下~

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:

我们传入的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;
复制代码

我们看看点击按钮会打印出什么?

img6.gif

这个不难理解,因为this.setState会进行批量更新,所以打印出的是3 接下来,我们用flushSync处理下number: 2 来看看是什么效果:

    onClick={() => {
      this.setState({ number: 1  })
      ReactDOM.flushSync(()=>{
        this.setState({ number: 2  })
      })
      this.setState({ number: 3  })
    }}
复制代码

img1.gif

可以发现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;
复制代码

效果:

img2.gif

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
复制代码

当我们点击按钮后,三个打印会打印出什么?

此时的场景只会执行一次,并且渲染一次,渲染时为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>  
复制代码

此时就会这样:

image.png

因为绕过了事件机制,此时就会渲染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> 
复制代码

效果:

img4.gif

最后

参考文档

  • react 官方文档[9]
  • 「React 进阶」 React 全部 Hooks 使用大全 (包含 React v18 版本 )[10]

总结

本文基本总结了React的所有Api,如果有没写到的,或者是Api用法没写全的,请在下方评论区留言,尽量把这篇文章打造成最全的~

主要包扩组件类工具类生命周期react-hooksreact-dom五大模块的内容,如果你能耐心的看完,相信你对React一定有了更深的理解,同时建议初学者亲自尝试一遍,看看这些Api怎么用,如何用~

至此,签约计划的三篇文章就写完了,说实话,太累了,写出硬文确实比较难,还是希望大家多多关注这个专栏,后续将会带来更好,更全,更容易理解的React文章,还请各位小伙伴多多支持,点赞 + 收藏 哦~

本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/oxQCbznh_zqTEXDPEhlY3A

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237227次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8063次阅读
 目录