React 中的 React.forwardRef 用法简介

在 React 组件中,有时我们可能需要直接操作子组件的 DOM 元素或者访问子组件的 props。React 16.3 版本引入了 React.forwardRef API,它允许我们创建一个高阶组件,将 ref 属性传递给子组件。这在封装组件库或者需要访问底层 DOM 元素时非常有用。

什么是 React.forwardRef

React.forwardRef 是一个函数,它接受一个组件作为参数,并返回一个新的组件。这个新组件可以将 ref 属性转发给其子组件。

如何使用 React.forwardRef

以下是使用 React.forwardRef 的基本步骤:

  1. 导入 React:确保你已经导入了 React。

    javascript 复制代码
    import React from 'react';
  2. 定义一个组件 :创建一个你想要转发 ref 的组件。

    javascript 复制代码
    const MyComponent = React.forwardRef((props, ref) => {
      // 使用 props 和 ref 渲染你的组件
      return <div ref={ref}>Hello, World!</div>;
    });
  3. 使用 ref :在组件中,你可以通过 ref 参数来访问 DOM 元素或者传递给子组件。

    javascript 复制代码
    // 组件内部使用 ref
    const MyComponentWithRef = React.forwardRef((props, ref) => {
      return (
        <div ref={ref}>
          <input {...props} />
        </div>
      );
    });
  4. 传递 ref :在使用组件时,你可以像使用任何其他组件一样传递 ref

    javascript 复制代码
    // 在组件外部使用 ref
    class App extends React.Component {
      componentDidMount() {
        this.myComponentRef.focus();
      }
    
      render() {
        return <MyComponentWithRef ref={el => this.myComponentRef = el} />;
      }
    }

示例1:封装 input 组件

假设我们想要封装一个 input 组件,并且希望外部能够通过 ref 直接访问到 input 元素。

javascript 复制代码
const InputComponent = React.forwardRef((props, ref) => {
  return (
    <input type="text" ref={ref} {...props} />
  );
});

// 使用封装的 InputComponent
class Form extends React.Component {
  componentDidMount() {
    this.inputRef.focus();
  }

  render() {
    return (
      <form>
        <InputComponent ref={input => this.inputRef = input} name="username" />
        <button type="submit">Submit</button>
      </form>
    );
  }
}

在这个例子中,InputComponent 使用 React.forwardRef 创建,它接收 propsref 作为参数,然后将 ref 传递给 input 元素。在 Form 组件中,我们通过 ref 属性将 ref 传递给 InputComponent,并通过 componentDidMount 生命周期方法来获取对 input 元素的引用。

示例2:带图标和提示的按钮组件

下面,让我们来看一个更高级、复杂的例子,其中 React.forwardRef 用于创建一个可定制的、带有图标和浮窗提示的按钮组件。这个组件将允许父级组件通过 ref 访问按钮元素,并且可以根据需要传递一个图标组件和一个提示信息。

1. 定义图标组件和提示组件

首先,我们定义两个简单的组件:一个用于显示图标,一个用于显示提示信息。

javascript 复制代码
const Icon = ({ name }) => <i className={`icon ${name}`} />; // 假设我们有一个图标类库
const Tooltip = ({ text }) => <span className="tooltip">{text}</span>;

2. 创建可转发 ref 的按钮组件

使用 React.forwardRef 创建一个按钮组件,它接受图标名称和提示信息作为 props,并将 ref 转发给按钮元素。

javascript 复制代码
const CustomButton = React.forwardRef(({ icon, tooltip, children, ...props }, ref) => {
  return (
    <div className="custom-button" ref={ref}>
      {icon && <Icon name={icon} />}
      <button {...props}>
        {children}
        {tooltip && <Tooltip text={tooltip} />}
      </button>
    </div>
  );
});

在这个组件中,我们接收了 icontooltip 作为额外的 props,并且将 ref 传递给了包裹按钮的 div 元素。

3. 使用 useImperativeHandle 控制子组件

假设我们还需要能够通过父组件控制按钮的焦点。我们可以使用 useImperativeHandle 来实现这一点。

javascript 复制代码
const CustomButton = React.forwardRef((props, ref) => {
  const { icon, tooltip, children, ...buttonProps } = props;

  const focusButton = () => {
    if (ref.current) {
      const buttonElement = ref.current.querySelector('button');
      buttonElement.focus();
    }
  };

  useImperativeHandle(ref, () => ({
    focus: focusButton
  }));

  return (
    <div className="custom-button">
      {icon && <Icon name={icon} />}
      <button {...buttonProps}>
        {children}
      </button>
      {tooltip && <Tooltip text={tooltip} />}
    </div>
  );
});

通过 useImperativeHandle,我们给父组件提供了一个 focus 方法,它可以用来聚焦按钮。

4. 使用组件并传递 ref

现在我们可以在父组件中使用 CustomButton 并传递 ref

javascript 复制代码
const App = () => {
  const buttonRef = useRef();

  const handleClick = () => {
    if (buttonRef.current) {
      buttonRef.current.focus(); // 使用通过 useImperativeHandle 提供的方法
    }
  };

  return (
    <div>
      <CustomButton
        icon="icon-edit"
        tooltip="Edit this item"
        onClick={handleClick}
        ref={buttonRef}
      >
        Edit
      </CustomButton>
      // ... 其他代码
    </div>
  );
};

在这个父组件中,我们创建了一个 buttonRef 并将其传递给 CustomButton。我们还定义了一个 handleClick 方法,当点击事件发生时,它将调用 buttonRef.current.focus() 来聚焦按钮。

这个例子展示了如何使用 React.forwardRef 创建一个高级组件,它不仅转发 ref 给子元素,还利用 useImperativeHandle 提供了额外的方法供父组件使用。这在构建具有复杂交互和可定制性的组件时非常有用。通过这种方式,你可以创建出既强大又灵活的组件,满足各种复杂的应用场景。

注意事项

  • 当你使用 React.forwardRef 时,React 会创建一个特殊的跨组件类型的 ref。这意味着你不能在组件定义中直接使用 ref 属性,因为 ref 必须指向一个 DOM 元素或类组件实例。
  • 确保你传递给 React.forwardRef 的组件是函数组件,因为类组件不能被转发 ref

总结

通过使用 React.forwardRef,你可以创建更灵活的组件,允许父组件直接与其子组件的 DOM 元素或 props 交互,这在构建可复用和可组合的 UI 组件时非常有用。

Tips

使用 ForwardRef 的步骤如下:

  1. 导入 React: 开始之前,确保你已经导入了 React。

    javascript 复制代码
    import React from 'react';
  2. 创建一个组件工厂 : 使用 React.forwardRef 创建一个组件工厂,它接受一个组件作为参数。

    javascript 复制代码
    const ForwardRefComponent = React.forwardRef((props, ref) => {
      // ...
    });
  3. 定义组件逻辑: 在组件工厂的函数内,定义你的组件逻辑,包括 JSX 结构和任何需要的逻辑。

    javascript 复制代码
    const ForwardRefComponent = React.forwardRef((props, ref) => {
      return <div {...props} ref={ref}>Some Content</div>;
    });
  4. 使用 ref 参数 : 在组件内部,使用 ref 参数来访问 DOM 元素或者将其传递给子组件。

    javascript 复制代码
    const ForwardRefComponent = React.forwardRef((props, ref) => {
      // 将 ref 传递给子组件
      return <InnerComponent ref={ref} {...props} />;
    });
  5. 使用 useImperativeHandle (可选): 如果你需要给父组件提供对子组件实例的控制,可以使用 useImperativeHandle

    javascript 复制代码
    const ForwardRefComponent = React.forwardRef((props, ref) => {
      useImperativeHandle(ref, () => ({
        // 定义可以被父组件访问的方法
        customMethod: () => {
          // ...
        }
      }));
      // ...
    });
  6. 在父组件中使用 ForwardRefComponent : 在父组件中,你可以通过 ref 属性将引用传递给 ForwardRefComponent

    javascript 复制代码
    class ParentComponent extends React.Component {
      componentDidMount() {
        if (this.myRef.current) {
          // 使用 ref 来访问子组件的 DOM 元素或通过 useImperativeHandle 提供的方法
          this.myRef.current.customMethod();
        }
      }
    
      render() {
        return <ForwardRefComponent ref={el => this.myRef = el} />;
      }
    }
相关推荐
林九生9 小时前
【React】(推荐项目)使用 React、Socket.io、Nodejs、Redux-Toolkit、MongoDB 构建聊天应用程序 (2024)
前端·mongodb·react.js
Redstone Monstrosity14 小时前
美团一面-2
前端·javascript·react.js
zhaoxiangchao14 小时前
node后端react前端简单实例
前端·react.js·前端框架
想做一只快乐的修狗14 小时前
【React】自定义hook函数
javascript·react.js·ecmascript
不cong明的亚子16 小时前
react项目中引入最新版本eslint
react.js·前端框架·eslint
solinger21 小时前
React入门准备
前端·javascript·react.js
疯一样的MEI男子1 天前
React【vite使用模块化css】
前端·css·react.js
哈哈皮皮虾的皮1 天前
react和taro之间的关系
前端·react.js·taro
MeGoodtoo1 天前
react 前端框架中的 三层(service,model,index)具体操作
前端·javascript·react.js
吕彬-前端2 天前
使用vite+react+ts+Ant Design开发后台管理项目(三)
前端·javascript·react.js