# TypeScript 基础
安装 npm install -g typescript
运行 tsc xx.ts
# 基础类型
js 是弱类型,ts 是强类型
布尔值 数字 字符串 数组 元组 枚举 any void null undefined Never object
-
类型案例
/* function greeter(person: string) { return "Hello, " + person; } let user = "毛茸茸"; // 如果改成了 数字1,下边的user会报错 document.body.innerHTML = greeter(user); */ // 使用 :类型 规定数据类型 // 布尔值 let bool: boolean = true; bool = false; // 数字 let num: number = 6; // 字符串 let username: string = "bob"; username = "cxh"; // 数组 // 数字数组 let arr: number[] = [1, 2, 3]; // arr[0] = '1'; //错误写法 arr[0] = 1; // 字符串数组 // let arr1: string[] = ["a", "b", "c"]; // 数组泛型写法 let arr1: Array<string> = ["a", "b", "c"]; // 元组 // 元组类型允许表示一个已知元素数量和类型的数组 let x: [string, number] = ["hello", 100]; x[1] = 2; // x[1] = '2';// 错误写法 // 越界的元组数据,默认可以使用联合类型替代 // 比如 x[3] 可以赋值成 数字或字符串 // 枚举 enum // 枚举数据类型 Color // 需要先创建一个枚举类型的数据 // 通过编号能拿到名字 通过名字能拿到编号 enum Color { Red, Green, Blue, } let c: Color = Color.Green; console.log(c); //1 console.log(Color); /* {0: "Red" 1: "Green" 2: "Blue" Blue: 2 Green: 1 Red: 0} */ // console.log(Color[1]); // Green // enum Color {Red = 1, Green, Blue} // 编号就成了1,2,3 // enum Color {Red = 1, Green = 2, Blue = 4} // 编号自定义 // any(任何一种类型) 赋值的时候可以随意赋值 // let notSure: any = 0; // notSure = "嘿嘿嘿"; let list: any[] = [1, true, "free"]; list[1] = 100; // void 无类型,和 any 相反 // 一般只有在函数的返回值类型设置 void // 或者创建 undefined 和 null 的时候使用 // undefined 和 null // 很少声明 null 或 undefined 类型 // undefined 和 null 可以赋值给任意类型 let n: undefined = undefined; let u: null = null; let str = "123"; let str = undefined; // 不会报错 // Never // 基本用不到 // object // 很少用下面的方式创建对象 /* let obj: object = { a: 10, b: 20, }; */ // 类型断言 // let someValue: any = "this is a string"; // let strLength: number = (<string>someValue).length; // <string>someValue 就是类型断言 将 someValue 当成 string 类型 // 另一种写法 let someValue: any = "this is a string"; let strLength: number = (someValue as string).length; // 两种形式是等价的。 至于使用哪个大多数情况下是凭个人喜好; // 然而,当你在TypeScript里使用JSX时,只有 as语法断言是被允许的。 -
异步输出小案例
for (var i = 0; i < 5; i++) { // var 改成 let 之后 结果变成了 0,1,2,3,4 // 因为 let 是块级作用域,每个 i 都有一个 setTimeout setTimeout(function () { // 循环执行完毕之后执行setTimeout // 因为同步先执行,异步后执行 console.log(i); }, 100 * i); // 100 * i 相当于函数调用 从0开始 } // 5个5 间隔 100毫秒 依次触发
# 类型推论
在有些没有明确指出类型的地方,类型推论会帮助提供类型
-
案例
// 类型推论 // 当没有给变量声明类型,却赋值了,那么 ts 会自动根据值,给这个变量提供对应的类型(类型声明) let number = 2; // number 被推论成了数字类型 // 等同于 // let number: number = 2; let xxxx = [0, 1, null]; // xxxx 被推论成数字数组,并不是数字或者 null 数组 // 因为 ts 底层会默认选择一个最佳的通用类型
# 函数
-
案例
// 函数 /* function add(x, y) { return x + y; } const z = add(1, 2); */ // 为函数定义类型 // 1. 参数类型 x: number, y: number // 2. 返回值类型 add(x: number, y: number): number(这里的number是返回值类型) // 3. 整个函数的类型 // 定义了参数类型和返回值类型,一般这样写 function add(x: number, y: number): number { return x + y; } const z: number = add(1, 2); // 返回值无类型,也就是 void 类型 // void 可省略 let showInfo = (info: string): void => { console.log(info); }; // ? 表示可选参数(可传可不传) function buildName(firstName: string, lastName?: string) { return lastName ? firstName + " " + lastName : firstName; } const fullName = buildName("李"); console.log(fullName); // 李 // 函数参数默认值 function buildName1(firstName: string, lastName = "明") { return firstName + " " + lastName; } const fullName1 = buildName1("张"); console.log(fullName1); // 张 明 // 剩余参数 const fun = (...rest: any[]) => { console.log(rest); //(6) [1, 2, 3, 4, true, "hello"] }; fun(1, 2, 3, 4, true, "hello");
# 接口
对值所具有的结构进行类型检查
-
案例
// 对象的详细类型定义 const obj: { username: string; password: string } = { username: "cxh", password: "123", }; // 用接口定义对象的规则 // 接口名称首字母一般大写 interface UserObj { // 必须要有age属性,且类型必须是数字 age: number; // 可选属性 sex,类型为字符串 // 只读属性 readonly,不可修改 readonly sex?: string; // 任意的属性名 任意的值 [propName: string]: any; } // 这个函数接收的参数是对象,而且对象里面必须有一个 age 属性,属性值是数字 // 函数的参数需要满足 UserObj // 是一个对象,里边有age属性,且属性类型为数字类型 function printAge(userObj: UserObj) { console.log(userObj.age); } // myObj 对象也需要满足我们定义的接口 let myObj: UserObj = { sex: "男", age: 10, hobby: ["篮球"] }; printAge(myObj); interface Todo { id: string; text: string; isDone?: boolean; } const todos: Todo[] = [ { id: "111", text: "222", }, ]; interface SquareConfig { color?: string; width?: number; } function createSquare(config: SquareConfig): { color: string; area: number } { // config是参数,SquareConfig是参数类型 // { color: string; area: number }是函数设置的返回值,返回的是一个对象 const width = config.width && 100; return { color: config.color && "orange", area: width * width, }; } // 正常应该不检查 colour,但是因为 ts 有额外的类型检查,相似的属性名会进行额外的检查, // 解决方法是使用类型断言 // let mySquare = createSquare({ colour: "orange", width: 100 }); // 解决方法:利用类型断言绕过额外的检查, // 告诉SquareConfig我的colour不是你的color,你继续用你的color就行 let mySquare = createSquare({ colour: "orange", width: 100 } as SquareConfig); // console.log(mySquare);//{color: undefined, area: 10000}
# 类
-
简单案例
class Cat { // 类的属性需要使用属性的新写法,对属性进行类型声明 type = "猫科类"; catAge: number = 1; // 相当于初始值 catName: string; //初始值 // new Greater()是实例化 的时候自动执行 constructor 函数 constructor(catName: string, catAge: number) { this.catName = catName; this.catAge = catAge; // this.type = "猫科类"; // 也可以在上边写 } say() { console.log("喵喵喵"); } } const catOne = new Cat("大花", 2); console.log(catOne); //Cat {catName: "大花", catAge: 2, type: "猫科类"} -
继承案例
class Animal { name: string; age: number; // 公有方法 constructor(name: string, age: number) { this.name = name; this.age = age; } // 不能加逗号 move(distance: number = 0) { console.log(`${this.name} moved ${distance}m.`); } } const hh = new Animal("老虎", 5); // 这里的原型对象是 constructor 和 move console.log(hh); //Animal {name: "老虎", age: 5} hh.move(100); //老虎 moved 100m. // 创建一个 Animal 的子类 Cat // extends 实现的是 // 1. super 关键字:将继承的类的 constructor 在子类的 constructor 内执行, // 并且将函数内部的 this 替换成子类的 this // 2. 将继承的类的原型对象给子类的原型的 __proto__ // 当实例化对象访问一个属性的时候 // 先找 constructor 然后找 类的实例化对象 // 然后再看有没有继承,有继承的话 去类的实例化对象的 __proto__ // 当找到最后一层 __proto__ 指的就是 Object 的构造函数的原型(鼻祖), // 再往上 __proto__ 就是 null // 实例对象的 __proto__ 指的是创建这个实例对象的构造函数(类)的原型对象 // 原型对象的 __proto__ 指的是继承的构造函数(类)的原型对象 class Cat extends Animal { type = "猫科类"; constructor(name: string, age: number) { super(name, age); } say() { console.log("喵喵喵"); } } const catOne = new Cat("小花", 2); console.log(catOne); // Cat {name: "小花", age: 2, type: "猫科类"} // console.log(catOne.__proto__);// 指向的Cat,constructor是Cat本身,有say方法 // __proto__.__proto__指向的是 Animal,下边有move方法和constructor(是Animal本身) catOne.say(); // 喵喵喵 catOne.move(10); // 小花 moved 10m. // // 类 class Point { x: number; y: number; } // 用类的类型声明一个变量 const pointA: Point = { x: 10, y: 20, }; // 接口 // 接口继承类 interface Point3d extends Point { z: number; } let point3d: Point3d = { x: 1, y: 2, z: 3 }; // 接口也可以继承接口,使用 extends 关键字 interface Shape { color: string; } interface PerStroke { penWidth: number; } interface Square extends Shape, PerStroke { sideLength: number; } const square: Square = { color: "orange", penWidth: 100, sideLength: 4, }; // 接口的联合类型 // 既有对象又有函数 interface Counter { // 函数类型 (start: number): string; // 属性 interval: number; // 方法 reset(): void; } // Counter返回值类型,是接口 function getCounter(): Counter { // 类型断言 把函数断言成了 接口 Counter let counter = <Counter>function (start: number) {}; counter.interval = 123; counter.reset = function () {}; return counter; } let xx = getCounter(); xx(10); xx.reset(); xx.interval = 5.0;
# 泛型
-
案例 1
// <T>:泛型 T 可以换成其他的名字,选T的意思是Type的意思 // 一旦泛型规定了某个类型,那么后续的所有泛型都是这个类型 // 一般在变量名之后定义某个泛型 `变量名<T>`:定义了一个泛型T(T以后就代表一个类型了) function identity<T>(arg: T): T { return arg; } // 要求 该函数参数和返回值同一个类型,不能用 any // 使用泛型解决 // 泛型的使用 // const res = identity<string>("hello world"); // 泛型如果不写类型的话会进行类型推论 // identity(myString),推论成myString类型 const res = identity(1); -
案例 2
// 多个泛型参数的函数:函数中有多个泛型的参数 function getMsg<K, V>(value1: K, value: V): [K, V] { return [value1, value2]; } const arr1 = getMsg<string,number>('jack',100.2345) console.log(arr1[0].split('')) // ['j','a','c','k'] console.log(arr1[1].toFixed(1)) // "100.2"
# 泛型接口
在定义接口时,为接口中的属性或方法定义泛型类型
在使用接口时,再指定具体的泛型类型
-
案例
interface IbaseCRUD <T> { data: T[] // data: Array<T> 这种写法也可以 add: (t: T) => void getById: (id: number) => T } class User { id?: number; //id主键自增,?代表该属性可有可无 name: string; //姓名 age: number; //年龄 constructor (name, age) { this.name = name this.age = age } } // 类和泛型接口用 implements class UserCRUD implements IbaseCRUD <User> { data: User[] = [] // data:Array<User> = [] 这种写法也可以 add(user: User): void { user = {...user, id: Date.now()} this.data.push(user) console.log('保存user', user.id) } getById(id: number): User { return this.data.find(item => item.id===id) } } const userCRUD:UserCRUD = new UserCRUD() userCRUD.add(new User('tom', 12)) userCRUD.add(new User('tom2', 13)) console.log(userCRUD.data) // [User,User] const {id} = userCRUD.add(new User('tom3', 14)) const res = userCRUD.getById(id)
# Vue 中使用 ts, 装饰器
装饰器:让类变的更简单,更容易操作
主要创建的是类组件,使用了装饰器语法,使用了两个官方推荐的
vue-class-component 以及 vue-property-decorator
配置代码片段:编辑器 ->f1-> 首选项:配置用户代码片段 ->vue
-
配置代码片段案例
{ // Example: "vue script ts": { "prefix": "vts", // 按什么出代码片段 "body": [ "<script lang='ts'>", "import { Component, Vue } from 'vue-property-decorator'", "@Component$1", "export default class ${TM_FILENAME_BASE} extends Vue {$2}", "</script>" ], "description": "Log output to vue script lang=ts" } } -
装饰器语法的使用
-
Todo 案例
-
App.vue
<template> <div id="app"> <TodoWrap title="Vue Ts Todo" :count.sync="count" /> </div> </template> <script lang="ts"> import { Component, Vue } from "vue-property-decorator"; import TodoWrap from "./components/TodoWrap.vue"; //要加后缀 @Component({ components: { TodoWrap, }, }) export default class App extends Vue { // 定义 data // count 相当于之前 data 内的 count count = 100; } </script> -
TodoWrap.vue
<template> <div class="todo-wrap"> <h1>{{ title }}</h1> <p>{{ syncedCount }}</p> <button @click="changeCount">按钮</button> </div> </template> <script lang="ts"> import { Component, Vue, Prop, PropSync } from "vue-property-decorator"; @Component // TodoWrap 就是类 export default class TodoWrap extends Vue { // Prop 用法: // 需要从 vue-property-decorator 导入 Prop 装饰器 // Prop 装饰器的写法 // @Prop()是装饰器 // @Prop(Number) readonly propA: number | undefined // @Prop({ default: 'default value' }) readonly propB!: string // @Prop([String, Boolean]) readonly propC: string | boolean | undefined // @Prop({ default: "todo title" }) // 它是之前prop的写法(写东西就是props:{},不写东西就是props:[]),后面的是类的写法 // title!: string; 的意思是 非空断言(告诉编译器有值) !:非空声明断言声明 // @Prop(String)是接收过来的东西是String类型 ,title是类里变的一个值 @Prop(String) title!: string; // @PropSync('name', { type: String }) syncedName!: string // @PropSync 装饰器 // 下面代码相当于之前的 /* props: { count: { type: Number; }; }, computed:{ syncedCount:{ get(){ return this.count }, set(value){ this.$emit('update:count',value) } } } */ @PropSync("count", { type: Number }) syncedCount!: number; // 以前的 methods 里面的函数直接在类里面声明即可 changeCount() { this.$emit("update:count", 200); } } </script>
-
-
自定义事件
-
App.vue
<template> <div id="app"> <TodoWrap title="Vue Ts Todo" :count.sync="count" @change-count1="handleClick" /> <!-- @change="handleClick" --> </div> </template> <script lang="ts"> import { Component, Vue } from "vue-property-decorator"; import TodoWrap from "./components/TodoWrap.vue"; //要加后缀 @Component({ components: { TodoWrap, }, }) export default class App extends Vue { count = 100; handleClick(value: number) { console.log(value); this.count = value; } } </script> -
ToDoWrap.vue
<template> <div class="todo-wrap"> <div> <p>{{ syncedCount }}</p> <button @click="changeCount(500)">按钮</button> <button @click="changeCount1(300)">按钮</button> </div> <hr /> <h1>{{ title }}</h1> <TodoHeader v-model="text" /> </div> </template> <script lang="ts"> import { Component, Vue, Prop, PropSync, Emit, } from "vue-property-decorator"; import TodoHeader from "./TodoHeader.vue"; @Component({ components: { TodoHeader, }, }) export default class TodoWrap extends Vue { text = "123"; @Prop(String) title!: string; @PropSync("count", { type: Number }) syncedCount!: number; // 使用 @Emit 接收父组件传递的自定义事件 // 接收到的事件和我们定义的事件会自动合并 // 重名的自定义事件接收 @Emit() // 重名接收方式 // @Emit("change") // 不重名的接收方式 @Emit("传过来的名字") // changeCount1 名字必须和父组件传递的自定义事件名称相同 // 注意父组件传递的名称写法是烤串写法 xx-xx // 接收的时候可以使用小驼峰 changeCount1(newCount: number) { console.log(111); // @Emit() 作用就是将自定义事件合并到了组件内的函数内 // this.$emit("change-count1", 200); } // 装饰器就是和父组件对接的,函数就是我自己的, // 要紧跟着 @Emit() 和 它下面的函数是一组 @Emit("update:count") // 之前的 methods 里面的函数直接在类里面声明即可 changeCount(value: number) {} } </script>
-
-
v-model (重点)
-
TodoHeader.vue
<template> <div class="todo-header"> <input type="text" v-model="text" /> <button @click="changeText('hhh')">修改父组件的 text</button> <button>提交</button> </div> </template> <script lang="ts"> import { Component, Vue, Model, Emit, ModelSync, } from "vue-property-decorator"; @Component({ // updated() { // console.log(this.text); // }, }) export default class TodoHeader extends Vue { // @Model('事件名',{prop的类型检验}) value!:string:用 value 接收 prop // 事件名的作用是修改传递过来的事件的名 默认是 input 事件可以修改成 change 事件 // prop 校验类型 可以写成字符串或者布尔值 @Model("change", { type: String }) value!: string; // 使用 @Emit() 接收父组件利用 v-model 传递的自定义事件 // 因为使用 @Model 将事件名修改成了 change,接收的时候也用 change @Emit("change") changeText(value: string) {} // 因为 v-model 会向下传递一个 value 的 prop 和 一个自定义事件 // @ModelSync 接收 value 和 change 事件,制作成计算属性 text, // 并且在计算属性的 set 内执行了 change 事件,自动修改了父组件的 data @ModelSync("value", "change", { type: String }) readonly text!: string; // @ModelSync 把 prop 接受完之后制作成计算属性,而且把计算属性里面的东西都修改了 } </script>
-
-
监听,防抖,计算属性,Hooks
<template> <div id="app"> <input type="text" placeholder="请输入你的问题" v-model="question" /> <p>问题的长度是:{{ questionNum }}</p> <p>答案:{{ answer }}</p> </div> </template> <script lang="ts"> import { Component, Vue, Watch } from "vue-property-decorator"; import TodoWrap from "./components/TodoWrap.vue"; @Component({ computed: { // answer() { // // 根据 question 去向服务器发请求然后得到答案更新 answer // return this.data; // }, // 计算属性中一般不写异步操作 }, }) export default class App extends Vue { question = ""; answer = ""; // 计算属性的 get get questionNum() { return this.question.length; } // 计算属性的 set set questionNum(value) { console.log(value); } // Hooks 就是生命周期(就是类里边的方法) created() { console.log("我是生命周期"); console.log(this.question); } // @Watch 的使用 // @Watch('监听的数据',{监听的配置可以设置 immediate 和 deep}) // @Watch 紧跟着的函数就是数据的监听函数,数据发生改变就会触发 // 一个数据可以有多个监听 @Watch("question", { immediate: true }) onQuestionChanged(newValue: string, oldValue: string) { // console.log("初始化时候监听" + newValue); this.getAnser(newValue); // newValue 是传递的参数 // 防抖:滚动条触发次数太多,只触发一次 // 节流 } // 根据 question 发请求更新 answer getAnser(question: string) { // question: string 是接收参数 setTimeout(() => { const questionLen = question.length; this.answer = questionLen === 0 ? "" : questionLen % 2 === 0 ? "是" : "否"; }, 200); } /* @Watch("question") onQuestionChanged2(newValue: string, oldValue: string) { console.log("普通的监听" + newValue); } */ } </script>
-
# Model 系列
-
App.vue
<template> <div class="app"> <ModelDemo v-model="message" /> </div> </template> <script lang="ts"> import { Component, Vue } from "vue-property-decorator"; import ModelDemo from "./components/ModelDemo.vue"; @Component({ components: { ModelDemo, }, }) export default class App extends Vue { message = "hello"; } </script> -
ModelDemo.vue
<template> <div> <h3>@Model @ModelSync @VModel装饰器</h3> <!-- 都可以接收 v-model 的值 --> <!-- <p>{{ message }}</p> <button @click="changeMessage('world')">修改</button> --> </div> </template> <script lang="ts"> import { Component, Vue, Model, Emit, ModelSync, } from "vue-property-decorator"; @Component export default class ModelDemo extends Vue { // 将 v-model 传递的给到 message /* @Model("xxx", { type: String }) message!: string; // 能改事件名(xxx) // 用 @Emit() 去接收事件 @Emit("xxx") changeMessage(str: string) { console.log(str); } */ // xxx 表示的是这个组件接收到的 prop 名 // aaa 表示的是这个组件接收到的 自定义事件名 // message 表示的是这个组件的计算属性 // 这个属性有 get 和 set // get 的返回值就是刚才接收到的 prop(xxx) set 执行的就是刚才接收的自定义事件(aaa) @ModelSync("xxx", "aaa", { type: String }) message!: string; // @VModel 用的就是默认传递的 value 和 事件 // @ModelSync 可以修改 自定义事件名,@VModel 用的是默认的,不可以修改事件名 } </script>
# ts 中用 Vuex
vue add vuex
会自动帮你做一些内容 (创建一个 store->index.ts, 且里边写好内容)
vue cli 3+ 支持 add, 去看 package.json 中看有没有 vue cli
-
案例
-
App.vue
<template> <div class="app"> <h3>测试 vuex</h3> <!-- <p>{{ myCount }}</p> --> <p>{{ count }}</p> <!-- <button @click="changeCount(300)">修改 state count</button> --> <button @click="setCount(300)">修改 state count</button> <ul> <li v-for="todo in list" :key="todo.id">{{ todo.text }}</li> </ul> <button @click="add">添加todo</button> </div> </template> <script lang="ts"> import { Component, Vue } from "vue-property-decorator"; import ModelDemo from "./components/ModelDemo.vue"; import { State, Mutation, namespace } from "vuex-class"; import { TodoItem } from "./interfaces"; const todosModule = namespace("todos"); // 模块 @Component({ components: { ModelDemo, }, }) export default class App extends Vue { // @State("count") 去拿 store 中的 count // @State("count") myCount!: number; // @State((state) => state.count) count!: number; @State count!: number; @Mutation setCount!: Function; /* @Mutation("setCount") changeCount(value: number) {} */ // 这个函数相当于给 index.ts 中的 setCount 传 // @State((state) => state.todos.todos) list!: TodoItem[]; // 导入模块 @todosModule.State("todos") list!: TodoItem[]; @todosModule.Mutation("addTodo") addTodo!: Function; add() { this.addTodo({ id: "1222", text: "艰苦艰苦", isDone: false }); } } </script> -
index.ts
import Vue from "vue"; import Vuex from "vuex"; import todos from "./modules/todos"; Vue.use(Vuex); // vue + ts+ vuex // vuex 的语法也要借助其他的包支持 ts 语法 // vuex-class(没有模块用这个包) // vuex-module-decorators(全是模块写法用这个) export default new Vuex.Store({ state: { count: 100, }, modules: { todos, }, mutations: { setCount(state, new_count: number) { state.count = new_count; }, }, actions: {}, }); -
todos.ts
模块// 模块 import { TodoItem } from "../../interfaces/index"; // 导入接口 interface InitState { todos: TodoItem[]; } export default { namespaced: "todos", // 模块必须加 state: (): InitState => ({ todos: [ { id: "111", text: "嘿嘿嘿", isDone: false }, { id: "112", text: "嘿嘿嘿11", isDone: false }, ], }), mutations: { addTodo(state: InitState, todo: TodoItem) { state.todos.push(todo); }, }, };
-