目录
一、快速上手
二、vue的TS
三、使用vue-class-component
四、vue-property-decorator
五、TypeScript 不适合在 vue 业务开发中使用吗?
六、Property 'name' has no initializer
七、下载量对比
八、tsconfig.json
九、shims-vue.d.ts
十、shims-tsx.d.ts
十一、vuex-class
十二、ts
十三、装饰器(Decorator)
十四、红绿灯
十五、Property '$router' does not exist on type 'Login'
十六、使用createDecorator自定义装饰器
十七、TSLint去掉分号和逗号的检查
十八、react与TS
十九、图片转base64
二十、React+TS
二十一、外部声明和三斜线指令
二十二、TS+Webpack
二十三、yarn清缓存和卸载全局安装的包
二十四、修改脚手架使用npm下载还是使用yarn下载
二十五、倒计时
参考链接:
TypeScript
是 JavaScript
的强类型版本。然后在编译期去掉类型和特有语法,生成纯粹的 JavaScript
代码。
TypeScript
是 JavaScript
的超集,这意味着他支持所有的 JavaScript
语法。
强类型语言的优势在于静态类型检查。
TypeScript是微软开发的语言。
vue3.0使用ts开发。
是github开源项目:https://github.com/Microsoft/TypeScript
2012 年 10 月诞生。
vscode是用ts编写的:https://github.com/Microsoft/vscode/
装包:
yarn global add typescript
检查版本:
tsc -V
初始化:
tsc --init
index.ts:
function greeter(person) {
return "Hello, " + person;
}
let user = "Jane User";
document.body.innerHTML = greeter(user);
编程成js文件:
tsc index.ts
index.js:
function greeter(person) {
return "Hello, " + person;
}
var user = "Jane User";
document.body.innerHTML = greeter(user);
类型检查:
如果函数参数声明是字符串,却传了数字,会有警告信息
function greeter(person:string) {
return "Hello, " + person;
}
let user = 1;
document.body.innerHTML = greeter(user);
及时不传参数也会报错:
interface接口:
定义Person包含的字段
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);
使用class创建类:
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);
编译后是:
var Student = /** @class */ (function () {
function Student(firstName, middleInitial, lastName) {
this.firstName = firstName;
this.middleInitial = middleInitial;
this.lastName = lastName;
this.fullName = firstName + " " + middleInitial + " " + lastName;
}
return Student;
}());
function greeter(person) {
return "Hello, " + person.firstName + " " + person.lastName;
}
var user = new Student("Jane", "M.", "User");
document.body.innerHTML = greeter(user);
访问网页,index.html:
Document
效果:
自动补全功能:
引入Vue文件的时候需要加上.vue
后缀,否则编辑器识别不到
TS路由:
import Vue from 'vue'
import VueRouter, { RouteConfig } from 'vue-router'
import Home from '../views/Home.vue'
Vue.use(VueRouter)
const routes: Array = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
import Vue from 'vue'
import VueRouter, { RouteConfig } from 'vue-router'
Vue.use(VueRouter)
const routes: Array = [
{
path: '/',
redirect: '/login'
},
{
path: '/login',
component: () => import('../views/Login.vue')
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
https://class-component.vuejs.org/
https://github.com/vuejs/vue-class-component#readme
annotating:注释
加加减减:
{{count}}
或者:
{{count}}
v-model:
挂载完生命周期:
1
计算属性:
{{double}}
父子组件传值:
父组件
子组件
子组件也可以写成这种
增加路由hook:
vue-class-component.ts
import Component from 'vue-class-component'
Component.registerHooks([
"beforeRouteEnter"
])
在入口文件main.ts里引入这个文件
在组件里可以使用
https://www.npmjs.com/package/vue-property-decorator
vue属性装饰器
父子组件传值,传参
父组件:
子组件:
https://www.zhihu.com/question/310485097/answer/591869966
Property 'name' has no initializer and is not definitely assigned in the constructor.
解决办法一:
把tsconfig.json文件里的strict字段改成false
解决办法二:
在属性名后面加叹号,这是一种修饰符,标识忽略
ts的配置
{
"compilerOptions": {
"target": "esnext", //编译的目标版本
"module": "esnext", //指定生成哪个模块系统代码
"strict": true, //静态类型检查
"jsx": "preserve", //
"importHelpers": true,
"moduleResolution": "node",
"experimentalDecorators": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"sourceMap": true, // 是否生成map文件
"baseUrl": ".",
"types": [
"webpack-env"
],
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": [
"node_modules"
]
}
shims: 垫片
由于 TypeScript 默认并不支持 *.vue 后缀的文件,所以在 vue 项目中引入的时候需要创建一个shims-vue.d.ts 文件,放在项目应使用目录下,例如 src/shims-vue.d.ts,用来支持*.vue 后缀的文件;
主要用于 TypeScript 识别.vue 文件,Ts默认并不支持导入 vue 文件,这个文件告诉ts 导入.vue 文件都按VueConstructor
declare module '*.vue' {
import Vue from 'vue'
export default Vue
}
允许你以.tsx结尾的文件,在Vue项目中编写jsx代码
import Vue, { VNode } from 'vue'
declare global {
namespace JSX {
// tslint:disable no-empty-interface
interface Element extends VNode {}
// tslint:disable no-empty-interface
interface ElementClass extends Vue {}
interface IntrinsicElements {
[elem: string]: any;
}
}
}
https://www.npmjs.com/package/vuex-class
使用仓库做加加减减
仓库:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
interface Payload {
key: string,
value: any
}
export default new Vuex.Store({
state: {
count: 0
},
mutations: {
setState(state:any, payload:Payload) {
state[payload.key] = payload.value
}
},
actions: {
},
modules: {
}
})
页面:
{{count}}
初始化:
在空文件夹里打开终端 -> 输入tsc --init 自动生成tsconfig.json
{
"compilerOptions": {
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */
"allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "./js", //输出文件夹 /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
"experimentalDecorators": true, //装饰器 /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
}
}
监视ts文件变化,自动编译:
vscode -> 终端 -> 运行任务 -> typescript -> tsc: 监视 - (tsconfig.json的目录)
课堂练习(1):
function test(name:string) {
console.log(name)
}
test('hello3')
//元组
let tuple:[string, number, string] = ['a', 1, 'b']
console.log(tuple)
//任意值
let person:any = 'xu'
console.log(person)
//四要素:调用, 参数,返回值,作用
function fun(name:string):string {
console.log(name)
return name
}
fun('xu')
//never 不可到达
const error = ():never => {
throw new Error('错误')
}
//error()
const loop = () => {
while(true);
}
//loop()
console.log(1)
//枚举
enum Color { Red = 2, Green, Blue }
console.log(Color.Red) //2
enum obj {
None,
Read,
G = '123'.length
}
console.log(obj)
enum Enum {
A
}
let a = Enum.A
console.log(a) //0
let na = Enum[a]
console.log(na) //A
课堂练习(2):
function sayHello(person: string) {
return 'Hello,' + person
}
let user:string = 'a'
console.log(sayHello(user))
let num:number = 1
//没有显示声明类型,则会进行类型推断
let a = 'a'
a = 'b' //赋值其他类型,则会报错
let flag:boolean = true
//数字类型的数组
let arr:number[] = [1,2]
let strArr:string[] = ['a', 'b']
let objArr:object[] = [{}]
//泛型方式
let arr1:Array = [1]
let strArr1:Array = ['a','b']
//元组
let arr2:[number, string, boolean] = [1, '2', true]
//null, undefined
let my_null:null = null
let my_undefined:undefined = undefined
//可以是字符串也可以是undefined
let c:string | undefined
console.log(c)
//never 从来不会出现的值
// let my_never: never = (() => {
// throw new Error()
// })()
//any 任何类型
let my_any:any = '任何类型'
console.log(my_any)
//任何类型的数组
let arrAny:any[] = ['a', 1, true]
function run():void {
console.log('run')
}
run()
function getName():string {
return 'xu'
}
console.log(getName())
const myRun = ():void => {
console.log('myRun')
}
myRun()
const myGetName = ():string => {
return 'myGetName'
}
console.log(myGetName())
//构造函数Boolean 创造的不是布尔值
//let myBoolean:boolean = new Boolean(1)
课堂练习(3):
//函数
//没有返回值是用void
function getInfo(name:string, age:number):string {
return `我叫${name},今年${age}岁。`
}
console.log(getInfo('xu', 30))
let getInfo1 = (name:string, age:number):string => {
return `我叫${name},今年${age}岁。`
}
console.log(getInfo('xu', 31))
//完整类型
let getInfo2:(name:string, age:number) => string = (name:string, age:number):string => {
return `我叫${name},今年${age}岁。`
}
let getInfo3:Function = (name:string, age:number):string => {
return `我叫${name},今年${age}岁。`
}
//函数可选参数,加个问好
function myInfo(name: string, age?: number):string {
if (typeof age === 'number') {
return `我叫${name},今年${age}岁。`
} else {
return `我叫${name}`
}
}
console.log(myInfo('xu'))
//默认值
function myInfo1(name: string, age:number = 32):string {
if (typeof age === 'number') {
return `我叫${name},今年${age}岁。`
} else {
return `我加${name}`
}
}
console.log(myInfo1('xu'))
//求和
function sum(a:number, b:number):number {
return a + b
}
console.log(sum(1, 2))
//剩余参数
function sum1(...rest:number[]):number {
console.log(rest) //数组
return rest.reduce((prev, item) => {
return prev + item
}, 0)
}
console.log(sum1(1, 2, 3, 4, 5))
//枚举类型
// 0: 女, 1:男, 2:保密
enum Sex {
gril,
boy,
secret
}
let xu:Sex = Sex.boy
console.log(xu)
let xu1:Sex = 3
console.log(xu1)
function getSex(sex:Sex):string {
if (sex === Sex.gril) {
return '我是女孩'
} else if (sex === Sex.boy) {
return '我是男孩'
} else {
return '保密'
}
}
console.log(getSex(xu))
class A {
v:number = 100
test(num:number) {
if (num > this.v) {
console.log('大了');
} else if (num < this.v) {
console.log('小了')
} else {
console.log('对了')
}
}
}
let a = new A()
a.test(100)
let isDone:boolean = false
let isDone1:object = new Boolean(1)
let isDone2:Boolean = new Boolean(1)
let isDone3:boolean = Boolean(1)
//变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型
let something //let something:any
something = 1
something = 'a'
//联合类型
let myA: string | number
myA = 'a'
myA = 1
function getLength(something: string | []):number {
return something.length
}
let myB: string | number
myB = 'A'
myB.length
myB = 1
//myB.length //报错
let arr:number[] = [1, 3, 2]
arr.sort((a, b) => {
return a - b
})
console.log(arr)
function is(ar:string,sr:string):boolean {
let result = true
if(ar.length===sr.length){
for(let i=0;i
课堂练习(4):
//函数重载
function add(a:string, b:string):string;
function add(a:number, b:number):number;
function add(a:any, b:any): any {
if (typeof a === 'string') {
return a + '---' + b
} else {
return a + b
}
}
console.log(add('a', 'b'))
//类
//修饰符 pubulic protected private
//静态属性 static
class Person {
public name: string
protected age: number
static height: number = 170
constructor(name:string, age:number) {
this.name = name
this.age = age
console.log(Person.height, Person.getHeight())
}
protected getName():string {
return `我的名字叫${this.name}`
}
static getHeight():number {
return this.height
}
}
let xu = new Person('xu', 30)
//console.log(xu.getName())
console.log(Person.getHeight())
// class Animal {
// name:string
// constructor(name:string) {
// this.name = name
// }
// sayHi() {
// return `My name is ${this.name}`
// }
// }
// let a = new Animal('jack')
// console.log(a.sayHi())
//继承
class Programmer extends Person {
job:string
constructor(name:string, age:number, job:string) {
super(name, age)
this.job = job
console.log(this.age)
}
getJob() {
return `${this.getName()},年龄${this.age},我的工作是${this.job}`
}
}
let xu1 = new Programmer('徐同保', 30, 'web前端')
//console.log(xu1.getName())
console.log(xu1.getJob())
//console.log(xu1.age)
//抽象类
abstract class Animal {
name: string
constructor(name: string) {
this.name = name
}
getName():string {
return this.name
}
abstract eat():void
}
class Cat extends Animal {
food: string
constructor(name: string, food: string) {
super(name)
this.food = food
}
eat():void {
console.log(`${this.getName()}爱吃鱼`)
}
}
let ketty = new Cat('小猫', '鱼')
ketty.eat()
//更简练的写法,不用定义name了,也不需要赋值
class MyAnimal {
constructor(public name:string) {
}
getName() {
return this.name
}
}
let myAnimal = new MyAnimal('小猫')
console.log(myAnimal.getName())
//接口
interface Goods {
name: string
price: number
flag: boolean
}
let cartList: Goods[] = [
{
name: '苹果',
price: 8,
flag: true
},
{
name: '香蕉',
price: 5,
flag: false
}
]
function goodsInfo(goods:Goods) {
console.log(`${goods.name}现在${goods.price}元一斤${goods.flag ? ',正在促销' : ''}`)
}
cartList.forEach(item => {
goodsInfo(item)
})
//函数接口
interface GoodsInfo {
(goods: Goods): string
}
let myGoodsInfo: GoodsInfo = (goods:Goods):string => {
return `${goods.name}现在${goods.price}元一斤${goods.flag ? ',正在促销' : ''}`
}
//类接口,实现接口的时候使用implements(实现)
interface PersonI {
name: string,
age: number,
getName():string
}
interface WebI {
name: string,
age: number,
job: string
getName(): string
getJob(): string
}
//接口也可以继承
interface WebIPlus extends PersonI {
job: string,
getJob(): string
}
class MyPerson implements PersonI {
name: string
age: number
constructor(name:string, age:number) {
this.name = name
this.age = age
}
getName() {
return this.name
}
}
class Web extends MyPerson implements WebIPlus {
job: string
constructor(name: string, age: number, job: string) {
super(name, age)
this.job = job
}
getJob() {
return this.job
}
getInfo() {
return `${this.name},${this.age},${this.job}`
}
}
let xu2 = new Web('xu', 30, 'web')
console.log(xu2.getInfo())
课堂练习(5):
let arr:Array = ['a', 'b', 'c']
//泛型函数
function getMin(arr:T[]):T {
let min:T = arr[0]
arr.forEach(val => {
if (val < min) {
min = val
}
})
return min
}
let min = getMin([5, 2, 8, 4, 4])
console.log(min)
let min1 = getMin(['d', 's', 'c', 'e', 'f'])
console.log(min1)
//泛型类
class Min {
list: T[] = []
add(params: T):void {
this.list.push(params)
}
getMin(): T {
let min:T = this.list[0]
this.list.forEach(val => {
if (val < min) {
min = val
}
})
return min
}
}
//函数接口泛型
interface Min {
(arr: T[]): T
}
//装饰器
function logClass(target:any) {
console.log('我是装饰器', target)
target.prototype.name = '我是装饰器添加的name'
target.prototype.getName = function () {
console.log('我是装饰器添加的方法')
}
}
function logName(params:any) {
console.log(params)
return function(target:any, attr:any) {
console.log('属性装饰器', target) //实例
console.log('attr', attr) //使用装饰器的属性
target[attr] = params
}
}
// function logName(target:any, attr:any) {
// console.log('属性装饰器', target) //实例
// console.log('attr', attr) //使用装饰器的属性
// target[attr] = 'xu'
// }
@logClass
class Person {
@logName('我是Person类')
myName: string | undefined
@logName(40)
age:number | undefined
getInfo() {
console.log(this.myName, this.age)
}
}
let a = new Person()
console.log(a)
a.getInfo()
笔记:
TypeScript 中,使用 :
指定变量的类型,:
的前后有没有空格都可以。
TypeScript 只会进行静态检查,如果发现有错误,编译的时候就会报错。
构造函数 Boolean
创造的对象不是布尔值。
当构造函数修饰为 private
时,该类不允许被继承或者实例化。
当构造函数修饰为 protected
时,该类只允许被继承,不允许实例化。
只读属性关键字,只允许出现在属性声明或索引签名或构造函数中。
一个类只能继承自另一个类,但是可以实现多个接口。
变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型。
联合类型使用 |
分隔每个类型。
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
提案官网:https://github.com/tc39/proposal-decorators
https://tc39.es/proposal-decorators/
装饰器是一个对类进行处理的函数。装饰器函数的第一个参数,就是所要装饰的目标类。
@decorator
class A {}
// 等同于
class A {}
A = decorator(A) || A;
如果觉得一个参数不够用,可以在装饰器外面再封装一层函数。
function testable(isTestable) {
return function(target) {
target.isTestable = isTestable;
}
}
mixins实例:
//装饰器函数,list合并到原型链上
const mixins = (...list:any) => (target:any) => {
Object.assign(target.prototype, ...list)
}
//待合并的对象
const Foo = {
name: 'xu',
foo() {
console.log('foo')
},
bar() {
console.log('bar')
}
}
//使用装饰器
@mixins(Foo)
class MyClass {
}
//测试
let obj = new MyClass();
(obj as any).foo();
(obj as any).bar();
console.log((obj as any).name)
装饰器有注释的作用。
由于存在函数提升,使得装饰器不能用于函数。
日志实例:
//计算器类
class Computed {
name:string
constructor(name:string) {
this.name = name
}
@log
add(a:number, b:number) {
console.log(this.name)
return a + b
}
@log
sub(a:number, b:number) {
return a - b
}
}
//日志装饰器
function log(target:any, name:any, descriptor:any) {
let oldValue = descriptor.value
descriptor.value = function () {
//日志
console.log(`日志:调用${name},参数是${Array.from(arguments)}`)
//return oldValue.call(this, ...arguments) //执行老方法
return oldValue.apply(this, arguments)
}
return descriptor
}
//测试
const computed = new Computed('xu')
console.log(computed.add(2, 4))
console.log(computed.sub(4, 2))
class Light {
constructor() {
this.init()
}
init() {
return this.light(3, '绿灯')
.then(() => {
return this.light(2, '红灯')
})
.then(() => {
return this.light(1, '黄灯')
}).then(() => {
this.init()
}).catch(() => {
console.log('失败了')
})
}
light(time: number, type: string) {
return new Promise((resolve, reject) => {
let timer = setInterval(() => {
console.log(type, time)
time--
if (time === 0) {
clearInterval(timer)
resolve()
//reject()
}
}, 1000)
})
}
}
let ligth = new Light()
interface LampI {
loop: number
start(): void
}
//红绿灯
class Lamp {
loop: number
constructor(public green: number, public red: number, public yellow: number) {
this.loop = 0
}
start() {
let loopFun = async () => {
await new Promise((resolve) => {
setTimeout(() => {
this.loop++
this.print()
loopFun()
}, 1000)
})
}
loopFun()
}
test(a:T) {
console.log(a)
}
private print(): void {
if (this.loop <= this.green) {
console.log(`绿灯${this.green + 1 - this.loop}`)
} else if (this.loop <= this.green + this.red) {
console.log(`红灯${this.green + this.red + 1 - this.loop}`)
} else if (this.loop <= this.green + this.red + this.yellow) {
console.log(`黄灯${this.green + this.red + this.yellow + 1 - this.loop}`)
} else {
this.loop = 1
console.log(`绿灯${this.green}`)
}
}
}
let lamp = new Lamp(3, 2, 1)
lamp.start()
lamp.test([1, 2, 3])
lamp.test('hello')
解决办法:
(1)在使用路由的组件导入路由包:
import VueRouter from 'vue-router';
(2)把this断言为any
(this as any).$router.push('/index')
(3)在类里定义$router属性
export default class Login extends Vue {
$router
}
decorators.ts:
import { createDecorator } from 'vue-class-component'
export const Log = createDecorator((options:any, key:string) => {
// 备份原始方法
const originalMethod = options.methods[key]
// 添加日志逻辑
options.methods[key] = function wrapperMethod(...args:any) {
// 打印日志
console.log(`日志: ${key}(`, ...args, ')')
// 执行原始方法
originalMethod.apply(this, args)
}
})
export const LogPlus = (payload: string) => createDecorator((options:any, key:string) => {
// 备份原始方法
const originalMethod = options.methods[key]
// 添加日志逻辑
options.methods[key] = function wrapperMethod(...args:any) {
// 打印日志
console.log(`${payload}日志: ${key}(`, ...args, ')')
// 执行原始方法
originalMethod.apply(this, args)
}
})
使用:
{{msg}}
{{count}}
"trailing-comma": [false],
"semicolon": [false],
脚手架:
npx create-react-app react-ts-app --typescript
按装用到的包:
yarn add react-router-dom redux react-redux redux-thunk redux-logger axios
安装TS版的声明:
yarn add @types/react-router-dom @type/react-redux
const getBase64Image = (url: string) => {
return new Promise((resolve,reject) => {
let image = new Image();
// CORS 策略,会存在跨域问题https://stackoverflow.com/questions/20424279/canvas-todataurl-securityerror
image.setAttribute("crossOrigin",'Anonymous');
image.src = url;
image.onload = () => {
let canvas = document.createElement('canvas');
canvas.width = image.width;
canvas.height = image.height;
(canvas as any).getContext('2d').drawImage(image,0,0);
let result = canvas.toDataURL('image/png');
resolve(result);
}
image.onerror = () => {
reject('图片流异常');
};
})
}
受控组件:
handleInput(e: ChangeEvent, field: string) {
// @ts-ignore
this.setState({
[field]: e.target.value
})
}
HTMLInputElement:
keyUp事件:
handleEnter(e: KeyboardEvent) {
if(e.keyCode === 13) {
this.handleLogin()
}
}
点击事件:
handleVisible(e: MouseEvent, count: number) {
let { visible } = this.state
this.setState({
visible: !visible
})
}
忽略类型检查:
// @ts-ignore
滚动事件:
const handleScroll = (e: React.UIEvent) => {
let scrollTop = (e.target as Element).scrollTop
console.log(scrollTop)
}
withRouter:
redux数据读写:
import React, { Dispatch } from 'react'
import { connect } from 'react-redux'
import { IState } from '../interface'
interface IProps {
list: any[],
currentId: number,
onSetState: (key: string, value: any) => void
onDispatch: (action: Function) => void
}
const Sidebar = (props: IProps) => {
const { list, currentId } = props
const handleNav = (id: number) => {
props.onSetState('currentId', id);
//@ts-ignore
document.getElementById(id + '').scrollIntoView({ block: 'start', behavior: 'smooth' })
}
const sidebarDom = list.map((item: any) => (
handleNav(item.id) }>{item.title}
))
return (
{sidebarDom}
)
}
const mapStateToProps = (state: IState) => {
return {
list: state.book.list,
currentId: state.book.currentId
}
}
const mapDispatchToProps = (dispatch: Dispatch) => {
return {
onSetState(key: string, value: any) {
dispatch({ type: 'SET_STATE', key, value })
},
onDispatch(action: Function) {
dispatch(action)
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Sidebar)
reducer:
import { IBookState, IAction } from '../../interface'
const defaultState: IBookState = {
title: '小米书城',
currentId: 0,
isRealScroll: true,
list: [],
myBooks: []
}
const reducer = (state = defaultState, action: IAction) => {
switch (action.type) {
case 'SET_STATE':
let newState = JSON.parse(JSON.stringify(state))
newState[action.key] = action.value
return newState
default:
return state
}
}
export default reducer
actionCreator.js:
import { Dispatch } from 'react'
import Api from '../../api'
const list = () => (dispatch: Dispatch) => {
Api.list().then(res => {
if (res.code === 200) {
dispatch({ type: 'SET_STATE', key: 'list', value: res.data })
}
})
}
export default {
list
}
接口:
export interface IBookState {
title: string,
currentId: number,
isRealScroll: boolean,
list: any[],
myBooks: any[]
}
export interface IState {
book: IBookState
}
export interface IAction {
type: string,
key: string,
value: any
}
登录页hook写法:
import React, { useState, MouseEvent, KeyboardEvent } from 'react'
import { RouteComponentProps } from 'react-router-dom'
import Icon from '../components/Icon'
import Api from '../api'
const Login = (props: RouteComponentProps) => {
const [ username, setUsername ] = useState('admin')
const [ password, setPassword ] = useState('123456')
const [ visible, setVisible ] = useState(false)
const handleVisible = (e: MouseEvent, count: number ) => {
setVisible(!visible)
}
const handleEnter = (e: KeyboardEvent) => {
if (e.keyCode === 13 ) {
handleLogin()
}
}
const handleLogin = () => {
Api.login({ username, password }).then(res => {
if (res.code === 200) {
localStorage.setItem('token', res.data.username)
props.history.push('/index/home')
}
})
}
return (
setUsername(e.target.value)} placeholder="请输入用户名" autoFocus>
setPassword(e.target.value)} onKeyUp={ (e) => handleEnter(e) } placeholder="请输入密码" type={ visible ? 'text' : 'password' }>
handleVisible(e, count) } className="m-login-icon">
)
}
export default Login
登录页class写法:
import React, { Component, ChangeEvent, KeyboardEvent, MouseEvent } from 'react'
import { RouteComponentProps } from 'react-router-dom'
import Icon from '../components/Icon'
import Api from '../api'
interface IState {
username: string,
password: string,
visible: boolean
}
interface IProp extends RouteComponentProps {
}
export default class LoginClass extends Component {
constructor(props: IProp) {
super(props)
this.state = {
username: 'admin',
password: '123456',
visible: false
}
}
handleInput(e: ChangeEvent, field: string) {
// @ts-ignore
this.setState({
[field]: e.target.value
})
}
handleVisible(e: MouseEvent, count: number) {
let { visible } = this.state
this.setState({
visible: !visible
})
}
handleEnter(e: KeyboardEvent) {
if(e.keyCode === 13) {
this.handleLogin()
}
}
handleLogin() {
let { username, password } = this.state
Api.login({ username, password }).then(res => {
if (res.code === 200) {
localStorage.setItem('token', res.data.username)
this.props.history.push('/index/home')
}
})
}
render() {
let { username, password, visible } = this.state
return (
this.handleInput(e, 'username')} placeholder="请输入用户名" autoFocus />
this.handleInput(e, 'password')} onKeyUp={ (e) => this.handleEnter(e) } placeholder="请输入密码" type={ visible ? 'text' : 'password' }/>
this.handleVisible(e, count) } className="m-login-icon">
)
}
}
Icon组件hook写法:
import React, { MouseEvent } from 'react'
interface IProps {
name: string,
className?: string,
onClick?: (e: MouseEvent, count: number) => void
}
const Icon = (props: IProps) => {
let { name, className = '', onClick = () => {} } = props
return (
onClick(e, 1)}>
)
}
export default Icon
Icon组件Class写法:
import React, { Component, MouseEvent } from 'react'
interface IProps {
name: string,
className?: string,
onClick?: (e: MouseEvent, count: number) => void
}
export default class IconClass extends Component {
render() {
const { name, className = '', onClick = () => {} } = this.props
return (
onClick(e, 1)}>
)
}
}
ReactElement:
import React, { Dispatch, ReactElement } from 'react'
import Header from '../components/Header'
import Footer from '../components/Footer'
import { Switch, Route } from 'react-router-dom'
import { connect } from 'react-redux'
import Home from './Home'
import MyBooks from './MyBooks'
import Me from './Me'
interface IProps {
onSetState: (key: string, value: any) => void
onDispatch: (action: Function) => void
}
const Index = (props: IProps) => {
const renderComponent = (Component: ReactElement, title: string) => {
setTimeout(() => {
props.onSetState('title', title)
})
return Component
}
return (
renderComponent( , '小米书城')}>
renderComponent( , '我的书包')}>
renderComponent( , '个人中心')}>
)
}
const mapStateToProps = () => {
return {
}
}
const mapDispatchToProps = (dispatch: Dispatch) => {
return {
onSetState(key: string, value: any) {
dispatch({ type: 'SET_STATE', key, value })
},
onDispatch(action: Function) {
dispatch(action)
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Index)
declare:声明
reference:引用
env:environment 环境
namespace:命名空间
module: 模块
外部声明一般针对第三方来历不明的库,当你想要在你的typescript项目中使用用javascript代码写的第三方库时,就需要用到外部声明。一个常见的例子,假设我们在HTML中通过`script`标签引入了全局`jQuery`:
// 注册全局变量 $
path/to/jquery.js` 文件在会在全局作用域中引入对象 `$`,接下来如果在同一项目下的TypeScript文件中使用 `$`,TypeScript编译器会报错:
// 错误,缺少名字 $ 的声明信息
// error TS2581: Cannot find name '$'. Do you need to install type definitions for jQuery? Try `npm i @types/jquery`
$('body').html('hello world');
由于没有任何类型信息,TypeScript编译器根本不知道 `$` 代表的是什么,此时需要引入**外部声明**(因为`$`是外部JavaScript引入TypeScript代码中的)。外部声明的关键字是:
分析语句 `$('body').html('hello world');` 得出:
// 声明 $ 的类型信息
declare let $: (selector: string) => {
html: (content: string) => void;
};
// 正确,$已经通过外部声明
$('body').html('hello world');
声明应该是纯粹对于一个标识符**类型或外观的描述**,便于编译器识别,外部声明具有以下特点:
// 声明a为一个数字
declare let a: number;
// 错误,外部声明不能初始化
// error TS1039: Initializers are not allowed in ambient contexts
declare let b: number = 2;
// 声明T为一个接口
declare interface T {}
// 声明接口类型变量b
let b: T;
// 声明fn为一个函数
// 错误,声明包含了函数实现
// error TS1183: An implementation cannot be declared in ambient contexts
declare function fn(){}
// 正确,不包含函数体实现
declare function fn(): void;
// 声明myFunc为一个函数
declare let myFunc: (a: number) => void;
// 声明MyEnum枚举类型
declare enum MyEnum {
A, B
}
// 声明NS为命名空间
declare namespace NS {
// 错误,声明不能初始化
// error TS1039: Initializers are not allowed in ambient contexts
const a: number = 1;
// 正确,仅包含声明
const b: number;
// 正确,函数未包含函数体实现
function c(): void;
}
// 声明一个类
declare class Greeter {
constructor(greeting: string);
greeting: string;
showGreeting(): void;
}
外部声明还可以用于声明一个**模块**,如果一个外部模块的成员要被外部访问,模块成员应该用 `export` 声明导出:
declare module 'io' {
export function read(file: string): string;
export function write(file: string, data: string): void;
}
习惯上,常常把外部声明写在一个后缀名为 `.d.ts` 的声明文件中,然后用三斜线指令引入进来
// jquery.d.ts 文件
declare let $: (selector: string) => {
html: (content: string) => void;
};
// main.ts 文件
///
$('body').html('hello world');
上述语句声明了 `main.ts` 依赖 `jquery.d.ts` 声明文件,在编译阶段,被依赖文件 `jquery.d.ts` 将被包含进来,就像将被依赖文件的源码展开在依赖声明处一样:
// main.ts文件等价于将代码在三斜线指令处展开
declare let $: (selector: string) => {
html: (content: string) => void;
};
$('body').html('hello world');
三斜线指令中需要注意的是 `path` 类型和 `types` 类型的区别:
///
///
///
指令声明了对某个包的依赖。
例如,把///
引入到声明文件,表明这个文件使用了@types/node/index.d.ts
里面声明的名字; 并且,这个包需要在编译阶段与声明文件一起被包含进来。
仅当在你需要写一个d.ts
文件时才使用这个指令。
参考链接:
https://zhongsp.gitbook.io/typescript-handbook/handbook/triple-slash-directives#less-than-reference-path-greater-than
https://www.teaspect.com/detail/5586
webpack.config.js:
module.exports = {
mode: 'development',
entry: './src/main.ts',
output: {
path: __dirname + '/dist',
filename: 'bundle.js'
},
devtool: "source-map",
resolve: {
extensions: [ ".ts" ]
},
module: {
rules: [
{
test: /\.ts$/,
exclude: /node_modules/,
use: [
{
loader: "ts-loader"
}
]
}
]
}
}
package.json:
{
"name": "m-ts-webpack",
"version": "1.0.0",
"description": "",
"main": "webpack.config.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"ts-loader": "^7.0.2",
"typescript": "^3.8.3",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11"
}
}
项目代码:
https://github.com/baweireact/m-apps/tree/master/demo/ts/01TS%2BWebpack
yarn cache clean
yarn global remove @vue/cli
yarn global add @vue/cli
interface IObj {
endtime: string,
wrapper: HTMLElement
}
class CountDown {
endtime: string
wrapper: HTMLElement
constructor(obj: IObj) {
this.endtime = obj.endtime
this.wrapper = obj.wrapper
}
start() {
setInterval(() => {
let start = new Date().getTime()
let end = new Date(this.endtime).getTime();
let result = end - start
let time = this.formatDateTime(result)
console.log(time)
this.wrapper.innerHTML = time
}, 1000)
}
formatDateTime(shijiancha: number) {
let days = shijiancha / 1000 / 60 / 60 / 24;
let daysRound = Math.floor(days);
let hours = shijiancha / 1000 / 60 / 60 - (24 * daysRound);
let hoursRound = Math.floor(hours);
let minutes = shijiancha / 1000 / 60 - (24 * 60 * daysRound) - (60 * hoursRound);
let minutesRound = Math.floor(minutes);
let seconds = shijiancha / 1000 - (24 * 60 * 60 * daysRound) - (60 * 60 * hoursRound) - (60 * minutesRound);
return `限时特卖 ${daysRound}天 ${hoursRound}时 ${minutesRound}分 ${Math.floor(seconds)}秒`
}
}
let countDown = new CountDown({ endtime: '2020/10/1', wrapper: document.getElementById('box') as HTMLElement })
countDown.start()
function longest(str?:string) {
if (typeof str === 'undefined') {
alert('未传递参数')
return
}
let arr = str.split(' ')
let result = 0
let longestWord = ''
for (let i = 0; i < arr.length; i++) {
if (arr[i].length > result) {
result = arr[i].length
longestWord = arr[i]
}
}
console.log(`最长的单词是:${longestWord}`)
return result
}
let result = longest('let life be beautiful like summer flowers')
console.log(result)
longest()
一起来拥抱强大的TypeScript吧--Ts+Vue完全教程
中文手册
英文手册
入门教程
装饰器----阮一峰
Learn React with TypeScript 3(英文版):
链接:https://pan.baidu.com/s/1BriHov6-BRiQTIZlcR3dWw
提取码:7erx