likes
comments
collection
share

一文彻底掌握ref,createRef,useRef,forwardRef,useImperativeHandle...

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

先有问题再有答案

  1. react中的ref是做什么的
  2. 为什么需要它?
  3. 该如何正确的使用ref?
  4. 官方为什么不提倡过渡使用?
  5. 如何理解useRef?
  6. React.createRef又是什么?
  7. forwardRef起到什么作用?
  8. useImperativeHandle与ref有什么关系?
  9. 你是如何理解ref,useref,forwardRef,useImperativeHandle之间的关系?

一文彻底掌握ref,createRef,useRef,forwardRef,useImperativeHandle...

ref的设计目的

ref(reference)是React中的一个特性,一种特殊的属性,它允许我们访问和操作DOM元素或者Class组件实例。

在React中,我们通常倾向于通过数据流(状态和props)来控制组件的渲染和行为。

但在某些情况下,我们需要 ref来绕过React的虚拟DOM,直接与DOM元素或组件实例进行交互。

在某些情况下,这是必要的。例如 需要直接测量DOM元素的尺寸或位置。需要调用底层DOM API,如scrollHeightscrollIntoView。这些操作通常在React方式之外进行,这就是ref出现的原因

官方为什么不提倡过渡使用

React的设计理念是使用声明式编程和组件化,通过props和state来控制视图,保持单项数据流,这意味着当数据改变时,UI会自动改变,从而降低了理解代码的复杂性和维护代码的困难。

而使用ref则是命令式编程和双向数据流的一种形式,它使得我们可以直接操作DOM元素和组件实例,从而绕过了React的渲染机制,这可能会让整个应用的数据流变得难以理解和预测。尤其是在大型复杂的项目中,过度使用ref可能会导致浏览器性能下降,代码的维护性和可读性也会变差。

因此,尽管ref在某些场景中(例如处理焦点事件,媒体播放等)是不可或缺的,但是React官方并不倡导过度使用ref,而是应该尽可能地用props和state来组织和管理应用的数据流。

需要注意的是,这不是说ref是“坏”的或者“不应该”的使用,而是说在使用ref时需要更加小心和注意,只有在React模式无法解决的情况下,再考虑使用ref。

使用方式

类中使用

class MyComponent extends React.Component {
    constructor(props) {
        super(props);
        this.myNode = React.createRef();
    }

    componentDidMount() {
        // 组件挂载后,可以通过 this.myNode.current 访问 DOM 元素
        console.log(this.myNode.current);
    }

    render() {
        // 使用 createRef 创建的 ref
        return <div ref={this.myNode}>Hello</div>;
    }
}

在类组件中 通常建议使用React.createRef 来创建和管理 ref。

函数中使用

function MyComponent() {
    const myRef = useRef();

    useEffect(() => {
        console.log(myRef.current);
    }, []);

    return <div ref={myRef}>Hello</div>;
}

上面代码中,我们用useRef创建了一个ref,并将其赋值到一个div元素上,在useEffect中获取到该元素。

知识点扫盲

  • 关于ref? ref是React的一个特性,可以用于访问和操作DOM元素或Class组件的实例。

  • React.createRef React.createRef是一个函数,在类组件中使用 ,用于创建ref。这种方式创建的ref被赋给组件或DOM元素的ref属性后,可以在组件的生命周期方法(如componentDidMount)或其他方法中通过this.refName.current访问到实际的DOM元素或组件实例。

  • forwardRef React.forwardRef 是一个函数,用于对 ref进行转发和传递 ,使得父组件可以获取子组件的引用,用于在高阶组件(HOC)中传递ref。因为默认情况下,ref不能被传递,但用此函数包裹后就能使得ref能被直接传到子组件上。

  • useRef useRef是React的一个Hook,用于在函数组件内创建ref。不仅可以用它来引用DOM元素或组件实例,也可以用来在整个组件生命周期内存储和操作可变值。使用useRef创建的ref在整个组件生命周期内保持不变。

  • useImperativeHandle useImperativeHandle是React的一个Hook,用于自定义暴露给父组件的实例值。也就是说,你可以确定父组件可以通过ref调用哪些子组件内部的方法或访问哪些属性。通常,父组件只能访问子组件暴露的DOM元素或定义的组件实例,但useImperativeHandle可以让你控制暴露什么给父组件。

父组件调用子组件的方法

react框架的世界中 操作dom可以有三种方式:

通过ref获取组件的实例

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

const ChildComponent = forwardRef((props, ref) => {
    // 这里通过 useImperativeHandle 关联 ref 和 someMethod 方法
    useImperativeHandle(ref, () => ({
        someMethod: () => {
            console.log('Method from ChildComponent');
        },
    }));

    return <div>Child Component</div>;
});

const ParentComponent = () => {
    const childRef = useRef();

    // 点击按钮时,调用子组件的 someMethod 方法
    const handleClick = () => {
        childRef.current.someMethod();
    };

    return (
        <div>
            {/* 传递 ref 给子组件 */}
            <ChildComponent ref={childRef} />
            <button onClick={handleClick}>Call Child Method</button>
        </div>
    );
};

export default ParentComponent;

在这个例子中,我们在子组件使用 forwardRef 和 useImperativeHandle 创建一个可供父组件调用的方法。然后在父组件中,我们创建一个 ref,并将其传递给子组件。在父组件的 handleClick 方法中,我们调用了子组件的方法。

通过props的回调

父组件可以通过props传递一个回调函数给子组件,子组件内部调用这个函数时,可以将自身的方法或属性作为参数传递给父组件。

import React from 'react';

function ChildComponent({ getMethod }) {
    const childMethod = () => {
        console.log("Child method called!");
    }

    getMethod(childMethod);

    return <div>Child</div>;
}

function ParentComponent() {
  function callChildMethod(method) {
    method();
  }

  return (
    <div>
      <ChildComponent getMethod={callChildMethod} />
    </div>
  );
}

通过全局(没人用过)

import React, { useEffect } from 'react';

function ChildComponent() {
  useEffect(() => {
    window.myMethod = () => {
      console.log("Child method called!");
    }

    return () => {
      window.myMethod = null;
    }
  }, []);

  return <div>Child</div>;
}

function ParentComponent() {
  function callChildMethod() {
    if (typeof window.myMethod === 'function') {
      window.myMethod();
    }
  }

  return (
    <div>
      <button onClick={callChildMethod}>Call Child Method</button>
      <ChildComponent />
    </div>
  );
}

直接使用原生document对象 操作文档流

import React, { useEffect } from "react";

function ChildComponent() {
  return <div id="child">Child</div>;
}

function ParentComponent() {
  function changeChildText() {
    const element = document.getElementById("child");
    if (element) {
      element.textContent = "Child Text Has Been Changed";
    }
  }

  useEffect(() => {
    changeChildText();
  }, []);

  return (
    <div>
      <ChildComponent />
    </div>
  );
}

在这个例子中,当ParentComponent渲染时,会调用 changeChildText 函数,查找id为"child"的元素并修改其文本。这样,即使此元素是在子组件中定义的,也可以被父组件修改。但是,这种方式只有当DOM已经渲染,并且id是唯一的时候才有效,而且不符合React的常规操作。

通常来说,向子组件传递状态和回调函数,然后让子组件自己更新是更好的方式。

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