返回

React-hook-form 'ref'错误解决指南:详解与最佳实践

javascript

“'ref' is specified more than once” 错误解决指南

当你在使用 react-hook-form 时,特别是结合 useController 以及 useRef 时,可能会遇到 TypeScript 报错,提示 'ref' is specified more than once, so this usage will be overwritten.ts(2783)。 这通常表示你在同一个组件中,同时通过 ref prop 和 useController 试图给同一个 HTML 元素赋予 ref,从而导致 ref 被覆盖。

问题根源分析

问题的核心在于,React 的 ref 属性只能接收一个 ref 值。当组件既使用了 useController 返回的 inputProps,又通过 ref prop 传递了 useRef 创建的 ref 时,就会产生冲突。因为 useController 内部也可能处理或需要 ref 来管理对应的表单控件状态。这种冲突导致 Typescript 提示“多次指定 'ref'”,这是因为 useController 和你自己声明的 ref 会同时被应用于同一 DOM 元素。

解决方案:放弃手动 ref

原理

最直接的解决办法是放弃直接给 input 元素传递 useRef 创建的 ref,而完全依赖 react-hook-formuseController 的机制进行元素状态的管理。这种方式依赖 react-hook-form 自动维护 DOM 的引用。

操作步骤

  1. 移除 HTML input 标签上的 ref={inputRef}

  2. 检查你的代码中是否存在通过 inputRef.current 获取input DOM元素的方法调用。通常这些调用是用来处理input的值或进行其他直接DOM操作的,需要寻找相应的替换方法,
    例如可以使用 react-hook-form 中的 getValue 方法来获取表单值。

     // 之前的代码
     const addTagInPost = () => {
     const inputValue = inputRef.current?.value.trim();
       ...
    }
    
    

// 修改后的代码(假设“text”是你表单字段名称):
const { control, getValues} = useForm(...)
const addTagInPost = () => {
const inputValue = getValues('text').trim()
...
}

3. 删除无用的  `const inputRef = useRef<HTMLInputElement>(null);` 声明。

   **代码示例:** 
   ```tsx
      // 修改之前

     const inputRef = useRef<HTMLInputElement>(null);
      ...
    <input
          type="text"
          ref={inputRef}
          placeholder="Что нового?"
          {...inputProps} // from useController
        />


        // 修改之后

     ...
        <input
          type="text"
          placeholder="Что нового?"
          {...inputProps}
         />

这种方案利用了 react-hook-form 和 useController 来管理表单输入元素的值,并完全避免手动管理 ref 可能带来的冲突。这会是首选解决方案,因为它更加符合 react-hook-form 的最佳实践。

解决方案:使用 useImperativeHandle 将 ref 传递给父组件(进阶方法,非必须)

原理

如果你确需要父组件能够访问某个input元素的 ref, 你可以通过React.forwardRefuseImperativeHandle 实现自定义的 Ref 转发。虽然这不是常见场景,但在有父子组件交互需要特定 ref 的情况下,该方法很有用。
useImperativeHandle 允许父组件传递一个ref到当前组件,并由当前组件内部指定 ref 指向的内容(可以是元素节点或对象)。

操作步骤

  1. 使用 React.forwardRef 包裹组件 : 将需要使用 ref 的组件用 React.forwardRef 包裹起来, 将 ref 参数添加到你的组件函数定义里。
  2. 使用 useImperativeHandle 自定义 ref 的暴露 : 使用 useImperativeHandle,在组件内部配置如何与父组件的 ref 交互,控制父组件 ref 指向什么,它接受两个参数,第一个是你的ref对象,第二个是回调函数。回调函数需要返回一个对象,这个对象中的属性可以被父组件通过ref进行调用。

代码示例:

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

interface MyInputProps {
  name:string;
}

const MyInput =  forwardRef<HTMLInputElement,MyInputProps>(({ name }, ref) => {
   const inputRef = useRef<HTMLInputElement>(null)

   useImperativeHandle(ref, () => inputRef.current!)

 return <input name={name} ref={inputRef} />

});

 const ParentComponent = ()=> {
    const inputRefFromParent = useRef<HTMLInputElement>(null)

   const handleClick = ()=> {
      console.log("inputRef value:", inputRefFromParent.current?.value)
   }


      return (
        <>
          <MyInput name='userName' ref={inputRefFromParent} />
         <button onClick={handleClick}> get input value </button>
        </>
        )
  }


export default ParentComponent;

这个解决方案的价值在于当你需要在父组件里操作子组件(此例中指 <input /> 元素)的 ref,或者对父组件暴露特定的控制函数时。如果仅仅是为了避免 “ref is specified more than once” 错误,可以优先使用第一个方案:删除多余的 ref。

安全建议

在使用 ref 和直接操作 DOM 元素时,始终要注意潜在的安全风险,尤其是涉及用户输入时。考虑以下方面:

  • 输入验证 : 在使用 ref 读取用户输入后,始终执行输入验证,防止 XSS 攻击或其他安全漏洞。
  • 避免 DOM 操作 : 尽量减少对 DOM 的直接操作,使用 react-hook-form 和 React 状态管理来代替手动修改 DOM。

使用正确的 react-hook-form 方式可以避免此类错误,同时简化你的代码。请根据实际需求选择最适合你的解决方案。