React TypeScript - 将动态泛型类型传递到forwardRef组件中

2024-05-03

我的问题的核心

const FinalComponent<GenericType extends 'a' | 'b'> =tsx 语法无效。

// The 1st line here is invalid tsx syntax
const FinalComponent<InvalidGenericType extends 'a' | 'b'> = 
    forwardRef<HTMLParagraphElement, PropsWithStandardRef<InvalidGenericType>>(({ value }, ref) => {
        return <Component forwardedRef={ref} value={value} />
    }) as ComponentType<InvalidGenericType>

该组件的预期用途:

const ExampleUsage = () => <FinalComponent<'b'> value="b" />

在这种情况下如何创建泛型类型?


额外的背景信息

对于其他上下文,以下是其余代码:

import { Ref, forwardRef } from 'react'

// These are the base props for the component. 
// In terms of usage, these are the props that I care about.
interface Props<GenericType extends 'a' | 'b'> {
    value: GenericType
}

// Adding forwardedRef to the props to define what props are usable inside the component
interface PropsWithForwardRef<GenericType extends 'a' | 'b'> extends Props<GenericType> {
    forwardedRef: Ref<HTMLParagraphElement | null>
}

// Adding standard ref to the props to define what props the component can accept from outside
interface PropsWithStandardRef<GenericType extends 'a' | 'b'> extends Props<GenericType> {
    ref?: Ref<HTMLParagraphElement | null>
}

// forwardRef is interfering with the inheritance of the generic types.
// This is a stand in for the expected return type of the component.
type ComponentType<GenericType extends 'a' | 'b'> = (props: PropsWithStandardRef<GenericType>) => JSX.Element

// The core component code
function CoreComponent<GenericType extends 'a' | 'b'> ({ value, forwardedRef }:PropsWithForwardRef<GenericType>):JSX.Element {
    return <p ref={forwardedRef}>{value}</p>
}

// !!!!!!!!!!! IMPORTANT BIT !!!!!!!!!!!!
// This is where my problem is, I need to be able to pass a dynamic generic type into PropsWithStandardRef and ComponentType.
// I'm not sure how to do that though because `const FinalComponent<InvalidGenericType extends 'a' | 'b'> = forwardRef()` is invalid
const FinalComponent<InvalidGenericType extends 'a' | 'b'> = forwardRef<HTMLParagraphElement, PropsWithStandardRef<InvalidGenericType>>(({ value }, ref) => {

    return <CoreComponent forwardedRef={ref} value={value} />

    // I need the `as ComponentType<InvalidGenericType>` bit because the inferred type that comes out of forwardRef
    // is making TS lose the generic types information
}) as ComponentType<InvalidGenericType>


// This is the end goal of how I want to be able to use this component
// I want to be able to pass a generic type into the component without TS complaining
const ExampleUsage = () => <FinalComponent<'b'> value="b" />

附言。我认识到这个例子有点做作,它是为了简化我的现实世界问题,该问题具有更复杂的组件。


类似但不同的问题

这不同于React Typescript - 动态类型 https://stackoverflow.com/questions/64208371/react-typescript-dynamic-types

在这个问题中,它不需要将类型信息传递到变量中,而只是根据用户提供的值来更改类型。

我需要组件的最终使用才能将类型传递给它。


您提供的代码非常接近工作,但只有一些事情需要更改以确保类型正确性:

  1. 保存 HTML 元素的 Ref 不应该是可变的/可空的,因为它们是由 React 设置和管理的,并且Ref<T>util 已经包含null反正。 (除非你正在做一些真正奇特的事情,比如强制操作渲染树之外的元素......但我什至从未在代码库中看到过这种情况。)因此,我删除了null来自您的工会Ref<HTMLParagraphElement | null>。 (这也会导致将值传递给中的实际段落元素时出现问题CoreComponent.)

  2. 您的返回类型ComponentType需要包括null对于返回类型forwardRef可分配给它。说到返回 React 元素的函数的返回类型,JSX.Element只是一个别名ReactElement with any传入类型参数。我改变了JSX.Element参考ReactElement.

  3. 类型注释仍然可以应用于保存函数表达式值的变量。它的编写方式与任何其他注释的编写方式相同:在标识符名称之后,如下所示:

    const add: (...numbers: number[]) => number = (...nums) => nums.reduce((sum, n) => sum + n, 0);
    

    在我看来,上面的语法不容易阅读,因此我更喜欢将类型括在括号中以提高可读性。您甚至可以使用带有签名的泛型(您的情况需要),并且也可以重载。请参阅FinalComponent在下面的代码中,修改:

import {
  default as React,
  createRef,
  forwardRef,
  ReactElement,
  Ref,
} from 'react';

type AorB = 'a' | 'b';

type Props<T extends AorB> = { value: T };
type PropsWithForwardRef<T extends AorB> = Props<T> & { forwardedRef: Ref<HTMLParagraphElement> };
type PropsWithStandardRef<T extends AorB> = Props<T> & { ref?: Ref<HTMLParagraphElement> };

function CoreComponent<T extends AorB> ({ value, forwardedRef }:PropsWithForwardRef<T>): ReactElement {
  return <p ref={forwardedRef}>{value}</p>;
}

const FinalComponent: (<T extends AorB>(props: PropsWithStandardRef<T>) => ReactElement | null) =
  forwardRef<HTMLParagraphElement, Props<AorB>>(({ value }, ref) => <CoreComponent forwardedRef={ref} value={value} />);

/**
 * The annotation for the function expression above can also be written this way,
 * which allows for overloading with multiple signatures, one on each line inside the braces:
 */
// const FinalComponent: {
//   <T extends AorB>(props: PropsWithStandardRef<T>): ReactElement | null;
// } = forwardRef<HTMLParagraphElement, Props<AorB>>(({ value }, ref) => <CoreComponent forwardedRef={ref} value={value} />);

/* Use: */

const ref = createRef<HTMLParagraphElement>();

const ExampleA = () => <FinalComponent<'a'> value="a" ref={ref} />;
const ExampleB = () => <FinalComponent<'b'> value="b" ref={ref} />;
const RefOptional = () => <FinalComponent<'a'> value="a" />;
const NoRestrictionA = () => <FinalComponent value="a" ref={ref} />;
const NoRestrictionB = () => <FinalComponent value="b" />;

const InvalidA = () => <FinalComponent<'a'> value="b" ref={ref} />;
const InvalidNotAorB = () => <FinalComponent value="c" />;
const InvalidNoValue = () => <FinalComponent<'a'> />;
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

React TypeScript - 将动态泛型类型传递到forwardRef组件中 的相关文章

随机推荐