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中useMemo的使用场景
前端·react.js·前端框架
红绿鲤鱼10 小时前
React-自定义Hook与逻辑共享
前端·react.js·前端框架
loey_ln12 小时前
FIber + webWorker
javascript·react.js
zhenryx13 小时前
前端-react(class组件和Hooks)
前端·react.js·前端框架
老码沉思录17 小时前
React Native 全栈开发实战班 - 性能与调试之打包与发布
javascript·react native·react.js
沉默璇年21 小时前
react中Fragment的使用场景
前端·react.js·前端框架
GISer_Jing1 天前
React渲染流程与更新diff算法
前端·javascript·react.js
老码沉思录1 天前
React Native 全栈开发实战班 - 性能与调试之内存管理
javascript·react native·react.js
yqcoder1 天前
reactflow 中 reactflowprovider 组件作用
前端·javascript·react.js
前端郭德纲1 天前
ReactNative的环境搭建
javascript·react native·react.js