React 嵌套的forwardRef

Val*_*rio 7 typescript reactjs react-hooks react-hook-form

在我的 React(w/ typescript)应用程序中,我使用react-hook-form 创建了一个表单来管理它的所有逻辑。

然后我用一些 css 和其他东西自定义了 select 元素。但是,为了问题简单起见,这里是准系统组件:

import { forwardRef } from 'react';

type Props = React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLSelectElement>, HTMLSelectElement>;

const Select = forwardRef<HTMLSelectElement, Props>((props, ref) => {
    return (
        <select ref={ref} {...props}>
            <option value="">...</option>
            {props.children}
        </select>
    );
});

export default Select;

Run Code Online (Sandbox Code Playgroud)

然后,我定义了另一个“专门化”前一个组件的组件,如下所示:

import { forwardRef } from 'react';

import Select from './select';

type Props = React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLSelectElement>, HTMLSelectElement> & { label: string; errors?: string };

const SelectWitLargeData = forwardRef<HTMLSelectElement, Props>((props, ref) => {
    return (
        <Select ref={ref} {...props}>
           ...large amount of options
        </Select>
    );
});

export default SelectWitLargeData;
Run Code Online (Sandbox Code Playgroud)

问题是编译器在 <Select ref={ref} (<- here) ...>上给我一个错误

Type 'ForwardedRef<HTMLSelectElement> | LegacyRef<HTMLSelectElement>' is not assignable to type 'Ref<HTMLSelectElement>'.
  Type 'string' is not assignable to type 'Ref<HTMLSelectElement>'.ts(2322)
index.d.ts(140, 9): The expected type comes from property 'ref' which is declared here on 

type 'IntrinsicAttributes & Pick<Props, "label" | "key" | keyof InputHTMLAttributes<HTMLSelectElement> | "errors"> & RefAttributes<...>'

(JSX attribute) RefAttributes<HTMLSelectElement>.ref?: Ref<HTMLSelectElement>
Run Code Online (Sandbox Code Playgroud)

我在网上搜索了解决方案,并找到了很多建议,但这些都不起作用。

如有任何帮助,请提前致谢!

编辑 这是一个使用组件的示例表单

import { useEffect } from 'react';
import { useForm } from 'react-hook-form';

import Select from '@components/select';
import SelectWithLargeData from '@components/select-with-large-data';

type Form = {
    email: string;
    state: string;
    country: string;
};

const Address: React.FC = () => {
    const { handleSubmit, register, setValue } = useForm<Form>();

    useEffect(() => {
        setValue('state', 'AL');
    }, [setValue]);

    const onSubmit = async (input: Form) => {
        console.log(input);
    };

    return (
        <>
            <form onSubmit={handleSubmit(onSubmit)} className="space-y-10">
                <input type="email" {...register('email', { required: true })} />

                <SelectWithLargeData label="Provincia" {...register('state', { required: true })} />

                <Select label="Stato" {...register('country', { required: true })}>
                    <option value="IT">Italia</option>
                </Select>
            </form>
        </>
    );
};

export default Address;
Run Code Online (Sandbox Code Playgroud)

编辑2 https://codesandbox.io/s/withered-rain-w9ej4


cap*_*ian 2

请参阅转发参考文档:

const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref} className="FancyButton">
    {props.children}
  </button>
));

// You can now get a ref directly to the DOM button:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
Run Code Online (Sandbox Code Playgroud)

您应该ref在内部创建SelectWitLargeData而不是再次转发。

考虑这个例子:

import React, { forwardRef } from 'react';

type Props = React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLSelectElement>, HTMLSelectElement>;

const Select = forwardRef<HTMLSelectElement, Props>((props, ref) => {
  return (
    <select ref={ref} {...props}>
      <option value="">...</option>
      {props.children}
    </select>
  );
});

type SelectHigherOrder = Props & { label: string; errors?: string };

const SelectWitLargeData = (props: Omit<SelectHigherOrder, 'ref'>) => {
  const ref = React.useRef<HTMLSelectElement>(null);

  return (
    <Select ref={ref} {...props}>
      1
    </Select>
  );
}
Run Code Online (Sandbox Code Playgroud)

操场

您可能已经注意到我已经使用了Omit<SelectHigherOrder, 'ref'>. 这是因为您使用的是这种类型:React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLSelectElement>, HTMLSelectElement>;。并且此类型包含遗留引用类型:当您实际覆盖正确的引用类型之后type WithLegacyRef = Props['ref'] 使用时,这是{...props}ref={ref}React.RefObject<HTMLSelectElement>