无法满足使用 TypeScript 的反应测试库渲染函数的包装器参数

Cannot satisfy wrapper parameter for react testing library render function using TypeScript

我正在尝试构建一个方便的函数,它允许我传递一个反应组件,并让它使用我想要的所有选项和路由规范来呈现组件,我将它包裹在 <BrowserRouter> 用于单元测试。

问题是当我尝试将 Wrapper 函数传递到反应测试库的 render() 时(请参阅下面的 rtlRender()),我从 TypeScript 中收到错误,说明以下内容

No overload matches this call. Overload 1 of 2, '(ui: ReactElement<any, string | JSXElementConstructor>, options: RenderOptions<Queries, HTMLElement>): RenderResult<...>', gave the following error. Type '({ children }: Props) => JSX.Element' is not assignable to type 'ComponentType<{}> | undefined'. Type '({ children }: Props) => JSX.Element' is not assignable to type 'FunctionComponent<{}>'. Types of parameters '__0' and 'props' are incompatible. Type '{ children?: ReactNode; }' is not assignable to type 'Props'. Types of property 'children' are incompatible. Type 'ReactNode' is not assignable to type 'ReactChildren'. Type 'undefined' is not assignable to type 'ReactChildren'. Overload 2 of 2, '(ui: ReactElement<any, string | JSXElementConstructor>, options?: Omit<RenderOptions<typeof import("/Users/myuser/dev/myapp/container/node_modules/@testing-library/dom/types/queries"), HTMLElement>, "queries"> | undefined): RenderResult<...>', gave the following error. Type '({ children }: Props) => JSX.Element' is not assignable to type 'ComponentType<{}> | undefined'. Type '({ children }: Props) => JSX.Element' is not assignable to type 'FunctionComponent<{}>'.ts(2769) index.d.ts(41, 3): The expected type comes from property 'wrapper' which is declared here on type 'RenderOptions<Queries, HTMLElement>' (property) RenderOptions<Queries, HTMLElement>.wrapper?: React.ComponentType<{}> | undefined

这是我的代码

import React from 'react';
import { Queries, render as rtlRender } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';
import { RenderResult } from '@testing-library/react/types';

/* Type Definitions */
type Route = {
  state: Record<string, unknown>;
  title: string;
  location: string;
};
type Props = {
  children: React.ReactChildren;
};

/* Helper Functions */
export function render(ui: JSX.Element, options: { route: Route }): RenderResult<Queries, HTMLElement> {
  const { route } = options;
  window.history.pushState(route?.state ?? null, route?.title ?? 'Root', route?.location ?? '/');

  const Wrapper = ({ children }: Props) => {
    return <BrowserRouter>{children}</BrowserRouter>;
  };

  return rtlRender(ui, { wrapper: Wrapper, ...options });
}

据我了解,它告诉我 Wrapper() return 不符合 React class 组件或功能组件的描述。这对我来说似乎很奇怪,因为您可以从代码中看出,Wrapper() 应该 return 是一个功能组件。

我试图将 Wrapper() 分配给 React.FC<Props> 类型,但 TypeScript 声明 React.FC<Props> 不是 React.ComponentType 类型,这让我更加困惑。

我对 TypeScript 还是个新手,所以加入所有这些 React 类型让我更加困惑。我希望对此有一些指导。

React.FC 类型是 React.FunctionComponent 类型的别名。 React.FunctionComponent 的道具是 PropsWithChildren 类型,其中有 children?: ReactNode 属性。您不需要创建自己的 Props 类型。

render函数的options参数类型为:

export interface RenderOptions<Q extends Queries = typeof queries> {
  container?: Element
  baseElement?: Element
  hydrate?: boolean
  queries?: Q
  wrapper?: React.ComponentType
}

没有route属性,我想是为了自定义render功能吧。但是你需要使用 Intersection Types 这样你就可以将 options 传递给 RTL 的 render 函数。

type FC<P = {}> = FunctionComponent<P>;

interface FunctionComponent<P = {}> {
    (props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null;
    propTypes?: WeakValidationMap<P>;
    contextTypes?: ValidationMap<any>;
    defaultProps?: Partial<P>;
    displayName?: string;
}

type PropsWithChildren<P> = P & { children?: ReactNode };

RenderResult 的默认通用参数类型是 typeof queriesNOT Queries。这是Queries界面:

export interface Queries {
    [T: string]: Query;
}

@testing-library/dom/types/queries.d.ts完全不同。

import React from 'react';
import { queries, render as rtlRender, RenderOptions } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';
import { RenderResult } from '@testing-library/react/types';

/* Type Definitions */
type Route = {
  state: Record<string, unknown>;
  title: string;
  location: string;
};

/* Helper Functions */
export function render(
  ui: React.ReactElement,
  options: { route: Route } & Omit<RenderOptions, 'queries'>
): RenderResult<typeof queries> {
  const { route, ...rest } = options;
  window.history.pushState(route?.state ?? null, route?.title ?? 'Root', route?.location ?? '/');

  const Wrapper: React.FC = ({ children }) => {
    return <BrowserRouter>{children}</BrowserRouter>;
  };

  return rtlRender(ui, { wrapper: Wrapper, ...rest });
}