浅谈forwardRef 和 useImperativeHandle

207 阅读2分钟

在 React 中,forwardRef 和 useImperativeHandle 是两个用于处理组件间引用传递的高级 API,常用于函数组件中暴露 DOM 操作或自定义方法给父组件。以下是详细说明和示例:


一、核心概念

  1. forwardRef

    • 作用:将父组件传递的 ref 转发到子组件内部,允许父组件直接访问子组件的 DOM 元素或类组件实例。
    • 适用场景:需要父组件直接操作子组件 DOM(如聚焦输入框)或调用子组件方法时。
  2. useImperativeHandle

    • 作用:自定义暴露给父组件的实例值(如方法或属性),避免直接暴露整个组件实例。
    • 适用场景:与 forwardRef 配合使用,在函数组件中暴露特定方法给父组件。

二、组合使用场景

当需要让父组件:

  1. 直接操作子组件的 DOM 元素(如 focus()
  2. 调用子组件中定义的自定义方法(如 validate()

时,需结合使用这两个 API。


三、完整示例

1. 子组件(CustomInput.js

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

	 

	const CustomInput = forwardRef((props, ref) => {

	  const inputRef = useRef(null);

	 

	  // 自定义暴露给父组件的方法

	  useImperativeHandle(ref, () => ({

	    focus: () => {

	      inputRef.current.focus();

	    },

	    getValue: () => {

	      return inputRef.current.value;

	    },

	    clear: () => {

	      inputRef.current.value = '';

	    }

	  }));

	 

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

	});

	 

	export default CustomInput;

2. 父组件(App.js

	import { useRef } from 'react';

	import CustomInput from './CustomInput';

	 

	function App() {

	  const inputRef = useRef(null);

	 

	  const handleFocus = () => {

	    inputRef.current.focus(); // 调用子组件暴露的 focus 方法

	  };

	 

	  const handleGetValue = () => {

	    const value = inputRef.current.getValue(); // 调用子组件暴露的 getValue 方法

	    alert(`当前值:${value}`);

	  };

	 

	  const handleClear = () => {

	    inputRef.current.clear(); // 调用子组件暴露的 clear 方法

	  };

	 

	  return (

	    <div>

	      <CustomInput ref={inputRef} placeholder="输入内容..." />

	      <button onClick={handleFocus}>聚焦输入框</button>

	      <button onClick={handleGetValue}>获取值</button>

	      <button onClick={handleClear}>清空内容</button>

	    </div>

	  );

	}

	 

	export default App;

四、关键点解析

  1. forwardRef 的作用

    • 将父组件的 ref 转发到子组件的 input 元素
    • 允许父组件通过 ref.current 访问子组件内部元素
  2. useImperativeHandle 的作用

    • 替代直接暴露 inputRef.current,只暴露父组件需要的方法
    • 避免父组件意外修改子组件内部状态
    • 返回的对象定义了父组件可调用的方法
  3. 优势

    • 解耦:父组件不直接依赖子组件实现细节
    • 安全:通过接口暴露方法,而非直接操作 DOM
    • 灵活:可组合多个方法(如示例中的 focus/getValue/clear

五、典型使用场景

  1. 表单组件需要暴露验证方法
  2. 复杂 UI 组件(如自定义下拉菜单)需要暴露打开/关闭方法
  3. 第三方库组件需要封装特定操作
  4. 需要父组件直接控制子组件动画/交互行为时

六、注意事项

  1. 避免过度使用:优先通过 props 驱动状态,ref 操作应作为最后手段
  2. 类型安全:使用 TypeScript 时需定义暴露方法的类型
  3. 性能:直接操作 DOM 可能导致非受控组件行为,需谨慎处理状态同步

通过合理使用这两个 API,可以在保持组件封装性的同时,实现必要的跨组件操作能力。