0

我有 Tree 组件,它呈现绘制 TreeItem 的 TreeItemsRenderer。它们中的每一个都是独立的组件。
我需要接受替换默认 TreeItem 的动态组件,并且我需要拥有所有默认 TreeItem 道具。(例如:const CustomComponent 扩展 TreeItem)。树在代码中的许多地方使用。所以它必须是通用的。

  • 我试图通过创建泛型类型从默认 TreeItem 获取所有属性 D extends ITreeItemProps<T>
  • Tgeneric 用于项目类型。
  • 所有的自动完成都是从外部工作的。只有错误出现在渲染 SelectedRowComponent 的 TreeItemRenderer 组件上。

这是我的组件(有时可能不合逻辑,因为我删除了不必要的东西并简化了逻辑):

树 :

import { ITreeItemProps, ITreeItem } from "./types";
import TreeItemRenderer from "./TreeItemRenderer";

export interface ITreeProps<T, D extends ITreeItemProps<T>> {
    items: ITreeItem<T>[];
    getItemKey: (item: T) => string | number; 
    SelectedRowComponent?: React.ComponentType<D>;
    SelectedRowProps?: Partial<React.ComponentProps<ITreeProps<T, D>['SelectedRowComponent'] & {}>>;
    selected?: string | number;
}

export default function Tree<T, D extends ITreeItemProps<T>>(props: ITreeProps<T, D>){
    const {
        items,
        SelectedRowComponent,
        SelectedRowProps,
        getItemKey,
        selected,
    } = props;
    return <>
    <TreeItemRenderer 
        getItemKey={getItemKey}
        items={items}
        SelectedRowComponent={SelectedRowComponent}
        SelectedRowProps={SelectedRowProps}
        selected={selected}
    />
    </>
}

树项渲染器:

import { ITreeItemProps, ITreeItem } from "./types";
import TreeItem from './TreeItem';

export interface ITreeItemRenderer<T, D extends ITreeItemProps<T>>{
    getItemKey: (item: T) => string | number; 
    SelectedRowComponent?: React.ComponentType<D>
    SelectedRowProps?: Partial<React.ComponentProps<ITreeItemRenderer<T, D>['SelectedRowComponent'] & {}>>;
    items: ITreeItem<T>[];
    selected?: string | number;
}

export default function TreeItemRenderer<T, D extends ITreeItemProps<T>>(props: ITreeItemRenderer<T, D>){
    const {
        SelectedRowComponent,
        SelectedRowProps,
        items,
        selected,
        getItemKey,
    } = props;

    return <>
        {items.map((item) => {
            const key = getItemKey(item);
            const isSelected = key === selected;
            return <>
                {
                    isSelected ? 
                    <SelectedRowComponent 
                        {...SelectedRowProps}
                        item={item}
                        getItemKey={getItemKey}
                        isSelected
                    />
                    : <TreeItem 
                        item={item}
                        getItemKey={getItemKey}
                        isSelected
                    />
                }
            </>
        })}
    </>
}

类型组件:

export interface ITreeItemProps<T> {
    getItemKey: (item: T) => string | number; 
    isSelected: boolean;
    item: ITreeItem<T>;
}
export type ITreeItem<T> = T & {
    children: ITreeItem<T>[];
};

我的问题是在第 28 行的 TreeItemRenderer

Type '(Partial<D> & { item: ITreeItem<T>; getItemKey: (item: T) => string | number; isSelected: true; }) | (Partial<D & { children?: ReactNode; }> & { ...; })' is not assignable to type 'IntrinsicAttributes & D & { children?: ReactNode; }'.
  Type 'Partial<D> & { item: ITreeItem<T>; getItemKey: (item: T) => string | number; isSelected: true; }' is not assignable to type 'IntrinsicAttributes & D & { children?: ReactNode; }'.
    Type 'Partial<D> & { item: ITreeItem<T>; getItemKey: (item: T) => string | number; isSelected: true; }' is not assignable to type 'D'.
      'Partial<D> & { item: ITreeItem<T>; getItemKey: (item: T) => string | number; isSelected: true; }' is assignable to the constraint of type 'D', but 'D' could be instantiated with a different subtype of constraint 'ITreeItemProps<T>'.ts(2322)
4

1 回答 1

0

我将尝试说明您的代码如何破坏类型。打字稿中有文字类型。并D extends ITreeItemProps<T>假设您可以提供更专业的类型ITreeItemProps<T>

/*
type ExtendedItemProps = {
    isSelected: false;
    getItemKey: (item: {}) => string | number;
    item: {
        children: ...[];
    };
}
*/
interface ExtendedItemProps extends ITreeItemProps<{}> {
  isSelected: false
}

这种类型完美地扩展了您的ITreeItemProps但在这里:

<SelectedRowComponent 
    {...SelectedRowProps}
    item={item}
    getItemKey={getItemKey}
    isSelected
/>

你的SelectedRowComponent而不是isSeleted: false接收isSelected: true。而 whiletrue是该类型的有效子类型,用字段类型为 的不同boolean类型D实例化。type 的另一个有效子类型,但与.isSelectedfalsebooleantrue

另一个问题。当您的组件期望类型的属性时,D您试图将SelectedRowProps类型的属性传递给它Partial<D>plus { item: ITreeItem<T>; getItemKey: (item: T) => string | number; isSelected: true; }。想象一下,我们将类型D声明为:

interface ExtendedItemProps extends ITreeItemProps<{}> {
  title: string
}

请注意,这title是必填字段。但是您可以轻松地SelectedRowProps在没有该字段的情况下提供,就像Partial对类型一样。

因此,这样的类型定义至少有两件事会出错。

过度复杂的类型还有一个小问题React.ComponentProps<ITreeItemRenderer<T, D>['SelectedRowComponent'] & {}>。这是相当复杂的说法D


总而言之,如果您SelectedRowComponent接受确切的类型ITreeItemProps<T> 加上一些额外的道具,您应该编写您的类型而不以通用方式扩展 ITreeItemProps。例如,您ITreeItemRenderer可能看起来像这样:

export interface ITreeItemRenderer<T, D extends Record<string, any> = {}>{
    getItemKey: (item: T) => string | number; 
    SelectedRowComponent?: React.ComponentType<Omit<D, keyof ITreeItemProps<any>> & ITreeItemProps<T>>
    SelectedRowProps: D & Partial<ITreeItemProps<T>>;
    items: ITreeItem<T>[];
    selected?: string | number;
}

function TreeItemRenderer<T, D>(props: ITreeItemRenderer<T, D>) {...}

游乐场链接

请注意,SelectedRowProps这里不是可选的。否则,您无法将这些额外属性传递给SelectedRowComponent组件。在这里,我们本质上是在告诉打字稿getItemkey,isSelecteditemfields 具有来自的确切类型,ITreeItemProps<T>但组件可以接受来自 type 的一些额外属性,D并且在内部SelectedRowProps你应该只指定那些需要的字段,但可以省略D字段 from 。ITreeItemProps<T>


我希望你明白,即使你的SelectedRowPropsprop 有任何 fields getItemKeyisSelected或者item设置它们将被这里的其他属性覆盖ITreeItemRenderer

<SelectedRowComponent 
    {...SelectedRowProps}
    item={item}
    getItemKey={getItemKey}
    isSelected
/>
于 2021-07-08T07:07:05.743 回答