React 高级概念之Ref

Ref 的功能强大,通过它能够让组件与 DOM 元素或类组件与其父级之间建立直接联系。总体而言,使用ref 出于以下3个目的:

  • 访问 DOM 元素
  • 访问组件的实例
  • 将它作为 mutable 数据的存储中心

创建 ref

有两种方式创建 ref,分别为 React.createRef 和 useRef。useRef 是一种 React Hook,只能在函数组件中使用,更多的 Hooks 在后续章节中介绍。React.createRef 的使用位置不限,但不要在函数组件中使用它,如果在函数组件中用它创建 ref,那么函数组件每一次重新渲染都会创建新的 ref。下面的代码显示了 React.createRef、useRef 和 ref 的数据类型:

// React.createRef 的类型
function createRef<T>(): RefObject<T>;

// useRef 的类型
function useRef<T>(initialValue: T|null): RefObject<T>;
function useRef<T>(initialValue: T): MutableRefObject<T>;
function useRef<T = undefined>(): MutableRefObject<T | undefined>;

// ref 的类型
interface MutableRefObject<T> {
    current: T;
}

interface RefObject<T> {
    readonly current: T | null;
}

ref 由 React.createRef 或 useRef 函数返回,从上述代码可以看出 ref 有两种数据类型,分别是 MutableRefObject 和 RefObject,这两种类型都有 current 字段,类型参数 T 用于注释current 的类型。下面的代码演示创建 ref:

// current 字段必须是 div 元素或者null
React.createRef<HTMLDivElement>()
// current 字段必须是布尔值或者undefined
useRef<boolean>()

// current 字段必须是 input元素或者null
useRef<HTMLInputElement>(null)

访问 DOM 元素

要想通过 ref 访问 DOM 元素,必须将 ref 绑定到浏览器内置的组件上,等组件装载之后使用ref.current 字段访问 DOM 元素。下面的代码演示用 ref 访问 input 元素使它获得焦点:

function RefUse() {
    const inputRef = useRef<HTMLInputElement>(null)

    const onClick = () => {
        if (inputRef.current) {
     // 调用DOM节点上的方法
       inputRef.current.focus()
      }
    }
    return (
        <div>
            <input ref={inputRef}/>
            <button onClick={onClick}>点击按钮让input获取焦点</button>
        </div>
    )
}

上述代码用 useRef 创建 ref,将 ref 属性绑定到 input 元素上,底层的 input 元素被赋值给 inputRef.current,使用 inputRef.current 直接操作 DOM 元素。

访问组件的实例

将 ref 属性绑定到类组件上,通过 ref.current 能访问到类组件的实例,由于函数组件没有实例,所以不能在函数组件上绑定 ref 属性。下面的代码演示在父组件中通过 ref 访问子组件实例,调用子组件的方法。

function RefUse() {
    const childRef = useRef<ChildCom>(null)
    const onClickToChangeState = () => {
        if (childRef.current) {
           // 直接调用子组件的方法
            childRef.current.changeCount()
        }
    }

    return (
        <div>
         // ChildCom 是类组件
         <ChildCom ref={childRef}/>
         <button onClick={onClickToChangeState}>在父组件中改变子组件的count</button>
        </div>
    )
}

上述代码通过 ref 使 RefUse 组件与 ChildCom 组件直接联系,在 RefUse 中调用 ChildCom 的实例方法修改组件的状态。

提示:上述两个示例都在函数组件中用useRef创建ref,如果在类组件中用React.createRef创建ref用法一样。

将 ref 当作 mutable 数据的存储中心

将 ref 当作 mutable 数据的存储中心,使用场景主要是函数组件,这是因为函数组件每一次重新渲染都会执行函数体,使函数体中的各个变量重新创建。如果函数体中声明了一些只用于缓存数据,不导致组件重新渲染的变量,将这些数据放在 ref 中能避免它们被反复创建。将 ref 当作 mutable 数据的存储中心,示例代码如下:

interface cache {
    updateCount: number
}

function RefUse() {
   // 创建mutable数据的存储中心
   const mutableRef = useRef<cache>({updateCount: 0})
   const printCount = () => {
     if (mutableRef.current.updateCount < 3) {
         mutableRef.current.updateCount ++
     } else {
        console.log('次数:'+mutableRef.current.updateCount)
     }
   }

return (// something)
}

将 ref 当作 mutable 数据的存储中心,不需要将它绑定到 React element 上,创建之后能直接使用,修改 mutableRef.current 上的值,组件不会重新渲染。

总结

Ref 的功能很强大,但不要滥用。在这里回顾一下使用 ref 的 3 个目的,第 1 个目的:访问 DOM 元素。请记住 React 基于数据驱动,也可以理解为基于状态驱动,在 React 程序中不推荐直接访问 DOM 元素;第2个目的:访问组件实例。通过 ref 在父组件中获得子组件的实例,让父子组件建立直接联系,这会让状态变更变得混乱,父子组件之间的交互应该通过 props 进行,遵循单向数据流原则;第3个目的:在函数组件中创建 ref,将它作为 mutable 数据的存储中心,这有它的用武之地,但在类组件中大可不必。

原文链接:https://juejin.cn/post/7243435843146498107 作者:何遇er

(1)
上一篇 2023年6月12日 上午10:00
下一篇 2023年6月12日 上午10:10

相关推荐

发表回复

登录后才能评论