React + TS:如何同时使用点符号组件和泛型?

huangapple go评论107阅读模式
英文:

React + TS: How to use dot-notation components and generics at the same time?

问题

I have a component List that has a sub-component Title, declared using dot-notation.

我有一个名为 `List` 的组件,它具有使用点表示法声明的子组件 `Title`

And then I use this component:

然后我使用这个组件:

At this point everything is fine, but if I need to add a generic type parameter to the component and its interface, it seems like there is no way to do this.

在这一点上一切都很好,但如果我需要向组件及其接口添加泛型类型参数,似乎无法实现这一点。

For example, I want the item name to be of a certain type that I can pass from the parent.

例如,我希望项目名称具有特定的类型,可以从父组件传递。

I know I can do it like this

我知道可以这样做

But in this case, I don't know how I can specify the type for List.Title. Is there any way to combine these two techniques?

但在这种情况下,我不知道如何为 `List.Title` 指定类型。是否有一种方法可以将这两种技术结合起来?

Codesandbox

英文:

I have a component List that has a sub-component Title, declared using dot-notation.

type Item = {
  name: string;
  value: number;
};

type ListProps = {
  title: string;
  data: Item[];
};

export const List: React.FC<ListProps> & {
  Title: React.FC<{ title: string }>
} = ({ title, data }) => {
  return (
    <div>
      <List.Title title={title}>
      <ul>
        {data.map((item) => (
          <li>
            {item.name}: {item.value}
          </li>
        ))}
      </ul>
    </div>
  );
};

List.Title = ({title}) => <div>{title}</div>

And then i use this component:

<List
	title="Stats"
	data={[
		{ name: "strength", value: 30 },
		{ name: "dexterity", value: 50 },
		{ name: "intellect", value: 100 },
		{ name: "vitality", value: 40 }
	]}
/>

At this point everything is fine, but if i need to add generic type parameter to component and its interface it seems like there is no way to do this.

For example i want item name to be of certain type, that i can pass from parent.

<List<"strength" | "dexterity" | "intellect" | "vitality">
	title="Stats"
	data={[
		{ name: "strength", value: 30 },
		{ name: "dexterity", value: 50 },
		{ name: "intellect", value: 100 },
		{ name: "vitality", value: 40 }
	]}
/>

I know i can do it like this

type Item<ItemName extends string = string> = {
  name: ItemName;
  value: number;
};

type ListProps<ItemName extends string = string> = {
  title: string;
  data: Item<ItemName>[];
};

export const List = <ItemName extends string = string,>({
  title,
  data
}: ListProps<ItemName>): JSX.Element => {
  return (
    <div>
      <ul>
        {data.map((item) => (
          <li>
            {item.name}: {item.value}
          </li>
        ))}
      </ul>
    </div>
  );
};

// This isn't working, because `List` has no property `Title`
// List.Title = ({ title }) => <div>{title}</div>;

But in this case i don't know how i can specify type for List.Title. Is there any way to combine this two techniques?

Codesandbox

答案1

得分: 1

> This isn't working, because List has no property Title

这不起作用,因为 List 没有 Title 属性。

That's not the error TypeScript is raising in the Codesandbox (or in the playground). The error is "Binding element 'title' implicitly has an 'any' type. (7031)" That's because you haven't given any type for the props of List.Title. The Title props used to get a type from the type you'd given List, but since you've removed the explicit type annotation on List, you need to specify the type of Title's title somewhere.

这不是 TypeScript 在 Codesandbox(或playground)中报错的原因。错误是“绑定元素 'title' 隐式具有 'any' 类型 (7031)”这是因为您没有为 List.Title 的 props 指定任何类型。Title 的 props 以前从您给定的 List 类型中获得类型,但由于您已经删除了 List 上的显式类型注释,您需要在某处指定 Titletitle 的类型。

The simple change is to just specify it inline:

简单的更改是直接内联指定它:

List.Title = ({ title }: { title: string; }) => <div>{title}</div>;
//                     ^^^^^^^^^^^^^^^^^^^

Playground | Codesandbox

或者,您可以像在您的第一个示例中那样为 List 指定一个显式类型。该类型是:

{
    <ItemName extends string = string>({ title, data }: ListProps<ItemName>): JSX.Element;
    Title({ title }: {
        title: string;
    }): JSX.Element;
}

所以:

export const List: {
    <ItemName extends string = string>({ title, data }: ListProps<ItemName>): JSX.Element;
    Title({ title }: {
        title: string;
    }): JSX.Element;
} = <ItemName extends string = string,>({/*...*/) => {
    // ...
};
List.Title = ({ title }) => <div>{title}</div>;

Playground

英文:

> This isn't working, because List has no property Title

That's not the error TypeScript is raising in the Codesandbox (or in the playground). The error is "Binding element 'title' implicitly has an 'any' type. (7031)" That's because you haven't given any type for the props of List.Title. The Title props used to get a type from the type you'd given List, but since you've removed the explicit type annotation on List, you need to specify the type of Title's title somewhere.

The simple change is to just specify it inline:

List.Title = ({ title }: {title: string; }) =&gt; &lt;div&gt;{title}&lt;/div&gt;;
//                     ^^^^^^^^^^^^^^^^^^^

Playground | Codesandbox

Alternatively, you can do what you did in your first example and give List an explicit type. The type is:

{
    &lt;ItemName extends string = string&gt;({ title, data }: ListProps&lt;ItemName&gt;): JSX.Element;
    Title({ title }: {
        title: string;
    }): JSX.Element;
}

so:

export const List: {
    &lt;ItemName extends string = string&gt;({ title, data }: ListProps&lt;ItemName&gt;): JSX.Element;
    Title({ title }: {
        title: string;
    }): JSX.Element;
} = &lt;ItemName extends string = string,&gt;({/*...*/}) =&gt; {
    // ...
};
List.Title = ({ title }) =&gt; &lt;div&gt;{title}&lt;/div&gt;;

Playground

huangapple
  • 本文由 发表于 2023年2月8日 18:50:38
  • 转载请务必保留本文链接:https://go.coder-hub.com/75384705.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定