useImperativeHandleuseRef 是 React Hooks 中用于处理 DOM 引用和暴露子组件方法的工具。以下是它们的规范使用方法和注意事项。

1. useRef

1.1 基本用法

useRef 用于创建一个可变的引用对象,通常用于访问 DOM 元素或存储可变值。

import React, { useRef, useEffect } from 'react';
 
function MyComponent() {
  const inputRef = useRef(null);
 
  useEffect(() => {
    // 聚焦输入框
    inputRef.current.focus();
  }, []);
 
  return <input ref={inputRef} type="text" />;
}

1.2 使用场景

  • 访问 DOM 元素:通过 ref 属性绑定到 DOM 元素。
  • 存储可变值:用于存储不需要触发重新渲染的值(如定时器 ID)。

1.3 注意事项

  • useRef.current 属性是可变的,修改它不会触发组件重新渲染。
  • 不要在渲染过程中修改 ref.current,这会导致不可预测的行为。

2. useImperativeHandle

2.1 基本用法

useImperativeHandle 用于自定义暴露给父组件的实例值或方法。通常与 forwardRef 一起使用。

import React, { useRef, useImperativeHandle, forwardRef } from 'react';
 
const ChildComponent = forwardRef((props, ref) => {
  const inputRef = useRef(null);
 
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    },
    getValue: () => {
      return inputRef.current.value;
    },
  }));
 
  return <input ref={inputRef} type="text" />;
});
 
function ParentComponent() {
  const childRef = useRef(null);
 
  const handleClick = () => {
    childRef.current.focus(); // 调用子组件的方法
    console.log(childRef.current.getValue()); // 获取子组件的值
  };
 
  return (
    <div>
      <ChildComponent ref={childRef} />
      <button onClick={handleClick}>Focus Input</button>
    </div>
  );
}

2.2 使用场景

  • 暴露子组件方法:父组件可以通过 ref 调用子组件的方法。
  • 封装复杂逻辑:将子组件的内部逻辑封装,只暴露必要的接口。

2.3 注意事项

  • useImperativeHandle 必须与 forwardRef 一起使用。
  • 避免过度暴露子组件的内部状态或方法,保持组件的封装性。

3. 综合使用示例

以下是一个结合 useRefuseImperativeHandle 的完整示例:

import React, { useRef, useImperativeHandle, forwardRef } from 'react';
 
// 子组件
const CustomInput = forwardRef((props, ref) => {
  const inputRef = useRef(null);
 
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    },
    clear: () => {
      inputRef.current.value = '';
    },
    getValue: () => {
      return inputRef.current.value;
    },
  }));
 
  return <input ref={inputRef} type="text" placeholder="Enter something" />;
});
 
// 父组件
function App() {
  const inputRef = useRef(null);
 
  const handleFocus = () => {
    inputRef.current.focus();
  };
 
  const handleClear = () => {
    inputRef.current.clear();
  };
 
  const handleLogValue = () => {
    console.log('Input Value:', inputRef.current.getValue());
  };
 
  return (
    <div>
      <CustomInput ref={inputRef} />
      <button onClick={handleFocus}>Focus Input</button>
      <button onClick={handleClear}>Clear Input</button>
      <button onClick={handleLogValue}>Log Value</button>
    </div>
  );
}
 
export default App;

4. 最佳实践

  1. 避免滥用 useImperativeHandle:尽量通过 props 和状态管理组件间的通信,只有在必要时才使用 useImperativeHandle
  2. 保持 ref 的单一职责ref 应该只用于访问 DOM 或暴露必要的子组件方法,避免存储复杂状态。
  3. 类型安全:在使用 TypeScript 时,为 ref 和暴露的方法定义明确的类型。

通过以上规范,可以更清晰、高效地使用 useRefuseImperativeHandle,提升代码的可维护性和可读性。