首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

Migrating from JavaScript

TypeScript不存在于真空中。它是以JavaScript生态系统为基础构建的,今天存在很多JavaScript。将JavaScript代码转换为TypeScript虽然有点乏味,但通常不具有挑战性。在本教程中,我们将着眼于如何开始。我们假设您已经阅读了足够的手册来编写新的TypeScript代码。

如果您希望转换React项目,我们建议首先查看React转换指南

设置您的目录

如果你在普通的JavaScript编写,很可能是因为你没有直接运行的JavaScript,在您.js的文件srclibdist目录,然后运行任意的。

如果是这样的话,你写的文件将被用作TypeScript的输入,并且你将运行它产生的输出。在我们的JS到TS迁移期间,我们需要分离我们的输入文件以防止TypeScript覆盖它们。如果您的输出文件需要驻留在特定的目录中,那么这将是您的输出目录。

您可能还会在JavaScript上运行一些中间步骤,例如捆绑或使用Babel等其他转播程序。在这种情况下,您可能已经拥有了像这样设置的文件夹结构。

从这一点开始,我们假设你的目录是这样设置的:

代码语言:javascript
复制
projectRoot
├── src
│   ├── file1.js
│   └── file2.js
├── built
└── tsconfig.json

如果你有tests你的文件夹以外的src目录,你可能有一个tsconfig.jsonsrc,一个在tests为好。

编写配置文件

TypeScript使用一个叫做tsconfig.json管理项目选项的文件,比如你想包含哪些文件,以及你想要执行什么样的检查。让我们为我们的项目创建一个简单的框架:

代码语言:javascript
复制
{
  "compilerOptions": {
    "outDir": "./built",
    "allowJs": true,
    "target": "es5"
  },
  "include": [
    "./src/**/*"
  ]
}

这里我们给TypeScript指定一些东西:

  1. 读入它在src目录(with include)中理解的任何文件。

2. 接受JavaScript文件作为输入(带allowJs)。

3.built(with outDir)发出所有输出文件。

  1. 将较新的JavaScript结构转换为较旧的版本,如ECMAScript 5(使用target)。

此时,如果您尝试tsc在项目的根目录下运行,则应该在built目录中看到输出文件。文件的布局built应该与布局相同src。你现在应该有TypeScript与你的项目一起工作。

早期利好

即使在这一点上,您也可以通过TypeScript了解您的项目获得很多好处。如果你打开一个像VS CodeVisual Studio这样的编辑器,你会发现你经常可以获得一些工具支持,比如完成。您还可以通过以下选项捕获某些错误:

  • noImplicitReturns 这可以防止您忘记在函数结束时返回。
  • noFallthroughCasesInSwitch如果你不想忘记块break之间case的语句,这是有帮助的switch

输出文件也警告可达代码和标签,您可以禁用allowUnreachableCodeallowUnusedLabels分别。

与构建工具集成

您的管道中可能还有一些构建步骤。也许你将某些东西连接到每个文件。每个构建工具都是不同的,但我们会尽我们所能来覆盖事物的要点。

如果你以某种方式使用Gulp,我们有一个使用Gulp和TypeScript的教程,并且集成了常见的构建工具,如Browserify,Babelify和Uglify。你可以在这里阅读更多。

WebPack

Webpack集成非常简单。您可以使用awesome-typescript-loaderTypeScript加载程序,source-map-loader以便于调试。只需运行

代码语言:javascript
复制
npm install awesome-typescript-loader source-map-loader

并将以下选项合并到您的webpack.config.js文件中:

代码语言:javascript
复制
module.exports = {
  entry: "./src/index.ts",
  output: {
    filename: "./dist/bundle.js",
  },

  // Enable sourcemaps for debugging webpack's output.
  devtool: "source-map",

  resolve: {
    // Add '.ts' and '.tsx' as resolvable extensions.
    extensions: ["", ".webpack.js", ".web.js", ".ts", ".tsx", ".js"]
  },

  module: {
    loaders: [
      // All files with a '.ts' or '.tsx' extension will be handled by 'awesome-typescript-loader'.
      { test: /\.tsx?$/, loader: "awesome-typescript-loader" }
    ],

    preLoaders: [
      // All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
      { test: /\.js$/, loader: "source-map-loader" }
    ]
  },

  // Other options...
};

需要注意的是,awesome-typescript-loader需要在处理.js文件的其他加载器之前运行。

这同样适用于TS-装载机,另一个打字稿装载机的WebPack。你可以在这里阅读更多关于两者之间的差异。

您可以在我们的React和Webpack教程中看到使用Webpack的示例。

移至TypeScript文件

此时,您可能已经准备好开始使用TypeScript文件。第一步是将您的一个.js文件重命名为.ts。如果您的文件使用JSX,则需要将其重命名为.tsx

完成哪一步?很好!您已经成功将文件从JavaScript迁移到了TypeScript!

当然,这可能不正确。如果您在支持TypeScript的编辑器中打开该文件(或者如果运行tsc --pretty),则可能会在某些行上看到红色的波形曲线。你应该像在Microsoft Word这样的编辑器中想像这些红色的小花一样。TypeScript仍然会翻译您的代码,就像Word仍然会让您打印文档一样。

如果这听起来太松懈,你可以收紧这种行为。例如,如果您希望TypeScript在面对错误时编译为JavaScript,则可以使用该noEmitOnError选项。从这个意义上说,TypeScript在其严格性上有一个拨号,并且可以将该旋钮向上调高。

如果您打算使用可用的更严格的设置,最好现在打开它们(请参阅下面的获取更严格的检查)。例如,如果您不希望TypeScript在any没有明确说明的情况下默默推断某个类型,则可以noImplicitAny在开始修改文件之前使用它。虽然可能感觉有些压倒性,但长期收益会变得更加迅速。

消除错误

就像我们所提到的,转换后得到错误信息并不意外。重要的是通过这些实际去一个一个去决定如何处理这些错误。通常这些都是合法的错误,但是有时候你必须解释你想要对TypeScript做的更好一点。

从模块导入

你可能会开始出现一些类似Cannot find name 'require'.和错误的错误Cannot find name 'define'.。在这些情况下,您可能正在使用模块。虽然你可以通过写出来说服TypeScript确定它们存在

代码语言:javascript
复制
// For Node/CommonJS
declare function require(path: string): any;

要么

代码语言:javascript
复制
// For RequireJS/AMD
declare function define(...args: any[]): any;

最好摆脱这些调用并使用TypeScript语法进行导入。

首先,您需要通过设置TypeScript的module标志来启用某个模块系统。有效的选项为commonjsamdsystem,和umd

如果您有以下Node/CommonJS代码:

代码语言:javascript
复制
var foo = require("foo");

foo.doStuff();

或以下RequireJS/AMD代码:

代码语言:javascript
复制
define(["foo"], function(foo) {
  foo.doStuff();
})

那么你会写下面的TypeScript代码:

代码语言:javascript
复制
import foo = require("foo");

foo.doStuff();

获取声明文件

如果你开始转换到TypeScript导入,你可能会遇到类似的错误Cannot find module 'foo'.。这里的问题是你可能没有声明文件来描述你的库。幸运的是,这很容易。如果TypeScript抱怨一个包lodash,你可以写

代码语言:javascript
复制
npm install -S @types/lodash

如果您使用的是其他模块选项commonjs,则需要将您的moduleResolution选项设置为node

之后,您将可以导入lodash而不会出现任何问题,并获得准确的完成。

从模块导出

通常,从模块导出涉及将属性添加到像“ exports或” 的值module.exports。TypeScript允许您使用顶级导出语句。例如,如果你导出了一个像这样的函数:

代码语言:javascript
复制
module.exports.feedPets = function(pets) {
  // ...
}

你可以写出如下内容:

代码语言:javascript
复制
export function feedPets(pets) {
  // ...
}

有时你会完全覆盖导出对象。这是人们用来使它们的模块立即可调用的常见模式,如下面的代码片段所示:

代码语言:javascript
复制
var express = require("express");
var app = express();

你以前可能会这样写:

代码语言:javascript
复制
function foo() {
  // ...
}
module.exports = foo;

在TypeScript中,你可以用这个export =结构对它进行建模。

代码语言:javascript
复制
function foo() {
  // ...
}
export = foo;

太多/太少的参数

有时您会发现自己调用的函数太多/很少有参数。通常,这是一个错误,但在某些情况下,您可能已经声明了一个使用该arguments对象而不是写出任何参数的函数:

代码语言:javascript
复制
function myCoolFunction() {
  if (arguments.length == 2 && !Array.isArray(arguments[1])) {
    var f = arguments[0];
    var arr = arguments[1];
    // ...
  }
  // ...
}

myCoolFunction(function(x) { console.log(x) }, [1, 2, 3, 4]);
myCoolFunction(function(x) { console.log(x) }, 1, 2, 3, 4);

在这种情况下,我们需要使用TypeScript来告诉我们的任何调用者myCoolFunction使用函数重载可以调用的方式。

代码语言:javascript
复制
function myCoolFunction(f: (x: number) => void, nums: number[]): void;
function myCoolFunction(f: (x: number) => void, ...nums: number[]): void;
function myCoolFunction() {
  if (arguments.length == 2 && !Array.isArray(arguments[1])) {
    var f = arguments[0];
    var arr = arguments[1];
    // ...
  }
  // ...
}

我们添加了两个重载签名myCoolFunction。第一个检查状态是myCoolFunction一个函数(需要一个number),然后是一个numbers 的列表。第二个说它也会使用一个函数,然后使用一个rest参数(...nums)来表示之后的任意数量的参数都必须是numbers。

顺序添加属性

有些人认为创建一个对象并立即添加属性会更美观,如下所示:

代码语言:javascript
复制
var options = {};
options.color = "red";
options.volume = 11;

TypeScript会说,你不能分配给colorvolume,因为它首先想出的类型options{}不具有任何属性。如果您将声明本身移入对象文本中,则不会出现错误:

代码语言:javascript
复制
let options = {
  color: "red",
  volume: 11
};

您还可以定义options对象文本的类型并添加类型断言。

代码语言:javascript
复制
interface Options { color: string; volume: number }

let options = {} as Options;
options.color = "red";
options.volume = 11;

或者,你可以说最简单的事情就是做options这种类型any的事情,但这对你至少是有利的。

anyObject{}

您可能会试图使用Object{}说一个值可以具有任何属性,因为Object对于大多数用途而言,它是最一般的类型。然而,实际上你想在这种情况下使用这种类型,因为它是最灵活的类型。any

例如,如果你有输入的内容,Object你将无法调用类似的方法toLowerCase()。更一般的意思通常意味着你可以少用一种类型,但any特别的是它是最普通的类型,同时还允许你对它做任何事情。这意味着你可以调用它,构造它,访问它的属性等等。请记住,无论何时使用any,你都会失去大部分TypeScript为你提供的错误检查和编辑器支持。

如果决定永远归结为Object{},你应该更喜欢{}。虽然它们大部分是相同的,但在技术上{}是比Object一些深奥的情况更普遍的类型。

获得更严格的检查

TypeScript带有特定的检查,为您提供更多的安全性和对程序的分析。一旦将代码库转换为TypeScript,您就可以开始启用这些检查以提高安全性。

没有隐含的 any

有些情况下TypeScript无法弄清楚某些类型应该是什么。为了尽可能宽松,它会决定使用这个类型any。虽然这对迁移非常有用,但使用any意味着您没有获得任何类型的安全性,并且您不会获得与其他地方相同的工具支持。您可以告诉TypeScript将这些位置标记下来,并给出该noImplicitAny选项的错误。

严格nullundefined检查

默认情况下,TypeScript假定nullundefined处于每种类型的域中。这意味着,与类型声明的任何number可能nullundefined。由于null并且undefined是JavaScript和TypeScript中的常见错误源,因此TypeScript可以strictNullChecks选择让您省却担心这些问题的压力。

strictNullChecks启用,nullundefined获得自己的类型叫nullundefined分别。只要有可能 null,就可以使用原始类型的联合类型。举例来说,如果某物可能是a number或者null,你会把这个类型写成number | null

如果你有一个TypeScript认为可能是null/ 的值undefined,但是你知道的更好,你可以使用postfix !操作符来告诉它。

代码语言:javascript
复制
declare var foo: string[] | null;

foo.length;  // error - 'foo' is possibly 'null'

foo!.length; // okay - 'foo!' just has type 'string[]'

作为strictNullChecks使用者,在使用时,您的依赖关系可能需要更新才能使用strictNullChecks

没有隐含anythis

当您在this类的外部使用关键字时,它any默认具有该类型。例如,设想一个Point类,并想象一个我们希望作为方法添加的函数:

代码语言:javascript
复制
class Point {
  constructor(public x, public y) {}
  getDistance(p: Point) {
    let dx = p.x - this.x;
    let dy = p.y - this.y;
    return Math.sqrt(dx ** 2 + dy ** 2);
  }
}
// ...

// Reopen the interface.
interface Point {
  distanceFromOrigin(point: Point): number;
}
Point.prototype.distanceFromOrigin = function(point: Point) {
  return this.getDistance({ x: 0, y: 0});
}

这与我们上面提到的同样的问题 - 我们可能很容易拼错getDistance,没有得到一个错误。出于这个原因,TypeScript可以noImplicitThis选择。当设置该选项时,TypeScript将在this没有显式(或推断)类型的情况下使用时发出错误。解决的办法是使用一个this参数在接口或函数中给出一个明确的类型:

代码语言:javascript
复制
Point.prototype.distanceFromOrigin = function(this: Point, point: Point) {
  return this.getDistance({ x: 0, y: 0});
}

扫码关注腾讯云开发者

领取腾讯云代金券