React - 使用 TypeScript 和函数式组件的 useRef

55

我试图使用useRef从父组件调用子组件方法。

未来,SayHi方法将在子组件中更新钩子状态。不幸的是,我无法处理错误。

行:ref.current.SayHi();

属性“SayHi”不存在于类型“ForwardRefExoticComponent<{ name: string; } & RefAttributes<{ SayHi: () => void; }>>”上。

行:<Child name="Adam" ref={ref}/>

类型“RefObject<ForwardRefExoticComponent<{ name: string; } & RefAttributes<{ SayHi: () => void; }>>>”不能分配给类型“((instance: { SayHi: () => void; } | null) => void) | RefObject<{ SayHi: () => void; }> | null | undefined”。“RefObject<ForwardRefExoticComponent<{ name: string; } & RefAttributes<{ SayHi: () => void; }>>>”不能分配给类型“RefObject<{ SayHi: () => void; }>”。类型“ForwardRefExoticComponent<{ name: string; } & RefAttributes<{ SayHi: () => void; }>>”中缺少属性“SayHi”,但在类型“{ SayHi: () => void; }”中是必需的。

完整的test.tsx文件:

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

const Parent = () => {
  const ref = useRef<typeof Child>(null);
  const onButtonClick = () => {
    if (ref.current) {
      ref.current.SayHi();
    }
  };
  return (
    <div>
      <Child name="Adam" ref={ref}/>
      <button onClick={onButtonClick}>Log console</button>
    </div>
  );
}

const Child = forwardRef((props: {name: string}, ref: Ref<{SayHi: () => void}>)=> {
  const {name} = props;
  useImperativeHandle(ref, () => ({ SayHi }));
  
  function SayHi() { console.log("Hello " + name); }
  
  return <div>{name}</div>;
});

我在这个话题上深切地请求帮助。

3个回答

87

你需要将参考类型从其他地方提取出来:

interface RefObject {
  SayHi: () => void
}

那么只需在两个地方都引用它即可。

const Child = forwardRef((props: {name: string}, ref: Ref<RefObject>)=> {
  const {name} = props;  
  useImperativeHandle(ref, () => ({ SayHi }));
  function SayHi() { console.log("Hello " + name); }

  return <div>{name}</div>;
});
const Parent = () => {
    const ref = useRef<RefObject>(null);
    const onButtonClick = () => {
      if (ref.current) {
        ref.current.SayHi();
      }
    };
    return (
      <div>
        <Child name="Adam" ref={ref}/>
        <button onClick={onButtonClick}>Log console</button>
      </div>
    );
}

非常感谢您提供如此详细的例子。 - undefined

4

只需将您的ref声明替换为此const ref = useRef<{ SayHi: () => void }>(null);


0

useRef<typeof SomeForwardRefComponent>的问题是,它认为forwardRef返回的对象类型将被赋给ref.current。它并不足够智能以查找ref类型。因此,它期望像ref.current.displayName而不是ref.current.focus()等属性。

您可以使用React的ElementRef类型工具提取传递给forwardRef的ref参数的类型,例如useRef<ElementRef<typeof Child>>

import React, { type ElementRef, type Ref, forwardRef, useRef } from 'react'

const Child = forwardRef<SomeElementType, Props>((props, ref) => (
  /* ...render something */
))
// ...or (same thing but with slightly different syntax where you specify ref type):
const Child = forwardRef((props: Props, ref: Ref<SomeElementType>) => (
  /* ...render something */
))

const Parent = () => {
  // ref here has the same type as if you did useRef<SomeElementType>
  const ref = useRef<ElementRef<typeof Child>>(null)
  return (
    <Child ref={ref} ... />
  )
}

这个方法是可行的,但是ElementRef<typeof Child>有点冗长。它基本上只是对你传递给forwardRef的类型参数的一个复杂别名。

如果你从一个你无法控制的库中导入了被forwardRef引用的组件,那么这可能是最好的解决方案。但是,如果你同时控制着父组件和子组件的代码,那么最好的做法可能是定义引用类型并进行导出和导入。例如:

// In `child.tsx`
import React, { forwardRef } from 'react'

export type ChildRefType = SomeElementType
export type ChildProps = { ... }

export const Child = forwardRef<ChildRefType, ChildProps>((props, ref) => (
  /* render something */
))

// In `parent.tsx`
import React, { useRef } from 'react'
import { Child, ChildRefType } from './child'

export const Parent = () => {
  // ref here has the same type as if you did useRef<SomeElementType>
  const ref = useRef<ChildRefType>(null)
  return (
    <Child ref={ref} ... />
  )
}

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