TypeScript 在声明文件中支持所有类型特性,

w7t8yxp5  于 3个月前  发布在  TypeScript
关注(0)|答案(9)|浏览(40)

我在编写此文时使用的 TypeScript 版本是 3.7.3。
这个问题似乎应该集中在解决,然后在继续向语言添加更多功能和扩大源声明与声明之间的差距之前加以修复。这是一个相当大的问题!

搜索词

"typescript 声明文件限制"
"具有或正在使用私有名称"
"导出的类表达式可能不是私有的或受保护的"
"导出函数的返回类型具有或正在使用私有名称 'foo'。ts(4094)"
相关问题(在 Google 上搜索结果中的一小部分,按顺序排列)包括:

解决方法

要解决上述所有问题,对于库来说,一种方法是让库作者将他们的 types 字段指向其源入口点。这将消除列出的问题,但有一些很大的缺点:

有了这两个问题的具体说明,如果您知道您将在一个系统中工作,可以保证所有库及其消费者都将使用相同的编译器(例如定义编译器选项的 Deno,适用于所有人),那么将 package.jsontypes 字段指向源文件(因为无法生成声明输出)是目前允许不受支持的声明文件功能的解决方法。 但是,如果库作者不知道库消费者将使用哪些编译器设置,这将导致大问题:一切可能会在开发和测试期间正常工作,但下游用户最终会在使用不同设置的情况下报告类型错误!

建议

支持声明文件中的所有语言特性。
请务必。🙏 我非常喜欢你们 TypeScript 团队,你们已经使 TypeScript 带来了更好的代码开发体验。❤️ 我只需要 TypeScript 为支持 所有 语言特性提供声明即可。一些工作正在进行中,例如 xE20fX ,但总体而言,我认为太多的努力被放在了新的语言特性上,而声明输出却被抛在了后面。这给开发者带来了糟糕的使用体验,当他们的工作代码不起作用时(他们希望发布它作为库并打开 xE24nX 时),他们的代码就无法正常工作。我希望神奇的、了不起的 TS 团队意识到这对某人来说可能是痛苦的,他们在项目上工作了数月之久,最后却以无法修复的类型错误结束,当他们想要发布这个库作为库并使用 xE25nX 时,然后不得不放弃功能并重写他们的代码,或者尝试将 package.json 的 xE26nX 指向 xE27nX 文件,却只能面对 xE28fX 和 xE29fX 。我希望每种语言的新功能都有与之配对的工作测试来等效地生成声明输出。能否将这一点变成每个新语言特性的要求?就像单元测试一样?
当然,这需要一些想象力,但更重要的是,它需要一些纪律:不允许在没有声明平衡的情况下引入新功能。

检查表

我的建议符合以下准则:

  • 这不会对现有的TypeScript/JavaScript代码造成破坏性改变
  • 这不会改变现有JavaScript代码的运行时行为
  • 这可以在不根据表达式的类型生成不同的JS代码的情况下实现
  • 这不是一个运行时特性(例如库功能、带有JavaScript输出的非ECMAScript语法等)
  • 这个特性将与TypeScript's Design Goals的其他部分保持一致。
ijxebb2r

ijxebb2r1#

@RyanCavanaugh 我有一个大型的 React UI 库,是用 TypeScript 编写的。我真的不希望导出所有 props 的类型,因为有一个明确的方法可以获取组件的 props。这是我的例子:

interface Props {
  children: React.ReactNode;
  disabled?: boolean;
  fullWidth?: boolean;
  intent?: 'primary' | 'secondary' | 'tertiary' | 'neutral' | 'destructive';
  onClick?: React.MouseEventHandler<HTMLButtonElement>;
  size?: 'smallest' | 'small' | 'medium' | 'large' | 'largest';
}

export function UIButton(props: Props): JSX.Element {
  return (
    <button
      className={clsx('UIButton', {
        'UIButton--disabled': props.disabled,
        'UIButton--full-width': props.fullWidth,
        'UIButton--intent-destructive': props.intent === 'destructive',
        'UIButton--intent-neutral': props.intent === 'neutral',
        'UIButton--intent-primary': props.intent === 'primary',
        'UIButton--intent-secondary': props.intent === 'secondary',
        'UIButton--intent-tertiary': props.intent === 'tertiary',
        'UIButton--size-large': props.size === 'large',
        'UIButton--size-largest': props.size === 'largest',
        'UIButton--size-medium': props.size === 'medium',
        'UIButton--size-small': props.size === 'small',
        'UIButton--size-smallest': props.size === 'smallest',
      })}
      disabled={props.disabled}
      onClick={props.onClick}
    >
      {props.children}
    </button>
  );
}

如果人们需要访问 props,他们可以通过使用 React.ComponentPropsWithoutRef<typeof UIButton> 来实现。我认为这是一个关于私有接口的使用清晰的例子,因为它仍然可以访问,而不需要显式地处理导出类型。
这个问题对我来说是个问题,因为我有一个单独的索引文件,我在那里定义了 export * from './UIButton'; 和库中的每个其他组件,这里会出现类型的冲突,而且我真的不希望将所有的类型重命名为以组件名称为前缀的形式。

zengzsys

zengzsys2#

#32028 中的更改可能导致在编辑器中代码运行正常,但在准备发布库时,在尝试制作声明文件时出现问题(由于OP问题列表中的问题)。(cc @sheetalkamat)

332nm8kg

332nm8kg3#

当我使用 hoist-non-react-statics 时,我也得到了 error TS4060: Return type of exported function has or is using private name 'WithLocale'。这种正常模式在 declaration: true 时会导致错误:

export default function withLocaleComponent<
  TProps extends WithLocaleComponentInjectedProps
>(Component: React.ComponentType<TProps>) {
  class WithLocale extends React.Component<
    Omit<TProps, keyof WithLocaleComponentInjectedProps> &
      WithLocaleComponentProps
  > {
    static displayName = getDisplayName("withLocale", Component);

    render() {
      return (
        <LocaleContext.Consumer>
          {context => {
            let locale = this.props.locale || context.locale;
            if (typeof locale === "string") {
              const [language, country] = locale.split("-");
              locale = { country, language };
            }
            return (
              <Component
                {...(this.props as TProps)}
                {...context}
                locale={locale}
              />
            );
          }}
        </LocaleContext.Consumer>
      );
    }
  }

  return hoistStatics(WithLocale, Component);
}

然后,我想到了一个创建函数混入的方法,它返回一个类,在使用 declaration: true 时没有出现错误。

function withLocaleMixin<TProps extends WithLocaleComponentInjectedProps>(
  Component: React.ComponentType<TProps>
) {
  return class WithLocale extends React.Component<
    Omit<TProps, keyof WithLocaleComponentInjectedProps> &
      WithLocaleComponentProps
  > {
    static displayName = getDisplayName("withLocale", Component);

    render() {
      return (
        <LocaleContext.Consumer>
          {context => {
            let locale = this.props.locale || context.locale;
            if (typeof locale === "string") {
              const [language, country] = locale.split("-");
              locale = { country, language };
            }
            return (
              <Component
                {...(this.props as TProps)}
                {...context}
                locale={locale}
              />
            );
          }}
        </LocaleContext.Consumer>
      );
    }
  };
}

export default function withLocaleComponent<
  TProps extends WithLocaleComponentInjectedProps
>(Component: React.ComponentType<TProps>) {
  return hoistStatics(withLocaleMixin<TProps>(Component), Component);
}

我的问题是,使用第二种模式(具有 withLocaleMixin 方法返回类的方法)是否可以?还是会引发与第一种模式不同的行为? 🙇

8tntrjer

8tntrjer4#

我更新了原始帖子,增加了一个名为Workaround的部分,详细描述了如何当前解决所有列出的问题,但它也带来了相当大的缺点。

daolsyd0

daolsyd05#

保持讨论继续,摘自 #44360 :
我发现 TypeScript 目前分裂成两种语言非常令人沮丧。第一种是 TypeScript 本身,第二种是 TypeScript+declaration files 。在第一种语言中有效的程序,在第二种中无效。这意味着,TypeScriptTypeScript+declarations 的超集。
您可以在 TypeScript 中表达更多的逻辑,但是 TypeScript+declarations 是分发代码的标准(以避免重新编译源文件)。
尽管这是一个众所周知的问题,但这种分裂并没有在任何地方进行文档记录,TypeScript 开发团队也没有提供关于它的很多反馈。这是一个被掩盖的事情,没有人想谈论这个。这个问题至少应该在文档网站上详细描述。
解决方案很简单 - 应该做出一个设计决策,使得 *.d.ts 文件创建与常规源文件完全相同的内部编译数据。为此,可能需要不同的声明文件格式,即 *.d2.ts
当你不得不花费数小时将你的代码(在 TypeScript 中编译得很好)混合在一起,降低类型安全性,使其在 TypeScript+declarations 语言中工作时,这是非常令人沮丧的。在许多情况下,这是根本不可能的。

kfgdxczn

kfgdxczn7#

是的,看起来是一个合适的地方。只需要明确一点,只有一部分常规的 TypeScript 特性在使用声明文件 emit 时才被支持。

vq8itlhq

vq8itlhq8#

根据@trusktr在帖子中的建议,改变声明文件生成的方式,只剥离实现并保留类型,可能是解决问题的最佳方案。
同时,即使走向修复现有解决方案的道路,似乎大部分拼图都已经在那里了。
正如帖子中提到的,匿名类型可以用#30979解决。也就是说,“绿色”,准备合并的PR。
另一个部分与内部mixin类的类型用对象表示的事实有关。当然,这会导致错误。例如,对于这个mixin:

export type AnyFunction<A = any> = (...input: any[]) => A
export type AnyConstructor<A = object> = new (...input: any[]) => A
export type Mixin<T extends AnyFunction> = InstanceType<ReturnType<T>>

export const MyMixin = <T extends AnyConstructor>(base : T) =>
    class MyMixin extends base {
        method () : this {
            return this
        }
    }
export type MyMixin = Mixin<typeof MyMixin>

生成的声明是:

export declare type AnyFunction<A = any> = (...input: any[]) => A;
export declare type AnyConstructor<A = object> = new (...input: any[]) => A;
export declare type Mixin<T extends AnyFunction> = InstanceType<ReturnType<T>>;
export declare const MyMixin: <T extends AnyConstructor<object>>(base: T) => {
    new (...input: any[]): {
        method(): this;
    };
} & T;
export declare type MyMixin = Mixin<typeof MyMixin>;

这导致错误this is only available in the non-static method of the class
但是,还有一个“绿色”的PR:#41587,可以用来解决这个问题。这个PR还应该解决众所周知的私有/受保护方法的问题。有了这个PR,MyMixin的声明将如下所示:

export declare const MyMixin: <T extends AnyConstructor<object>>(base: T) => typeof class MyMixin {
    new (...input: any[]): MyMixin;
    method(): this;
} & T;

因此,我相信,修复这个工单是一个适当的优先级问题,其中没有不可抗拒的技术问题。
这个问题显然给很多人带来了很多困扰,而且很明显没有讨论修复它的意图。亲爱的TS团队,为什么这种态度?

thigvfpy

thigvfpy9#

@RyanCavanaugh 我必须说,我也对这个感到失望。一个团队可能会花很长时间建立一个库,并假设“如果它编译了,它就会工作!”结果在打开declarations后发现他们有很多非常难以解决的错误。这并不直观,而且没有太多的文档说明有这样的限制。我很庆幸现在偶然发现了这个问题。

相关问题