【问题标题】:How do you correctly use React.lazy() in Typescript to import a react component with a generic type parameter?如何在 Typescript 中正确使用 React.lazy() 来导入带有泛型类型参数的反应组件?
【发布时间】: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 | 

【问题讨论】:

  • 你找到合适的解决方案了吗?

标签: reactjs typescript typescript-generics


【解决方案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()结合时才会发生。
    猜你喜欢
    • 2020-07-25
    • 2021-09-25
    • 1970-01-01
    • 2021-01-21
    • 2022-01-21
    • 2023-02-09
    • 2022-01-16
    • 1970-01-01
    • 2020-05-30
    相关资源
    最近更新 更多