【问题标题】:Uncaught TypeError: Typescript extension method not a function未捕获的 TypeError:Typescript 扩展方法不是函数
【发布时间】:2020-12-17 11:06:09
【问题描述】:

我正在尝试为 Array 编写扩展方法。每当程序运行代码时,我都会得到Uncaught TypeError: pairs.map(...).flatten is not a function。代码如下所示:

extensions.ts

declare interface Array<T> {
  flatten<T>(): T;
}

Array.prototype.flatten = function<T> () : T {
  return this.reduce(function (flat: Array<T>, toFlatten: Array<T>) {
    return flat.concat(Array.isArray(toFlatten) ? toFlatten.flatten() : toFlatten )
  }, []);
}

values.ts

export let pairs: { start: String[], end: String[] }[] = [
  { start: [ ... ], end: [ ... ] }, ...
];
export let items: String[] = pairs.map(pair => pair.start.concat(pair.end)).flatten();

我尝试将Array.prototype.flatten = function&lt;T&gt; () : T { ... 更改为Array.prototype.flatten = &lt;T&gt; () : T =&gt; { ...pairs.map(pair =&gt; pair.start.concat(pair.end)).flatten() 更改为pairs.map(pair =&gt; pair.start.concat(pair.end)).flatten&lt;String[]&gt;(),但没有任何效果。

我在这里的某个地方也读到它可能是一个转译问题,所以我将--target 编译器更改为ESNext,但错误仍然出现。

【问题讨论】:

    标签: typescript


    【解决方案1】:

    欢迎来到 SO,b12629 ?

    您看到的是运行时异常。虽然您的类型定义为 TS 提供了编译代码所需的一切,但您需要确保在入口文件顶部附近包含 flatten 的定义(在调用它之前),否则 JS 引擎不会知道它存在于Array 原型上。

    【讨论】:

    • 谢谢!我通过将flatten 定义放在value.ts 文件的顶部来让它工作。我可以将flatten 定义和interface Array&lt;T&gt; 声明都放在1 个extensions.ts 文件中吗?如果是这样,我应该把extensions.ts放在哪里?我试过将它放在src 文件夹、app 文件夹和我创建的models 文件夹中,但仍然出现运行时错误。
    • 我建议将您的声明 (declare ...) 保存在单独的文件中,例如可以是extensions.d.ts。然后,您将其余部分放在哪里完全取决于您,只要您在使用之前扩展原型即可。它可能在同一个文件中,或者在您调用新定义的函数之前需要的另一个模块中。
    【解决方案2】:

    不幸的是,TypeScript 在编译时加载模块的方式与 Javascript 在运行时加载模块的方式有很大不同(尤其是在使用 React 或 Jest 等具有自己的 Javascript 模块加载系统的框架时)。模块加载系统的这种差异意味着即使您的代码编译,它也可能在运行时失败(并且它是否失败在很大程度上取决于您使用的特定框架和模块导入/导出,导致代码非常脆弱)。因此,在 TypeScript 中可靠地使用扩展方法非常困难,因此建议使用替代方法(例如辅助方法、自定义类等)。

    但是,如果您确实想让扩展方法正常工作,请继续阅读。

    从根本上说,这个错误意味着 TypeScript 在编译期间正确定位了接口扩展:

    declare interface Array<T> {
      flatten<T>(): T;
    }
    

    但是 Javascript 没有在运行时执行扩展原型的调用:

    Array.prototype.flatten = function<T> () : T {
      return this.reduce(function (flat: Array<T>, toFlatten: Array<T>) {
        return flat.concat(Array.isArray(toFlatten) ? toFlatten.flatten() : toFlatten )
      }, []);
    }
    

    不幸的是,目前在 StackOverflow 上接受的答案和其他相关答案只提到将定义放在“顶级文件”中,这并没有给出足够清晰的说明来处理所有场景(参见 How to create an extension method in TypeScript for 'Date' data typeTypescript extension method compiling but not working at run timeTypescript Extend String interface Runtime Error)。

    要调试不一致的行为,您可以在原型扩展调用上放置一个断点,以查看何时/是否调用它。您还可以检查扩展类的proto 字段,以检查扩展方法是否已在运行时创建。请记住,由于 Tree Shaking,即使添加多余的导入行也不足以强制 JavaScript 评估此行。

    为确保原型扩展调用发生,找到所有应用程序入口点(例如 Jest 测试、Web 框架全局等)并确保从每个入口点调用原型扩展调用(粗略的解决方案是在每个入口点开始时调用的“设置”方法)。注意:记住 Jest 和其他测试库可以模拟模块,所以你要确保你永远不会模拟调用原型扩展的模块。实际上这个意味着您应该有一个单独的扩展文件并禁用 Jest 自动模拟(或等效)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-01-24
      • 2019-03-02
      • 1970-01-01
      • 1970-01-01
      • 2017-07-20
      • 2019-09-26
      • 2015-12-29
      相关资源
      最近更新 更多