TypeScript是一种由微软开发的自由和开源的编程语言。它是JavaScript的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。
它可以编译为JavaScript。是一种给JavaScript添加特性的语言扩展。它拥有以下特性:
TypeScript的设计目的应该是解决JavaScript的“痛点”:弱类型和没有命名空间,导致很难模块化,不适合开发大型程序。另外它还提供了一些语法糖来帮助大家更方便地实践面向对象的编程。
ts是angular的默认开发语言,在即将面世的vue3也将使用js。
官方中文文档地址:
https://www.tslang.cn/index.html
npm install -g typescript
创建一个 hello.ts
:
function greeter(person) {
return "Hello, " + person;
}
let user = "Dang Jingtao";
document.body.innerHTML = greeter(user);
完全支持es5/6的写法。
在命令行终端运行:
tsc hello.ts
编译完成后,生成了一个同名的js文件,这就是ts编译出来的JavaScript。
在 greeter.html
里输入如下内容:
<!DOCTYPE html>
<html>
<head><title>TypeScript Greeter</title></head>
<body>
<script src="hello.js"></script>
</body>
</html>
把你编译好的 hello.js
文件在浏览器里打开 greeter.html
运行这个应用即可。
类型推论——灵活的强类型语言:声明一个变量,但你没赋值时,一定要写类型。
let name1:string;
let name2='';
以上都默认声明了字符串的变量。都可以!
你也可以声明一个全部由字符串组成的变量:
let arr:string[]
如果你尝试给arr push一个数字,就会报错。
因此你只能 arr.push('1')
对于可能变化的类型变量;
可以用或符号 |
:
let foo:number|string;//可以是数字或字符串
也可以定义为 any
let foo:any;//
let arr:any[];//数组成员可以是任意类型
在看下官网的案例:
function greeter(person: string):string {
return "Hello, " + person;
}
在参数之外加一个 :string
表示期望返回值是个字符串。如果传进去的person是个数组,就报错。
如果我这段函数只是处理一个业务,不需要返回值,那可以这么写:
function greeter(person: string):void {
alert(person)
}
ts常见的内置类型(开头都是小写): string,number,boolean,any,void
现在看一个小学英语难度的案例:
function sayHi (name:string,age:number):string{
return `hi! ${name}, i am ${age} years old`;
}
// sayHi('djtao') 报错
这个案例中,name和age是必填参数。
那么需要是可选参数,加上问号就行了。
function sayHi (name:string,age:number,addr?:string):string{
return `hi! ${name}, i am ${age} years old`;
}
那如果我需要设置一个默认参数呢?比如默认30岁:
function sayHi (name:string,age:number=30,addr?:string):string{
return `hi! ${name}, i am ${age} years old`;
}
也支持es6特性
如果一个函数,根据参数的数量,类型的不同,返回不同类型的数据,称为函数重载。
function foo(a:{name:string}):string;
function foo(a:string):object;
function foo(a:string|{name:string}):any{
if(typeof a == 'object'){
return a.name;
}else if(typeof a== 'string'){
return {name:a};
}
}
等效于:
function foo(a) {
if (typeof a == 'object') {
return a.name;
}
else if (typeof a == 'string') {
return { name: a };
}
}
关于接口,你可以描述为定义了属性和类型,但是没有定义其它任何东西的构造函数。
这里我们使用接口来描述一个拥有 firstName
和 lastName
字段的 Person
对象。 在TypeScript里,只在两个类型内部的结构兼容那么这两个类型就是兼容的。 这就允许我们在实现接口时候只要保证包含了接口要求的结构就可以,而不必明确地使用 implements
语句。
interface Person {
firstName: string;
lastName: string;
}
function greeter(person: Person) {
return "Hello, " + person.firstName + " " + person.lastName;
}
let user = { firstName: "Jane", lastName: "User" };
document.body.innerHTML = greeter(user);
在前后端联调时,接口可以很好的对数据类型进行规范。
最后,再看看ts中无处不在的类。
TypeScript支持JavaScript的新特性,比如支持基于类的面向对象编程。
让我们创建一个 Student
类,它带有一个构造函数和一些公共字段。 注意类和接口可以一起工作,程序员可以自行决定抽象的级别。
还要注意的是,在构造函数的参数上使用 public
等同于创建了同名的成员变量。
class Student {
fullName: string;
constructor(public firstName, public middleInitial, public lastName) {
this.fullName = firstName + " " + middleInitial + " " + lastName;
}
}
// 如同格式配置
interface Person {
firstName: string;
lastName: string;
}
function greeter(person : Person) {
return "Hello, " + person.firstName + " " + person.lastName;
}
let user = new Student("Jane", "M.", "User");
document.body.innerHTML = greeter(user);
重新运行 tsc greeter.ts
,你会看到生成的JavaScript代码和原先的一样。 TypeScript里的类只是JavaScript里常用的基于原型面向对象编程的简写。
ts同样也具有多态特性:
class Shape{
area:number //如果想要子类用不了,就用private修饰
protected color:string //子类可以用
constructor(color:string,width:number,height:number){
this.area=width*height;
this.color=color;
}
shoutout(){
return "I'm "+this.color+" with an area of "+this.area+" cm squared."
}
}
class Square extends Shape{
constructor(color:string,side:number){
super(color,side,side);
console.log(this.color)
}
// 覆盖了父类的方法,子类调用的是汉化的shoutout方法
shoutout(){
return "我是"+this.color+",面积为"+this.area+"平方厘米。"
}
}
const square=new Square('red',2);
console.log(square.shoutout()) //我是red,面积为4平方厘米。
修饰符的使用——上面的代码也可这么写:
class Shape{
constructor(protected color:string,private width:number,private height:number){
}
// 读取area时,执行的事情。
get area(){
console.log('面积被读取了!')
return this.width*this.height;
}
shoutout(){
return "I'm "+this.color+" with an area of "+this.area+" cm squared."
}
}
class Square extends Shape{
constructor(color:string,side:number){
super(color,side,side);
console.log(this.color)
}
// 覆盖了父类的方法,子类调用的是汉化的shoutout方法
shoutout(){
return "我是"+this.color+",面积为"+this.area+"平方厘米。"
}
}
const square=new Square('red',2);
console.log(square.shoutout())
// red
// 面积被读取了!
// 我是red,面积为4平方厘米。
area就成了计算属性。
泛型就是说,在定义函数,接口或类的时候,不预先指定类型,而是等到使用时才?️。
具体应用见第三章。
创建项目时,执行
vue create vue-ts
接下来会有一段默认选项,可按照以下配置
创建好项目之后发现整个应用都怪怪的:
执行:
npm run serve
即可编译查看。
3.1 项目变化
在根目录下,多了一个 tslint.json
,这是对编程偏好的设置。
还多了一个 tsconfig.json
:它反映的是项目需要编译那个js版本,哪些库,编译目录等。
在 Home.vue
中:
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import HelloWorld from '@/components/HelloWorld.vue'; // @ is an alias to /src
@Component({
components: {
HelloWorld,
},
})
export default class Home extends Vue {}
</script>
主要变化的还是script部分的编码:
结尾需要声明一个类,说明继承于vue:
export default class Home extends Vue {}
开头声明组件,需要一个装饰器 @
@Component({
components: {
HelloWorld,
},
})
由此可见,component下面的都是配置文件。
装饰器 @
就是一个工厂函数。
你也可以这么写:
@Component({
props:{
name:{
tyoe:String,
default:'djtao'
}
}
})
在组件 Helloworld.vue
中:
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
@Component
export default class HelloWorld extends Vue {
@Prop() private msg!: string;
}
</script>
@Prop()privatemsg!:string;
表示: msg
的类型必须存在(!)且必须是字符串,而且为 Helloworld
私有。
创建一个 Hello.vue
组件,
在Home.vue中导入Hello:
<template>
<div class="home">
<Hello msg="hello"/>
</div>
</template>
import Hello from '@/components/Hello.vue';
在hello里这么写:
<template>
<div>{{msg}}</div>
</template>
<script lang="ts">
import {Component,Prop,Vue} from 'vue-property-decorator';
@Component
export default class Hello extends Vue {
@Prop({required:true}) private msg!:string;
}
</script>
写到这里,命令行里已经报很多警告了。但没报错就先不管。
msg!
表示告诉编译器,父类会传属性。但实际上不会报错。而 ({required:true})
表示告诉vue,必须检验这个值是否存在。因此二者必须一起写。
传统的data应该怎么写?直接写。
export default class Hello extends Vue {
@Prop({ required: true }) private msg!: string;
featrues = ['类型注释', '函数'];
}
引起异常的舒适。
现在要给hello加点功能:
<template>
<div>
<input type="text" @keyup.enter="addFeatrue($event)">
<ul v-for="featrue in featrues" :key="featrue.index">
<li>{{featrue}}</li>
</ul>
</div>
</template>
直接在component配置写方法就行了:
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
@Component
export default class Hello extends Vue {
@Prop({ required: true }) private msg!: string;
featrues = ['类型注释', 'balabala'];
addFeatrue(e:any){
this.featrues.push(e.target.value);
e.target.value='';
}
}
</script>
实现是异常简单。
目前这个组件根本没有定义数据是私有或是共有
所有的属性都包括hello里面,对此
protected
public
在hello.vue页面中需要一个计算属性。比如说features的条数
// 计算属性
get count(){
return this.featrues.length;
}
private created(){
window.setTimeout(()=>{
this.featrues.push('aaa')
},2000)
}
简单到无话可说。
interface Featrue{
id:number,
name:string,
version:string
}
那么:
featrues:Featrue[]=[{id:1,name:'功能',version:'1'}, ...];
定义泛型Result:
interface Result<T> {
ok:0|1,
data:T[]
}
使用泛型约束接口返回的类型。
function getData<T>():Result<T> {
const data:any[]=[
{id:1,name:'类型注释',version:'1.0'}, {id:2,name:'bala',version:'1.0'}
]
return {ok:1,data}
}
使用时:
private created() {
this.featrues=getData<Featrue>().data;
}
在这里调用类似匿名类型的T作为别名。掉接口后,明确泛型为 <result>
子组件派发事件给父组件,通常是用 emit
。在ts'中,为 @Emit
(需要导入)
// 通知老爹要发生addFeatrue事件
@Emit
private addFeatrue(e,any){
this.featrues.push({id:this.featrues.length,name:e.target.value,version:'1.0'});
e.target.value='';
}
// 父组件
<Hello @add-featrue="onAddFeatrue">
@Watch('msg',{deep:true});
onChange(val:string,oldVal:string){
// do sth
}