Skip to content

forwardRef 有什么作用?

在 React 的生态系统中,forwardRef 是一个强大的工具,尤其是当你需要在组件之间传递 ref 时。在本文中,我们将深入探讨 forwardRef 的各种用法。

什么是 forwardRef?

forwardRef 是一个函数,它接收一个渲染函数作为参数。这个渲染函数接收 props 和 ref 作为参数,并返回一个 React 节点。

js
React.forwardRef((props, ref) => {
  return <div ref={ref} />;
});
React.forwardRef((props, ref) => {
  return <div ref={ref} />;
});

为什么要使用 forwardRef?

在 React 中,ref 是一个特殊的属性,它可以用来引用组件或 DOM 元素。当你在组件中使用 ref 时,你可以通过 ref.current 来访问组件或 DOM 元素。

用法

1. 传递 ref 给 DOM 元素

forwardRef 的主要功能是将引用 (ref) 从父组件转发到子组件。这对于访问子组件的 DOM 元素非常有用。

demo 代码
jsx
import React, { forwardRef, useRef } from "react";
import { Card, Button } from "antd";

const FancyButton = forwardRef((props, ref) => (
  <Button {...props} ref={ref}>
    {props.children}
  </Button>
));

function App() {
  const ref = useRef();
  return (
    <Card title="案例 demo">
      <FancyButton
        ref={ref}
        onClick={() => {
          // 更改按钮字体的随机颜色
          ref.current.style.color = `#${Math.floor(
            Math.random() * 16777215
          ).toString(16)}`;
          // 更改按钮背景的随机颜色
          ref.current.style.background = `#${Math.floor(
            Math.random() * 16777215
          ).toString(16)}`;
        }}
      >
        Click me!
      </FancyButton>
    </Card>
  );
}

export default App;
import React, { forwardRef, useRef } from "react";
import { Card, Button } from "antd";

const FancyButton = forwardRef((props, ref) => (
  <Button {...props} ref={ref}>
    {props.children}
  </Button>
));

function App() {
  const ref = useRef();
  return (
    <Card title="案例 demo">
      <FancyButton
        ref={ref}
        onClick={() => {
          // 更改按钮字体的随机颜色
          ref.current.style.color = `#${Math.floor(
            Math.random() * 16777215
          ).toString(16)}`;
          // 更改按钮背景的随机颜色
          ref.current.style.background = `#${Math.floor(
            Math.random() * 16777215
          ).toString(16)}`;
        }}
      >
        Click me!
      </FancyButton>
    </Card>
  );
}

export default App;

在这个例子中,FancyButton 组件使用 forwardRef 来接收一个 ref 并将其传递给内部的 Button 组件。由于 ref 已经被转发到 Button 组件,我们可以直接通过 ref.current 访问到 DOM 元素。在 App 组件中,当按钮被点击时,我们使用 ref.current 来更改按钮的字体颜色和背景色。

2. 在高阶组件中使用 forwardRef

forwardRef 也可以在高阶组件中使用。在这种情况下,ref 参数不会作为 props 传递给组件,而是作为第二个参数传递给渲染函数。

demo 代码
jsx
import React, { forwardRef } from "react";
import { Card, Button } from "antd";

// 高阶组件,为组件添加一个边框
function withBorder(WrappedComponent) {
  const WithBorder = forwardRef((props, ref) => {
    // TODO:先执行
    return (
      <div style={{ border: "2px solid blue" }}>
        <WrappedComponent ref={ref} {...props} />
      </div>
    );
  });

  return WithBorder;
}

// 原始组件
const SimpleButton = forwardRef((props, ref) => {
  // TODO:后执行
  return <button ref={ref} style={{ width: "100%" }} {...props} />;
});

// 使用高阶组件包裹原始组件
const ButtonWithBorder = withBorder(SimpleButton);

function App() {
  const ref = React.useRef();

  return (
    <Card title="案例 demo">
      <ButtonWithBorder ref={ref} />
      <Button
        onClick={() => {
          // 随机文本
          ref.current.innerText = Math.random().toString(36).slice(-8);
        }}
        style={{ marginTop: 16 }}
      >
        Change Text
      </Button>
    </Card>
  );
}

export default App;
import React, { forwardRef } from "react";
import { Card, Button } from "antd";

// 高阶组件,为组件添加一个边框
function withBorder(WrappedComponent) {
  const WithBorder = forwardRef((props, ref) => {
    // TODO:先执行
    return (
      <div style={{ border: "2px solid blue" }}>
        <WrappedComponent ref={ref} {...props} />
      </div>
    );
  });

  return WithBorder;
}

// 原始组件
const SimpleButton = forwardRef((props, ref) => {
  // TODO:后执行
  return <button ref={ref} style={{ width: "100%" }} {...props} />;
});

// 使用高阶组件包裹原始组件
const ButtonWithBorder = withBorder(SimpleButton);

function App() {
  const ref = React.useRef();

  return (
    <Card title="案例 demo">
      <ButtonWithBorder ref={ref} />
      <Button
        onClick={() => {
          // 随机文本
          ref.current.innerText = Math.random().toString(36).slice(-8);
        }}
        style={{ marginTop: 16 }}
      >
        Change Text
      </Button>
    </Card>
  );
}

export default App;

在这个例子中,我们创建了一个 withBorder 高阶组件,它为传入的组件添加了一个蓝色的边框。在 App 组件中,我们创建了一个 ref 并将其传递给 ButtonWithBorder。然后,我们可以使用这个 ref 来访问 SimpleButton 的 DOM 元素,并调用其 innerText 更改按钮的文本。

3. 与 useImperativeHandle 配合使用

forwardRef 还可以与 useImperativeHandle 配合使用。useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。

demo 代码
jsx
import React, { useRef, useImperativeHandle, forwardRef } from "react";
import { Card, Button, Input } from "antd";

const ForwardedInput = forwardRef((props, ref) => {
  const inputRef = useRef();

  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    },
  }));

  return <Input ref={inputRef} {...props} />;
});

function App() {
  const inputRef = useRef();

  return (
    <Card title="案例 demo">
      <ForwardedInput ref={inputRef} placeholder="点击按钮试试!" />
      <Button
        onClick={() => inputRef.current.focus()}
        type="primary"
        style={{ marginTop: 16 }}
      >
        Focus Input
      </Button>
    </Card>
  );
}

export default App;
import React, { useRef, useImperativeHandle, forwardRef } from "react";
import { Card, Button, Input } from "antd";

const ForwardedInput = forwardRef((props, ref) => {
  const inputRef = useRef();

  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    },
  }));

  return <Input ref={inputRef} {...props} />;
});

function App() {
  const inputRef = useRef();

  return (
    <Card title="案例 demo">
      <ForwardedInput ref={inputRef} placeholder="点击按钮试试!" />
      <Button
        onClick={() => inputRef.current.focus()}
        type="primary"
        style={{ marginTop: 16 }}
      >
        Focus Input
      </Button>
    </Card>
  );
}

export default App;

总结

forwardRef 提供了一种在 React 组件中传递 ref 的强大方法。通过上述示例,我们可以看到它在各种场景中的应用,从基本的函数组件到高阶组件,再到与其他 React Hooks 的结合使用。掌握 forwardRef 可以帮助你更好地管理组件间的交互和引用,从而提高应用的灵活性和可维护性。