当手动设置current时,我应该使用哪种typescript类型与useRef()钩子一起使用?

158

如何在Typescript中使用React ref作为可变实例?当前的属性似乎被定义为只读。

我正在使用React + Typescript开发一个库,与非由React渲染的输入字段交互。我想捕获对HTML元素的引用,然后将React事件绑定到它上面。

  const inputRef = useRef<HTMLInputElement>();
  const { elementId, handler } = props;

  // Bind change handler on mount/ unmount
  useEffect(() => {
    inputRef.current = document.getElementById(elementId);
    if (inputRef.current === null) {
      throw new Exception(`Input with ID attribute ${elementId} not found`);
    }
    handler(inputRef.current.value);

    const callback = debounce((e) => {
      eventHandler(e, handler);
    }, 200);

    inputRef.current.addEventListener('keypress', callback, true);

    return () => {
      inputRef.current.removeEventListener('keypress', callback, true);
    };
  });

它生成编译器错误:语义错误 TS2540:无法分配给只读属性“current”。

我还尝试过const inputRef = useRef<{ current: HTMLInputElement }>(); 这导致了以下编译器错误:

Type 'HTMLElement | null' is not assignable to type '{ current: HTMLInputElement; } | undefined'.

  Type 'null' is not assignable to type '{ current: HTMLInputElement; } | undefined'.

我认为 HTMLInputElement 是正确的,但是 inputRef 应该最初设置为 null,使用 useRef<HTMLInputElement>(null) - dev
我也这么认为。如果在React的渲染过程中捕获了ref - <input ref={myRef} /> - 那么设置myRef.current = ...就可以起作用。 - JPollock
1
这可能会有所帮助:https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31065#issuecomment-446660394,特别是`ref7`。 - dev
4个回答

383

是的,这是类型编写方式的一个怪癖:

function useRef<T>(initialValue: T): MutableRefObject<T>;
function useRef<T>(initialValue: T|null): RefObject<T>;

如果初始值包含null,但指定的类型参数不包含它,则它将被视为不可变的RefObject
当您执行useRef<HTMLInputElement>(null)时,您会遇到这种情况,因为T被指定为HTMLInputElement,而null被推断为HTMLInputElement | null
您可以通过执行以下操作来解决此问题:
useRef<HTMLInputElement | null>(null)

那么 T 就是 HTMLInputElement | null,它与第一个参数的类型匹配,因此您会触发第一个重载并获得可变的 ref。


4
谢谢!这让我的代码得以编译。我使用了const inputRef = useRef<HTMLElement | null>(null);来避免编译器的错误提示,因为inputRef.current = document.getElementById(elementId);的原因。不确定是否值得进行修改。 - JPollock
2
如果您不需要从ref获取任何特定于输入的属性,那么我会像您所做的那样扩大您注释ref的类型;如果您确实需要输入特定属性,则在分配之前可能会执行getElementById(elementId) as HTMLInputElement - Retsam
作为提醒,我发现这不能使用 createRef 而必须使用 useRef hook。也许有其他方法,但我没有找到。 - rt_
似乎无法处理在 useEffect 中分配的变量 const shaderMat: React.RefObject<THREE.ShaderMaterial|null> = useRef(null);,因为 current 是只读的,所以我无法执行 shaderMat.current = mat; - Ambroise Rabier
2
@AmbroiseRabier,你已经明确注释了shaderMat是一个不可变类型的RefObject,所以在useEffect中它不会让你进行修改。你需要使用useRef的类型参数来控制类型,而不是在变量本身上进行注释。 - Retsam
显示剩余2条评论

16

as关键字。

你可以在输入组件中像这样使用它。

const inputRef = useRef() as MutableRefObject<HTMLInputElement>;

10

我通过搜索如何在使用setTimeoutsetInterval时,使用Typescript来输入useRef,进入了这个问题。被接受的答案帮助我解决了这个问题。

你可以像这样声明超时/间隔

const myTimeout = useRef<ReturnType<typeof setTimeout> | null>(null)

要清除并重新设置它,您可以像往常一样进行:

const handleChange = () => {
  if (myTimeout.current) {
    clearTimeout(myTimeout.current)
  }
  myTimeout.current = setTimeout(() => {
    doSomething()
  }, 500)
}

如果您在Node环境或浏览器中运行,打字功能都可以工作。


这个答案更适合于 https://dev59.com/WVEG5IYBdhLWcg3wduYf,那里有类似的答案。 - Pavlo Zhukov

0
你必须像这样写代码:
const inputRef = useRef<HTMLInputElement>(null);

当你需要使用它时,你必须这样写:
inputRef.current?.focus();

你的回答可以通过提供更多支持性信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人能够确认你的回答是否正确。你可以在帮助中心找到关于如何撰写好回答的更多信息。 - Community

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接