Migrating from JavaScript
从JavaScript迁移
TypeScript不存在于真空中。它是以JavaScript生态系统为基础构建的,今天存在很多JavaScript。将JavaScript代码转换为TypeScript虽然有点乏味,但通常不具有挑战性。在本教程中,我们将着眼于如何开始。我们假设您已经阅读了足够的手册来编写新的TypeScript代码。
如果您希望转换React项目,我们建议首先查看React转换指南。
设置您的目录
如果你在普通的JavaScript编写,很可能是因为你没有直接运行的JavaScript,在您.js
的文件src
,lib
或dist
目录,然后运行任意的。
如果是这样的话,你写的文件将被用作TypeScript的输入,并且你将运行它产生的输出。在我们的JS到TS迁移期间,我们需要分离我们的输入文件以防止TypeScript覆盖它们。如果您的输出文件需要驻留在特定的目录中,那么这将是您的输出目录。
您可能还会在JavaScript上运行一些中间步骤,例如捆绑或使用Babel等其他转播程序。在这种情况下,您可能已经拥有了像这样设置的文件夹结构。
从这一点开始,我们假设你的目录是这样设置的:
projectRoot
├── src
│ ├── file1.js
│ └── file2.js
├── built
└── tsconfig.json
如果你有tests
你的文件夹以外的src
目录,你可能有一个tsconfig.json
在src
,一个在tests
为好。
编写配置文件
TypeScript使用一个叫做tsconfig.json
管理项目选项的文件,比如你想包含哪些文件,以及你想要执行什么样的检查。让我们为我们的项目创建一个简单的框架:
{
"compilerOptions": {
"outDir": "./built",
"allowJs": true,
"target": "es5"
},
"include": [
"./src/**/*"
]
}
这里我们给TypeScript指定一些东西:
- 读入它在
src
目录(withinclude
)中理解的任何文件。
2. 接受JavaScript文件作为输入(带allowJs
)。
3.built
(with outDir
)发出所有输出文件。
- 将较新的JavaScript结构转换为较旧的版本,如ECMAScript 5(使用
target
)。
此时,如果您尝试tsc
在项目的根目录下运行,则应该在built
目录中看到输出文件。文件的布局built
应该与布局相同src
。你现在应该有TypeScript与你的项目一起工作。
早期利好
即使在这一点上,您也可以通过TypeScript了解您的项目获得很多好处。如果你打开一个像VS Code或Visual Studio这样的编辑器,你会发现你经常可以获得一些工具支持,比如完成。您还可以通过以下选项捕获某些错误:
noImplicitReturns
这可以防止您忘记在函数结束时返回。
noFallthroughCasesInSwitch
如果你不想忘记块break
之间case
的语句,这是有帮助的switch
。
输出文件也警告可达代码和标签,您可以禁用allowUnreachableCode
和allowUnusedLabels
分别。
与构建工具集成
您的管道中可能还有一些构建步骤。也许你将某些东西连接到每个文件。每个构建工具都是不同的,但我们会尽我们所能来覆盖事物的要点。
吞
如果你以某种方式使用Gulp,我们有一个使用Gulp和TypeScript的教程,并且集成了常见的构建工具,如Browserify,Babelify和Uglify。你可以在这里阅读更多。
WebPack
Webpack集成非常简单。您可以使用awesome-typescript-loader
TypeScript加载程序,source-map-loader
以便于调试。只需运行
npm install awesome-typescript-loader source-map-loader
并将以下选项合并到您的webpack.config.js
文件中:
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确定它们存在
// For Node/CommonJS
declare function require(path: string): any;
要么
// For RequireJS/AMD
declare function define(...args: any[]): any;
最好摆脱这些调用并使用TypeScript语法进行导入。
首先,您需要通过设置TypeScript的module
标志来启用某个模块系统。有效的选项为commonjs
,amd
,system
,和umd
。
如果您有以下Node/CommonJS代码:
var foo = require("foo"
foo.doStuff(
或以下RequireJS/AMD代码:
define(["foo"], function(foo) {
foo.doStuff(
})
那么你会写下面的TypeScript代码:
import foo = require("foo"
foo.doStuff(
获取声明文件
如果你开始转换到TypeScript导入,你可能会遇到类似的错误Cannot find module 'foo'.
。这里的问题是你可能没有声明文件
来描述你的库。幸运的是,这很容易。如果TypeScript抱怨一个包lodash
,你可以写
npm install -S @types/lodash
如果您使用的是其他模块选项commonjs
,则需要将您的moduleResolution
选项设置为node
。
之后,您将可以导入lodash而不会出现任何问题,并获得准确的完成。
从模块导出
通常,从模块导出涉及将属性添加到像“ exports
或” 的值module.exports
。TypeScript允许您使用顶级导出语句。例如,如果你导出了一个像这样的函数:
module.exports.feedPets = function(pets) {
// ...
}
你可以写出如下内容:
export function feedPets(pets) {
// ...
}
有时你会完全覆盖导出对象。这是人们用来使它们的模块立即可调用的常见模式,如下面的代码片段所示:
var express = require("express"
var app = express(
你以前可能会这样写:
function foo() {
// ...
}
module.exports = foo;
在TypeScript中,你可以用这个export =
结构对它进行建模。
function foo() {
// ...
}
export = foo;
太多/太少的参数
有时您会发现自己调用的函数太多/很少有参数。通常,这是一个错误,但在某些情况下,您可能已经声明了一个使用该arguments
对象而不是写出任何参数的函数:
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
使用函数重载可以调用的方式。
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
),然后是一个number
s 的列表。第二个说它也会使用一个函数,然后使用一个rest参数(...nums
)来表示之后的任意数量的参数都必须是number
s。
顺序添加属性
有些人认为创建一个对象并立即添加属性会更美观,如下所示:
var options = {};
options.color = "red";
options.volume = 11;
TypeScript会说,你不能分配给color
和volume
,因为它首先想出的类型options
为{}
不具有任何属性。如果您将声明本身移入对象文本中,则不会出现错误:
let options = {
color: "red",
volume: 11
};
您还可以定义options
对象文本的类型并添加类型断言。
interface Options { color: string; volume: number }
let options = {} as Options;
options.color = "red";
options.volume = 11;
或者,你可以说最简单的事情就是做options
这种类型any
的事情,但这对你至少是有利的。
any,Object和{}
您可能会试图使用Object
或{}
说一个值可以具有任何属性,因为Object
对于大多数用途而言,它是最一般的类型。然而,实际上你想
在这种情况下使用这种类型
,因为它是最灵活的
类型。any
例如,如果你有输入的内容,Object
你将无法调用类似的方法toLowerCase()
。更一般的意思通常意味着你可以少用一种类型,但any
特别的是它是最普通的类型,同时还允许你对它做任何事情。这意味着你可以调用它,构造它,访问它的属性等等。请记住,无论何时使用any
,你都会失去大部分TypeScript为你提供的错误检查和编辑器支持。
如果决定永远归结为Object
和{}
,你应该更喜欢{}
。虽然它们大部分是相同的,但在技术上{}
是比Object
一些深奥的情况更普遍的类型。
获得更严格的检查
TypeScript带有特定的检查,为您提供更多的安全性和对程序的分析。一旦将代码库转换为TypeScript,您就可以开始启用这些检查以提高安全性。
没有隐含的 any
有些情况下TypeScript无法弄清楚某些类型应该是什么。为了尽可能宽松,它会决定使用这个类型any
。虽然这对迁移非常有用,但使用any
意味着您没有获得任何类型的安全性,并且您不会获得与其他地方相同的工具支持。您可以告诉TypeScript将这些位置标记下来,并给出该noImplicitAny
选项的错误。
严格null和undefined检查
默认情况下,TypeScript假定null
并undefined
处于每种类型的域中。这意味着,与类型声明的任何number
可能null
或undefined
。由于null
并且undefined
是JavaScript和TypeScript中的常见错误源,因此TypeScript可以strictNullChecks
选择让您省却担心这些问题的压力。
当strictNullChecks
启用,null
并undefined
获得自己的类型叫null
和undefined
分别。只要有可能
null
,就可以使用原始类型的联合类型。举例来说,如果某物可能
是a number
或者null
,你会把这个类型写成number | null
。
如果你有一个TypeScript认为可能是null
/ 的值undefined
,但是你知道的更好,你可以使用postfix !
操作符来告诉它。
declare var foo: string[] | null;
foo.length; // error - 'foo' is possibly 'null'
foo!.length; // okay - 'foo!' just has type 'string[]'
作为strictNullChecks
使用者,在使用时,您的依赖关系可能需要更新才能使用strictNullChecks
。
没有隐含any的this
当您在this
类的外部使用关键字时,它any
默认具有该类型。例如,设想一个Point
类,并想象一个我们希望作为方法添加的函数:
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
参数在接口或函数中给出一个明确的类型:
Point.prototype.distanceFromOrigin = function(this: Point, point: Point) {
return this.getDistance{ x: 0, y: 0}
}