likes
comments
collection
share

React基础篇——ref&forwardRef

作者站长头像
站长
· 阅读数 40

何时使用ref

通常,当你的组件需要“跳出” React 并与外部 API 通信时,你会用到 ref —— 通常是不会影响组件外观的浏览器 API。以下是这些罕见情况中的几个:

  • 存储 timeout ID
  • 存储和操作 DOM 元素
  • 存储不需要被用来计算 JSX 的其他对象。

如果你的组件需要存储一些值,但不影响渲染逻辑,请选择 ref。

注意

  • 在函数组件上不能使用ref 函数组件没有实例,但是在函数组件内部是可以使用的
  • ref在类组件上得到类的实例,
  • ref在dom元素上得到dom的实例

如何产生ref

在react中有多种产生ref的方法

  • 通过 React.createRef()
  • 通过 useRef()
  • 通过一个回调函数 (ref) => {}

类组件中使用ref

随着hook的流行,虽然类组件几乎不会在项目中使用,但是还是可以了解下 ref的使用

createRef()

class 组件 中调用 createRef 来声明一个 ref

createRef 返回一个对象,该对象只有一个属性:

  • current:初始值为 null,你可以稍后设置为其他内容。如果你把 ref 对象作为 JSX 节点的 ref 属性传递给 React,React 将设置其 current 属性。

注意事项 

  • createRef 总是返回一个 不同的 对象。这相当于你自己编写了 { current: null }
  • 在函数组件中,你可能想要使用 useRef,因为它始终返回相同的对象。
  • const ref = useRef() 等同于 const [ref, _] = useState(() => createRef(null))

使用

import { Component, createRef } from 'react';  
class Form extends Component {  
    inputRef = createRef();  
}

如果你现在将 ref={this.inputRef} 传递给 JSX 中的 <input>,React 将把 input 的 DOM 节点赋值给 this.inputRef.current

import { Component, createRef } from 'react';

export default class Form extends Component {
  inputRef = createRef();

  handleClick = () => {
    this.inputRef.current.focus();
  }

  render() {
    return (
      <>
        <input ref={this.inputRef} />
        <button onClick={this.handleClick}>
          聚焦这个输入框
        </button>
      </>
    );
  }
}

替代方案

从使用 createRef 的 class 组件迁移到使用 useRef 的函数组件

export const Form = () => {
    const inputRef = useRef(null)
    const handleClick = () => {
        inputRef.current.focus()
    }
    return <>
        <input ref={inputRef}/>
        <button onClick={handleClick}>
            聚焦这个输入框
        </button>
    </>
}

为什么有了 createRef 还要设计 useRef

   export const App = () => {
   const [count,setCount]= useState(0)
    const divRef1 = useRef(null)
    const divRef2 = React.createRef()
    
    console.log('useRef =====>',divRef1)
    console.log('createRef =====>',divRef2)
    
    return <>
        <div ref={divRef1}>useRef</>
        <div ref={divRef2}>createRef</div>
        <button onClick={() => {setCount(count + 1)}>
            点击
        </button>
    </>
} 

第一次渲染 React基础篇——ref&forwardRef

点击更改count重新渲染时

React基础篇——ref&forwardRef

在上述例子中,可以看到 useRef不会在视图更新时重新创建ref对象 但是createRef 每次更新都会创建新的ref对象

函数组件中使用ref

useRef()

使用

import React, { useRef, useState, useEffect } from "react";

function RefCom() {
  const divRef1 = useRef(null);
  const handleClick = () => {
    console.log(divRef1)
  }
  return (
    <>
      <div ref={divRef1}>useRef</div>
      <button
        onClick={() => {handleClick}
      >
       点击
      </button>
    </>
  );
}

export default RefCom;
  • 在你的组件内,调用 useRef Hook 并传入你想要引用的初始值作为唯一参数。例如,这里的 ref 引用的值是“null”:
  • useRef 返回一个这样的对象:
{ 

  current: null // 你向 useRef 传入的值

}
  • 你可以用 ref.current 属性访问该 ref 的当前值。这个值是有意被设置为可变的,意味着你既可以读取它也可以写入它。就像一个 React 追踪不到的、用来存储组件信息。

特别之处

ref 像 state 一样,你可以让它指向任何东西:字符串、对象,甚至是函数。与 state 不同的是,ref 是一个普通的 JavaScript 对象,具有可以被读取和修改的 current 属性。

注意,组件不会在每次递增时重新渲染。 与 state 一样,React 会在每次重新渲染之间保留 ref。但是,设置 state 会重新渲染组件,更改 ref 不会!

ref 和 state 的不同之处

refstate
useRef(initialValue)返回 { current: initialValue }useState(initialValue) 返回 state 变量的当前值和一个 state 设置函数 ( [value, setValue])
更改时不会触发重新渲染更改时触发重新渲染。
可变 —— 你可以在渲染过程之外修改和更新 current 的值。“不可变” —— 你必须使用 state 设置函数来修改 state 变量,从而排队重新渲染。
你不应在渲染期间读取(或写入) current 值。你可以随时读取 state。但是,每次渲染都有自己不变的 state 快照
import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  function handleClick() {
    setCount(count + 1);
  }

  return (
    <button onClick={handleClick}>
      你点击了 {count} 次
    </button>
  );
}
import { useRef } from 'react';

export default function Counter() {
  let countRef = useRef(0);

  function handleClick() {
    // 这样并未重新渲染组件!
    countRef.current = countRef.current + 1;
  }

  return (
    <button onClick={handleClick}>
      你点击了 {countRef.current} 次
    </button>
  );
}

ref数据的更新无法触发组件的重新渲染

ref转发

  1. 参数,传递的是函数组件,不能是类组件,并且,函数组件需要有第二个参数来得到ref
  2. 返回值,返回一个新的组件

将dom节点暴露给父组件

    const App = () => {
        const ref = useRef()
        /* 如果不使用forwardRef ,这句代码就会报错 */
        
        const clickHandle = () => {
       /* ref.current:会得到DivDom的实例 */
            consle.log(ref)
        }
        return <>
            <Com ref={ref}></Com>
            <button>点击</button>
            </>
    }
    
    Const Com = forwardRef((props,ref) =>{
        return <div ref={ref}>DivDom</div>
    })

暴露出一个对象,而不是Dom节点实例

将收到的 ref 传递给 useImperativeHandle 并指定你想要暴露给 ref 的值:

 const App = () => {
        const ref = useRef()
      
        const clickHandle = () => {
            consle.log(ref)
        }
        
        /* ref.current 会指向 useImperativeHandle 返回的对象*/
        return <>
            <Com ref={ref}></Com>
            <button>点击</button>
            </>
    }
    
    Const Com = forwardRef((props,ref) =>{
        const inputRef = useRef(null);
        const innerFuncHandle = () => {
            console.log("Com组件中的方法")
        }
        useImperativeHandle(ref, () => {  
            return { 
                focus() {  
                    inputRef.current.focus();  
                },  
                scrollIntoView() {  
                    inputRef.current.scrollIntoView();  
                },  
                innerFuncHandle
            };  

        }, []);  
        return <input ref={inputRef}/>
    })

下一篇分享下在使用react组件时的一些优化点

转载自:https://juejin.cn/post/7251786381483114551
评论
请登录