React中useRef的使用、React.forwardRef、useImperativeHandle、useLayoutEffect的使用


1. useRef的使用以及它和createRef的区别

1.1 useRef介绍

useRef 跟 createRef 类似,都可以用来生成对 DOM 对象的引用,useRef 默认只能在 html 标签中使用,不可以在函数组件中使用。

useRef 和 createRef 的区别:

  • createRef 它可以用在类组件和函数组件中,声明时不能给初始值

    useRef 它只能使用在函数组件中,useRef 它可以在声明时给初始值

    const usernameCreateRef = createRef()
    const usernameUseRef = useRef(null)
    
  • createRef 每次重新渲染时都会创建一个新的 ref 对象,但是类组件中由于生命周期的存在,所以它可以不重新创建(可以将 createRef 写在类组件的构造函数中),但是它在函数组件中就没有这种效果了,它会被重复创建,由此导致性能低下。

    useRef 第1次渲染时创建一个对象之后,再新渲染时,如果发现这个对象已经存在过就不会再创建,它的性能更好一些,在函数组件中推荐使用 useRef。

1.2 createRef在函数组件中的使用

import React, {
    
     createRef, useRef, useEffect, useState } from 'react'
// createRef 它可以用在类组件和函数组件中
// useRef 它只能使用在函数组件中

const App = () => {
    
    
  const usernameRef = createRef()

  return (
    <div>
      <input type="text" ref={
    
    usernameRef} />
      <button
        onClick={
    
    () => {
    
    
          console.log(usernameRef.current.value)
        }}
      >
        获取表单项中的数据
      </button>
    </div>
  )
}

export default App

在这里插入图片描述

1.3 useRef 在函数组件中的使用

上文提到,在函数组件中 createRef 会被重复创建,而 useRef 不会,我们现在来验证一下这个结论:

import React, {
    
     createRef, useRef, useEffect, useState } from 'react'
// createRef每次重新渲染时都会创建一个ref对象,类组件中它有生命周期,所以它也不会重新创建
// useRef第1次渲染时创建一个对象之后,再新渲染时,如果发现这个对象已经存在过不会再创建,它的性能更好一些,在函数组件中推荐使用useRef

const App = () => {
    
    

  // 此两个ref对象就是用来验证ref是否在函数组件中重复创建的区别
  const usernameCreateRef = createRef()
  const usernameUseRef = useRef()

  const [count, setCount] = useState(100)

  // console.log('usernameCreateRef',usernameCreateRef);//null
  // console.log('usernameUseRef',usernameUseRef);//undefined

  // 每次渲染usernameCreateRef都是一个新的空对象,所以每次都要执行条件语句中的代码
  if (!usernameCreateRef.current) {
    
    
    console.log('createRef')
    usernameCreateRef.current = count
  }

  // 如果usernameUseRef在第一次时创建的对象存在则不执行条件语句中的代码
  // 也就是说,如果你第1次创建成功后,第2次渲染时就会使用之前的对象,条件就不成立了,也就不会执行了
  if (!usernameUseRef.current) {
    
    
    console.log('useRef')
    usernameUseRef.current = count
  }

  return (
    <div>
      <h3>useState中的count值:{
    
    count}</h3>
      <h3>createRef:{
    
    usernameCreateRef.current}</h3>
      <h3>useRef:{
    
    usernameUseRef.current}</h3>
      <button
        onClick={
    
    () => {
    
    
          setCount(v => v + 1)
        }}
      >
        +++++++
      </button>
    </div>
  )
}

export default App

在这里插入图片描述

2. React.forwardRef

概述:

如果 ref 对象绑定在自定义类组件中,则可以得到当前自定义类组件的实例,从而可以实现组件通信

如果 ref 直接绑定在自定义函数组件中,则不行,因为函数组件没有实例,所以会报错

在函数组件中,如果你需要绑定 ref,则需要通过它提供的一个顶层 api 方法来增强函数组件从而实现函数组件也可以绑定 ref 对象 ,这个方法就是 React.forwardRef。

使用:

import React, {
    
     useRef, forwardRef } from 'react'

// 使用forwardRef方法完成对于函数组件中绑定ref对象
const Child = forwardRef((props, _ref) => {
    
    
  console.log(_ref)
  return (
    <div>
      <h3 ref={
    
    _ref}>child组件</h3>
    </div>
  )
})

const App = () => {
    
    
  let childRef = useRef()
  return (
    <div>
      <Child ref={
    
    childRef} />
      <button
        onClick={
    
    () => {
    
    
          console.log(childRef.current)
        }}
      >
        ++++
      </button>
    </div>
  )
}

export default App

在这里插入图片描述

上面代码的执行顺序是这样的,首先负父组件中创建的 useRef 对象,会通过自定义属性传入到子组件中,然后子组件中的 forwardRef 函数中的 _ref 参数接收了 ref 对象,并将 Dom 对象的引用传回到父组件中:

在这里插入图片描述

通过 ref 父子组件间可以传值:

import React, {
    
     useRef, forwardRef } from 'react'

// 使用forwardRef方法完成对于函数组件中绑定ref对象
const Child = forwardRef((props, _ref) => {
    
    
  // 利用useRef可以初始化,可以在父组件中初始化数据,然后把值传进子组件中
  console.log(_ref)
  return (
    <div>
      <h3>child组件</h3>
    </div>
  )
})

const App = () => {
    
    
  let childRef = useRef(1000)
  return (
    <div>
      <Child ref={
    
    childRef} />
      <button
        onClick={
    
    () => {
    
    
          childRef.current++
          console.log(childRef.current)
        }}
      >
        ++++
      </button>
    </div>
  )
}

export default App

在这里插入图片描述

3. useImperativeHandle

概述:

使用它可以透传 Ref,因为函数组件没有实例,所以在默认自定义函数组件中不能使用 ref 属性,使用为了解决此问题,react 提供了一个 hook 和一个高阶组件完帮助函数组件能够使用 ref 属性。

useImperativeHandle 集合 useRef 和 forwardRef 来模拟给函数组件绑定ref对象来得到子组件中对外暴露出来的方法或数据。

使用:

import React, {
    
     useRef, forwardRef, useState, useImperativeHandle } from 'react'

const Child = forwardRef((props, _ref) => {
    
    
  // 值
  let [name, setName] = useState('张三')

  // 方法
  const setNameFn = name => setName(name)

  // 对象
  const userRef = useRef()

  // 参数1:就是父组件传过来的ref对象
  // 参数2:回调函数,返回一个对象,穿透给父组件的数据
  useImperativeHandle(_ref, () => {
    
    
    return {
    
    
      // 给父组件传回了
      // 值
      name,
      // 方法
      setNameFn,
      // 对象
      userRef
    }
  })

  return (
    <div>
      <h3>child组件 -- {
    
    name}</h3>
      {
    
    /* 这里的 ref 是子组件自己的 */}
      <input type="text" ref={
    
    userRef} />
    </div>
  )
})

const App = () => {
    
    
  let childRef = useRef()
  return (
    <div>
      <Child ref={
    
    childRef} />
      <button
        onClick={
    
    () => {
    
    
          console.log(childRef.current.name)
          childRef.current.setNameFn(Date.now() + '')
          childRef.current.userRef.current.value = 'abc'
        }}
      >
        ++++
      </button>
    </div>
  )
}

export default App

在这里插入图片描述

上述代码的执行顺序是这样的:

在这里插入图片描述

4. useLayoutEffect

概述:

大部分情况下,使用 useEffect 就可以帮我们处理组件的副作用,但是如果想要同步调用一些副作用,比如对 DOM 的操作,就需要使用 useLayoutEffect,useLayoutEffect 中的副作用会在 DOM 更新之后同步执行。

useEffect 是异步的,而 useLayoutEffect 是同步的。

使用:

import React, {
    
     useLayoutEffect, useEffect } from 'react'

const App = () => {
    
    

  // js事件循环中,同步优先于异步

  // 异步
  useEffect(()=>{
    
    
    console.log('useEffect');
  },[])

  // 同步  如果你是操作dom来说,建议用它
  useLayoutEffect(()=>{
    
    
    console.log('useLayoutEffect');
    document.title = '你好react'
  },[])


  return <div>
    <h3>App组件</h3>
  </div>
}

export default App

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_45605541/article/details/127288942