TypeScript
不是一个高深的技术,它不过是一个javascript
的超集,那么什么是超集呢?
所谓的超集 其实就是最终将你写的 TypeScript
编译成 javascript
去执行,因为浏览器上能跑的脚本语言是javascript
,这个本质要搞清楚
传统的Javascript
缺点:
1. 弱类型,不严谨
无法在编写时察觉出同一个变量的类型是否保持一致
比如:
var a = 1
// 如果这个 b 的值是“1”,字符串
var b = "1"
console.log(a+b)
结果:
2. 不依赖插件,无法感知编码书写是否出现边际错误(出现某一瞬间空值等)
特别是 ES6
之前存在全局变量,var
会给全局状态下添加属性以及污染全局加上 ES5
的变量提升作用域等混合情况,很容易导致变量查找时出现 undefined
的问题,但是这个问题需要代码运行才能报错
例如:
var a;
function test() {a = 1}
console.log(a) //undefined
console.log(window.a)//undefined
-------------------
var a;
function test() {a = 1}
test()
console.log(a) // 1
console.log(window.a) //1
像上面这种情况,如果遇到了,项目很大,排查起来还是很烦的
3. 不依赖插件,没有静态类型以及上下文检查
特别是在书写 Node.js
的时候,往往这种偏后台类型的代码,高并发场景出现一个小问题都是致命的,如果是一个超大型项目,排查问题起来非常困难
传统的javascript
这段代码,变量 a
根本就没有定义,但是根本没有报错,这种场景可以在项目中可能是右查询没有查询到,然后输出 undefined. 可是如果是在使用这个变量去做某些事情
例如:
这个 a
变量是一个用户很核心的数据,但是它是 undefined
。然后又经过若干的类型转换,被js
转换成不知道是什么的数据展示给了客户,那么炸了,可能会引起整个项目出现致命性错误直接奔溃
4. 大型项目,多人合作,如果出了 BUG
往往可能要浪费大家很多时间(摸鱼时间)
例如:
你的同事 A
写了一个模块,大概 5 个文件,一共 1000 行代码
经过 1000 行代码的处理,最终输出好几个值(变量)给了你
如下代码:
export default {
store,
checkPassWord,
applyMiddleWare,
//....
}
一个不合格的同事 给你的没有注释的代码 于是你:
一个合格的同事:
/**
@params store // 数据对象
@params checkPassWord // 检查密码函数
@params applyMiddleWare // 应用中间间
*/
export default {
store,
checkPassWord,
applyMiddleWare,
//....
}
如果你用到他的暴露对象内容特别多的时候,就要一个一个去看注释,而且关键是:
这里面每个函数的传入参数,返回的参数,注释不一定那么完整详细。
那么只有去沟通了,一旦沟通起来。时间成本上升,并且如果大家开发任务都特别紧急的时候,炸了~
于是,TypeScript
出现了
TypeScript 3.1
现已发布
最新版本文档地址 最新 TypeScript 版本文档地址
TypeScript
并不能说是一门完全全新的语言,可以说它是一个基于 javaScipt
的超集
什么是超集?其实就是原生 ES6
语法 +Type
类型
强烈建议阅读阮一峰老师的 ES6 入门
我们来看下 TypeScript
的工作方式:
全局下载 TypeScript
手动编译TS
文件变成 js
文件
npm install -g typescript
用全局安装的 typescript
来编译输出一把刚才的文件
还没有编译,现在已经开始报出问题,但是报出问题可以继续编译吗?
即使静态校验出现问题,最终还是编译成功:
这里特别注意,
TS
里面的静态类型,以及枚举等,编译成js
后是不存在的
上面并没有体现 typeScript
的特殊价值
TypeScript
的核心原则之一是对值所具有的结构进行类型检查。它有时被称做“鸭式辨型法”或“结构性子类型化”。在 TypeScript
里,接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。
// 接口名为 LabelledValue
interface LabelledValue {label: string;}
// 函数传入的参数 labelledObj 遵循 LabelledValue 接口
function printLabel(labelledObj: LabelledValue) {console.log(labelledObj.label);
}
let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);
以上代码经过 ts
编译后, 所有接口和静态类型都没有了:
function printLabel(labelledObj) {console.log(labelledObj.label);
}
var myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);
如果 ts
在代码编写阶段出现了类型的校验错误,那么会直接提示:
我将接口的 string
改成了 number
类型
我们仅仅改变了接口的类型,就立刻检验到了错误,这样不必等到开发模式下的热更新调试后再报错。
当然 你在接口定义时候,可以在变量后加上 ?
号
这样是一个可选属性
例如:
interface SquareConfig {
color?: string;
width?: number;
}
function createSquare(config: SquareConfig): {color: string; area: number} {let newSquare = {color: "white", area: 100};
if (config.color) {newSquare.color = config.color;}
if (config.width) {newSquare.area = config.width * config.width;}
return newSquare;
}
let mySquare = createSquare({color: "black"});
还有只读属性的接口定义:
interface Point {
readonly x: number;
readonly y: number;
}
你可以通过赋值一个对象字面量来构造一个 Point。赋值后,x 和 y 再也不能被改变了。
let p1: Point = {x: 10, y: 20};
p1.x = 5; // error!
用得比较多的函数类型检查
先编写接口
interface SearchFunc {(source: string, subString: string): boolean;
}
定义函数
let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {let result = source.search(subString);
return result > -1;
}
对于函数类型的类型检查来说,函数的参数名不需要与接口里定义的名字相匹配。比如,我们使用下面的代码重写上面的例子:
let mySearch: SearchFunc;
mySearch = function(src: string, sub: string): boolean {let result = src.search(sub);
return result > -1;
}
函数的参数会逐个进行检查,要求对应位置上的参数类型是兼容的。如果你不想指定类型,TypeScript
的类型系统会推断出参数类型,因为函数直接赋值给了 SearchFunc 类型变量。函数的返回值类型是通过其返回值推断出来的(此例是 false 和 true)。如果让这个函数返回数字或字符串,类型检查器会警告我们函数的返回值类型与 SearchFunc 接口中的定义不匹配。
let mySearch: SearchFunc;
mySearch = function(src, sub) {let result = src.search(sub);
return result > -1;
}
鸭式辨形法,说的是:一个动物长得看起来像鸭子,叫起来也像鸭子,那么它就可以被认为是鸭子
定义类的类型:
interface ClockInterface {
currentTime: Date;
setTime(d: Date);
}
class Clock implements ClockInterface {
currentTime: Date;
setTime(d: Date) {this.currentTime = d;}
constructor(h: number, m: number) {}}
一个接口可以继承多个接口,创建出多个接口的合成接口。
interface Shape {color: string;}
interface PenStroke {penWidth: number;}
interface Square extends Shape, PenStroke {sideLength: number;}
照本宣科写了这么多,其实这些就是
TS
的最有用的地方。文档写得比较好,建议多去看几遍,前提是一定要学好ES6
!
TS
中一定要尽量避免使用 any
类型
any
类型有太多不可预测的后果
function identity<T>(arg: T): T {return arg;}
我们给 identity
添加了类型变量T
。T 帮助我们捕获用户传入的类型(比如:number),之后我们就可以使用这个类型。之后我们再次使用了 T 当做返回值类型。现在我们可以知道参数类型与返回值类型是相同的了。这允许我们跟踪函数里使用的类型的信息。
其他的 API
可以去刷文档,下面说重点:
工程化环境:
typescript
遇上了webpack
React
官方推荐使用typescript
使用传统的 react
脚手架
在 Create React App 中使用 TypeScript
npx create-react-app my-app --typescript
typescript 遇上 webpack
切记 所有的
ts
的依赖,都必须是@types
开头的 否则用不了
配置 tsconfig.json
文件
{
"compilerOptions": {
"outDir": "./dist/",
"sourceMap": true,
"noImplicitAny": true,
"module": "commonjs",
"target": "es5",
"jsx": "react"
},
"include": ["./src/**/*"]
}
npm install --save react react-dom @types/react @types/react-dom
webpack.config.js
配置文件
module.exports = {
entry: "./src/index.tsx",
output: {
filename: "bundle.js",
path: __dirname + "/dist"
},
devtool: "source-map",
resolve: {extensions: [".ts", ".tsx", ".js", ".json"]
},
module: {
rules: [{ test: /\.tsx?$/, loader: "awesome-typescript-loader"},
{enforce: "pre", test: /\.js$/, loader: "source-map-loader"}
]
},
externals: {
"react": "React",
"react-dom": "ReactDOM"
}
};
大家可能对 externals 字段有所疑惑。我们想要避免把所有的 React 都放到一个文件里,因为会增加编译时间并且浏览器还能够缓存没有发生改变的库文件。
webpack 4.39 版配置 typeScript
TS
最基础关键的核心思想,已经介绍完了
我们不妨总结一下:
TS
最核心的优势 :
静态类型检查 + 校验, 代码并没有运行编译,就已经知道哪里有问题了,无论是变量查找还是类型错误
TS
给我们解决了什么问题
减少了开发沟通成本,打开你的代码就知道传入的是什么参数,返回什么参数。编译后代码量并没有增加
TS
给我们带来了什么麻烦
多写了很多接口,类型,一些快速开发的小项目感觉用上更麻烦。如果是比较古老的 js
插件第三方库,还用不了,要另想其他办法去支持。
大型项目,可以上ts
,还是要上ts
,中小型项目,看工期,看你是否打算在时间允许情况下尝试使用ts
。
技术本身没有好坏,长远看,弱类型语言并不是那么的友好。谷歌的 Go
语言,写法就跟 TypeScript
很像,如果想要拥有更广阔的技术视野,建议前端是可以从 TS
学起,他们的思想大都差不多。
最后,欢迎大家加入我们的segmentFault 前端交流群
,我的个人微信是:CALASFxiaotan
,加我会拉你进群哦~
群里大把小姐姐等你们~
觉得写得好,记得点个赞哦,永不脱发