在 React 中,使用 TypeScript,如何使用 RefForwardingComponent 和 forwardRef 将 ref 传递给自定义组件?

Tob*_*bbe 2 typescript reactjs react-hooks

我正在尝试将 ref 传递给自定义组件(以便我可以将焦点设置到该组件)。

但我不断收到此错误

const RefComp: React.RefForwardingComponent<HTMLInputElement, Props>
Type '{ value: string; onChange: Dispatch<SetStateAction<string>>; ref: MutableRefObject<HTMLInputElement | undefined>; }'
is not assignable to type 'IntrinsicAttributes & Props & { children?: ReactNode; }'.
  Property 'ref' does not exist on type 'IntrinsicAttributes & Props & { children?: ReactNode; }'.ts(2322)
Run Code Online (Sandbox Code Playgroud)

这是我的代码

import * as React from 'react';
import {useRef, useState, RefForwardingComponent, forwardRef} from 'react';

interface Props {
    value: string;
    onChange(event: string): void;
}

const RefComp: RefForwardingComponent<HTMLInputElement, Props> = forwardRef<
    HTMLInputElement,
    Props
>(({value, onChange}, ref) => (
    <input
        value={value}
        onChange={event => onChange(event.target.value)}
        ref={ref}
    />
));

export default function App() {
    const [val, setVal] = useState('');
    const inputRef = useRef<HTMLInputElement>();

    return (
        <div className="App">
            <RefComp value={val} onChange={setVal} ref={inputRef} />
            <p>{val}</p>
        </div>
    );
}
Run Code Online (Sandbox Code Playgroud)

这是一个代码和框 https://codesandbox.io/s/crimson-cdn-klfp5

如果我忽略错误,代码似乎工作正常。所以不知道为什么我得到它......

任何人都可以解释为什么我收到错误,以及我如何解决它?:)

编辑:

正如评论中提到的,你可以通过添加ref到你的道具来解决这个问题。

import * as React from 'react';
import {
    useRef,
    useState,
    RefForwardingComponent,
    forwardRef,
    MutableRefObject,
} from 'react';

interface Props {
    value: string;
    onChange(event: string): void;
    ref?: MutableRefObject<HTMLInputElement | null>;
}

const RefComp: RefForwardingComponent<HTMLInputElement, Props> = forwardRef<
    HTMLInputElement,
    Props
>(({value, onChange}, ref) => (
    <input
        value={value}
        onChange={event => onChange(event.target.value)}
        ref={ref}
    />
));

export default function App() {
    const [val, setVal] = useState('');
    const inputRef = useRef<HTMLInputElement>(null);

    return (
        <div className="App">
            <RefComp value={val} onChange={setVal} ref={inputRef} />
            <p>{val}</p>
        </div>
    );
}
Run Code Online (Sandbox Code Playgroud)

但这感觉不对。现在我们说我们有ref两个作为 props 的一部分,以及作为传递给函数的第二个回调参数forwardRef

如果我这样写,我的问题可能会更清楚

const RefComp: RefForwardingComponent<HTMLInputElement, Props> = forwardRef<
    HTMLInputElement,
    Props
>((props: Props, ref) => (
    <input
        value={props.value}
        onChange={event => props.onChange(event.target.value)}
        ref={ref /* here, props.ref is also available, but wouldn't work */ }
    />
));
Run Code Online (Sandbox Code Playgroud)

编辑2:

这是一个相关的问题

打字稿 RefForwardingComponent 不起作用

编辑 3:

我找到了相关的源代码。https://github.com/DefinitelyTyped/DefinitelyTyped/blob/33f6179e0f25b0ca798ad89a667c0a27ea0c98dd/types/react/index.d.ts#L702

function forwardRef<T, P = {}>(Component: RefForwardingComponent<T, P>): ForwardRefExoticComponent<PropsWithoutRef<P> & RefAttributes<T>>;
Run Code Online (Sandbox Code Playgroud)

因此该forwardRef函数返回一个类型为 的组件ForwardRefExoticComponent<PropsWithoutRef<P> & RefAttributes<T>>。这是一个将类型 propsPropsWithoutRef<P>RefAttributes<T>. 这两种类型也在同一个文件中定义。

据我所知,第一个基本上只是ref从 P 中排除(Props在我的原始示例中)。第二个定义为

interface RefAttributes<T> extends Attributes {
    ref?: Ref<T>;
}
Run Code Online (Sandbox Code Playgroud)

鉴于这些信息,我真的不明白为什么我必须添加ref到我自己的Props. 它真的看起来好像forwardRef应该为我做 - 甚至实际上ref从我自己的Props......

有人可以解释一下这里发生了什么吗?

Tob*_*bbe 7

问题是RefComp. 删除它(让 TypeScript 推断类型)并且代码工作得很好。

import * as React from 'react';
import {useRef, useState, forwardRef} from 'react';

interface Props {
    value: string;
    onChange(event: string): void;
}

const RefComp = forwardRef<HTMLInputElement, Props>(
    ({value, onChange}, ref) => (
        <input
            value={value}
            onChange={event => onChange(event.target.value)}
            ref={ref}
        />
    ),
);

export default function App() {
    const [val, setVal] = useState('');
    const inputRef = useRef<HTMLInputElement>(null);

    React.useEffect(() => {
        if (inputRef.current) {
            inputRef.current.focus();
        }
    }, []);

    return (
        <div className="App">
            <RefComp value={val} onChange={setVal} ref={inputRef} />
            <p>{val}</p>
        </div>
    );
}

Run Code Online (Sandbox Code Playgroud)

https://codesandbox.io/s/quizzical-liskov-bsruh