如何在 Typescript 中正确使用 React.lazy() 来导入带有泛型类型参数的反应组件?

Posted

技术标签:

【中文标题】如何在 Typescript 中正确使用 React.lazy() 来导入带有泛型类型参数的反应组件?【英文标题】:How do you correctly use React.lazy() in Typescript to import a react component with a generic type parameter? 【发布时间】:2020-08-23 07:53:29 【问题描述】:

(在https://github.com/codingismy11to7/lazy-component-typing 重现)

我猜这是@types/reactLazyExoticComponent 定义的问题...

在带有类型参数的组件上使用React.lazy() 时,Typescript 无法再编译 项目。

给定组件:

import React from "react";

export interface Props<T> 
  value: T;
  valueCallback: (t: T) => void;


export default function TypedComponent<T>(props: Props<T>) 
  return (
    <>
      <div>`$props.value`</div>
      <button onClick=() => props.valueCallback(props.value)>click</button>
    </>
  )

这样编译:

import React, useState from "react";
import TypedComponent from "./TypedComponent";

export default function StrictComponent() 
  const [state, setState] = useState("blah");

  return (
    <TypedComponent value=state valueCallback=setState/>
  );

虽然没有:

import React, lazy, useState from "react";
const TypedComponent = lazy(() => import("./TypedComponent"));

export default function LazyComponent() 
  const [state, setState] = useState("blah");

  return (
    <TypedComponent value=state valueCallback=setState/>
  );

TypeScript error in D:/dev/lazy-component-typing/src/LazyComponent.tsx(9,35):
Type 'Dispatch<SetStateAction<string>>' is not assignable to type '(t: unknown) => void'.
  Types of parameters 'value' and 't' are incompatible.
    Type 'unknown' is not assignable to type 'SetStateAction<string>'.
      Type 'unknown' is not assignable to type '(prevState: string) => string'.  TS2322

     7 | 
     8 |   return (
  >  9 |     <TypedComponent value=state valueCallback=setState/>
       |                                   ^
    10 |   );
    11 | 
    12 | 

【问题讨论】:

你找到合适的解决方案了吗? 【参考方案1】:

TL;DR: 您可以覆盖 React.lazy 类型定义以支持泛型。

react.d.ts:

declare namespace React 
  function lazy<T extends ComponentType<any>>(
    factory: () => Promise< default: T >,
  ): T;

说明:原始React.lazy 声明如下所示:

function lazy<T extends ComponentType<any>>(
  factory: () => Promise< default: T >
): LazyExoticComponent<T>;

它返回LazyExoticComponent 类型,它是ExoticComponent 的超集。不幸的是,这个接口的编写方式不支持泛型。但是,根据comment above the interface,它仅用于内部区分“常规”和其他组件,例如lazymemo 调用的结果;在 JSX 语法中,ExoticComponentComponent 之间没有区别。这就是为什么我们可以安全地在自定义覆盖中省略 LaxyExoticComponent 类型并使用泛型。

【讨论】:

【参考方案2】:

不是理想的解决方案,但我能够通过导出组件的类型来实现这一点,然后使用它来转换 React.lazy 的结果

export type TypedComponentType = typeof TypedComponent;

然后

import type  TypeComponent  from './TypedComponent';
const TypedComponent = lazy(() => import("./TypedComponent")) as TypedComponentType;

【讨论】:

我喜欢这个想法,但我只是将其转换为typeof TypedComponent,而不是为此目的创建新类型。【参考方案3】:

TSX 似乎不能很好地处理 props 中的类型参数。

你应该设置一个默认的类型参数值,例如any,但这只是假设你的类型在使用组件时总是any,这可能不是你想要的:

export interface Props<T = any> 
  value: T;
  valueCallback: (t: T) => void;


// ...

// `valueCallback` will pass an explicit `any`
<TypedComponent value=value valueCallback=setState />

您可以尝试使用有效的扩展类型复制它,然后使用它,例如:

import  Props as TCProps  from './TypedComponent'
const StronglyTypedComponent: React.Component<TCProps<string>> = TypedComponent

// ...

// correctly typed, but more verbal
<StronglyTypedComponent value=value valueCallback=setState /> 

【讨论】:

TSX 在像你写的那样处理我的类型参数的情况下,问题只有在与React.lazy()结合时才会发生。

以上是关于如何在 Typescript 中正确使用 React.lazy() 来导入带有泛型类型参数的反应组件?的主要内容,如果未能解决你的问题,请参考以下文章

使用 create-react-app 和 Typescript 时如何在构建时加载 .md 文件?

如何在 Typescript 中正确使用环境变量? [复制]

Typescript - 如何在 Angular2 中正确使用 jsPDF?

如何使用 Typescript 在 mongoose 中正确键入对象 ID 数组

如何使用 Typescript 在 mongoose 中正确键入对象 ID 数组

如何在 React 中正确使用 useState 钩子和 typescript?