【问题标题】:Typescript and JSX without React: Child Types没有 React 的 Typescript 和 JSX:子类型
【发布时间】:2021-03-30 05:26:33
【问题描述】:

所以我觉得我错过了一些明显的东西。我已经在非 React 上下文中阅读了有关 JSX 的文档,但我看不出我做错了什么。考虑以下代码:

/** @jsx pragma */

function pragma(node: any, props: any, ...children: any[]) {
  return node;
}

declare namespace JSX {
  type Element = string;
  interface ElementChildrenAttribute {
    children: {}; // specify children name to use
  }
}

interface PropsType {
  children: string;
  name: string;
}

export const Prop = (props: PropsType) => {};

const foo = <Prop name="foo">hello</Prop>;

我得到的错误(在最后一行)是:

Property 'children' is missing in type '{ name: string; }' but required in type 'PropsType'.

我可以通过以下方式“纠正”这个问题:

const foo = <Prop name="foo" children="bye">hello</Prop>;

但我预计hello 将被分配为children。我不应该指定明确的children property,对吗?我在这里想念什么?我希望原件没问题(不会产生错误),因为在这种情况下,children 字段应该用字符串值"hello" 填充(并且不需要我添加children 属性?!?) .

我在这里缺少什么?我需要在此处填写 JSX 命名空间的其他类型/字段以使其正常工作吗?

澄清:我担心的是 TypeScript 没有将字符串“hello”分配给孩子。如果这样做,一切都会好起来的。因此,我的 prop 类型似乎不是问题,因为我在重新声明 JSX 命名空间时遗漏了一些东西,告诉编译器如何处理我的“自定义”JSX。

另外,确实我不需要在我的道具中有children。但我当然被允许而且我肯定想要这样做,因为这允许我缩小类型。正是出于这个原因,这绝对是允许的(参见第二段here)。在此处添加它并不会创建新的道具,它只是对JSX.ElementChildrenAttribute 中定义的内容进行改进。

【问题讨论】:

  • 我比较熟悉直接用react使用children,但是children应该是JSX中默认的prop类型。从您的 propTypes 界面中删除子定义,并尝试通过 props.children[0] 直接访问文本。
  • 我猜你正在读这个? typescriptlang.org/docs/handbook/… 我认为你不需要在 Proptypes 界面中使用 `children: string;`,因为它现在是“隐式”的
  • 我确实需要它,因为我希望将更具体的类型约束应用于 Prop 而不仅仅是 {}。这在 TypeScript 的 JSX 实现中是明确允许的。
  • 如果你想让孩子成为一个字符串,我只是使用你的代码没有任何错误。项目设置如何?我对 React 之外的 JSX 不太熟悉。
  • 我正在运行 TypeScript 4.1.3。在 tsconfig.json 中,我将目标设置为“es2019”,将“jsx”设置为“react”。除了那些变化之外,我几乎是一个普通的 TS 项目。如果您无法使用这些设置进行复制,我将创建一个 repro-repo。

标签: typescript jsx


【解决方案1】:

在 React 中,您可以使用类型 PropsWithChildren&lt;T&gt;。这是 React 使用的,但您可以自己制作并将 React 的细节替换为 JSX。

这应该可以处理儿童可能成为的所有用例。

type Children = JSX.Element[] | JSX.Element | string | number | boolean | null | undefined;

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

type PropsType = {
    name: string;
}

export const Bar = ({ name, children }: PropsWithChildren<PropsType>) => (
    <div>
        <p>{name}</p>
        <div>{children}</div>
    </div>
);

我测试过,所有这些都不会引发类型错误。

export const ElementArray = () => (
    <Bar name="Foo">
        <div>Hello</div>
        <div>World</div>
    </Bar>
)

export const Element = () => (
    <Bar name="Foo">
        <div>Hello World</div>
    </Bar>
)

export const String = () => (
    <Bar name="Foo">Hello World</Bar>
)

export const Number = () => (
    <Bar name="Foo">{43110}</Bar>
)

export const Boolean = () => (
    <Bar name="Foo">{true}</Bar>
)

export const Null = () => (
    <Bar name="Foo">{null}</Bar>
)

export const Undefined = () => (
    <Bar name="Foo" />
)

此外,您可以使用 PropsWithChildren 创建自己的 FC(功能组件)类型,并像在 React 中一样使用它。

type FC<P = {}> = {
    (props: PropsWithChildren<P>): JSX.Element;
}

// usage
export const FooBar: FC<PropsType> = ({ name, children }) => (
    <div>
        <p>{name}</p>
        <p>{children}</p>
    </div>
);

【讨论】:

    【解决方案2】:

    您的原始代码declare global 有轻微遗漏。以下工作没有任何错误:

    /** @jsx pragma */
    
    function pragma(node: any, props: any, ...children: any[]) {
      return node;
    }
    
    declare global {
      namespace JSX {
        type Element = string;
        interface ElementChildrenAttribute {
          children: {}; // specify children name to use
        }
      }
    }
    
    interface PropsType {
      children: string;
      name: string;
    }
    
    export const Prop = (props: PropsType) => { return null };
    
    const foo = <Prop name="foo">hello</Prop>; // children assumed
    

    【讨论】:

      【解决方案3】:

      改变

      interface PropsType {
        children: string;
        name: string;
      }
      

      interface PropsType {
        name: string;
      }
      

      因为children 已经在ElementChildrenAttribute 中。您实际上是在将 children 覆盖为道具而不是 a "special property"

      【讨论】:

      • 这种“覆盖”是特别允许的,并且允许您进一步专门化类型(我想在这里做)。如果您查看链接到的部分之后的下一部分,它会说“您可以像任何其他属性一样指定子项的类型。这将覆盖默认类型”,然后给出一个示例。所以我认为我在这里所做的事情没有任何问题。
      • 澄清一下,我在这里担心的是 TypeScript 没有将字符串“hello”分配给孩子。如果这样做,一切都会好起来的。因此,我的 prop 类型似乎不是问题,因为我在重新声明 JSX 命名空间时遗漏了一些东西,告诉编译器如何处理我的“自定义”JSX。
      【解决方案4】:

      好的,我想我已经弄清楚了,但是文档中(我可以找到)中没有任何内容可以解释为什么我的原始代码不起作用以及为什么这种方法起作用。

      首先,我的代码中有一个小错误。我的Prop 组件应该返回JSX.Element 类型的东西,但它没有。这是一个简单的解决方法,并不能真正解决核心问题:

      export const Prop = (props: PropsType) => "world";
      export const Buz = (props: { id: string }) => "test2";
      

      但我也意识到了一些相当重要的事情。我注意到 Visual Studio Code 警告我上面代码中 JSX 的值没有被使用。我假设这个声明(只)存在给编译器看,而不是给我使用......所以我忽略了它。但后来我尝试将它粘贴到一个名为jsx.d.ts 的单独文件中。这有很大的不同。这是我的jsx.d.ts 文件:

      declare namespace JSX {
        type Element = string;
        interface ElementChildrenAttribute {
          children: {}; // specify children name to use (type seems irrelevant)
        }
      }
      

      这是我的代码(在.tsx 文件中):

      /** @jsx pragma */
      
      function pragma(node: any, props: any, ...children: any[]) {
        return node;
      }
      
      export interface PropsType {
        name: string;
        children: string;
      }
      
      export const Prop = (props: PropsType) => "test";
      export const Buz = (props: { id: string }) => "test2";
      

      有了这个改变,所有这些行现在的行为都符合我的预期:

      const ok1 = <Prop name="foo">hello</Prop>; // Compiles!
      const err1 = <Prop name="bar" />; // Error children missing
      const err2 = <Prop name="bar">{5}</Prop>; // Error cannot assign number to string
      const ok2 = <Buz id="roger"></Buz>; // Compiles (has no children)
      const err3 = (
        <Buz id="roger">
          <Buz id="wilco"></Buz>
          <Buz id="over"></Buz>
        </Buz>
      ); // Error, has children and shouldn't.
      

      【讨论】:

        猜你喜欢
        • 2017-05-24
        • 2017-02-24
        • 2019-05-10
        • 1970-01-01
        • 2019-06-06
        • 2019-04-07
        • 2021-07-10
        • 2017-11-28
        • 2021-12-29
        相关资源
        最近更新 更多