Vue3.0 和 TypeScript 学习笔记

Vue3.0 和 TypeScript

前端流行三大框架: Vue、React、Angular(入门门槛高)

2020.9.19 vue3发布正式版, 命名 One Piece

vue3带来的变化:

1.性能的提升(打包体积减少,内存减少,渲染更快)

vue2使用Object.defineProperty来劫持数据的getter和setter方法

2.源码的升级

使用Proxy进行数据劫持

通过monorepo的形式来管理源代码

3.拥抱ts,源码使用ts进行重写,vue3更好的支持ts

vue2,vue使用flow来进行类型检测,vue使用ts重构

4.新的特性:

  1. Composition API(组合api)

  2. 新的内置组件

  3. 其他改变

    data应始终被声明为一个函数

    移除keyCode支持作为v-on的修饰符

4.删除了一些不必要的API

5.包括编译方面的优化

生成Block Tree、Slot编译优化、diff算法优化

7.Hooks函数增加代码的复用性

VUE3.0

一、创建Vue3项目

1.使用vue-cli创建

查看vue-cli版本(vue -V)确保vue-cli版本在4.5.0以上

vue create v3-ts-app(项目名称)

2.使用vite创建

vite官网:https://vitejs.cn

vite------新一代前端构建工具

1.开发环境中,无需打包,可快速的冷启动

2.轻量快读的热重载

3.真正的按需编译,不再等待整个应用编译完成

传统构建工具(webpack)VS vite构建对比

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oxbe7rjV-1680683049573)(C:\Users\Siki\AppData\Local\Temp\1637992002925.png)]

传统:先有入口文件——》分析路由(规则、什么路径)——》模块——》把所有东西进行打包——》告诉我们服务器准备好了

vite:先准备服务器——》入口文件——》找到路由——》对应分析文件

3.分析工程结构

3.1.入口文件 main.js

在vue3项目中不能使用vue2的写法

3.2.App.vue

vue3 中模板结构可以没有根标签

4.安装vue3专用的开发者工具

4.1.在线安装

chrome网上商店

4.2.离线安装

二、常用的Composition API (组合式API)

组合式的意思?

1.setup

  • 理解:Vue3.0中的一个新的配置项,值为一个函数

  • 组件中所用到的:数据、方法等等,均要配置在setup中

  • setup函数的两种返回值

    1.若返回值是一个对象,在 模板中可以直接使用(重点)

      setup() {
        //数据
        let name = '张三'
        let age = 18
        // 方法
        function sayHello(){
            alert(`我叫${name},我${age}岁了,你好啊`)
        }   
        //setup的返回值——对象
         return {
            name,
            age,
            sayHello
        };
      },
    

    ​ 2.若返回一个渲染函数,则可以自定义渲染内容

    import {ref, defineComponent, h} from 'vue'
    setup(){
    	let name = '1234'
        return ()=> {
    		return h('h1', '尚硅谷2323')
    	}
    }
    

注意:

  1. vue2 和vue3 的配置不要混用
  • vue2.x的配置(data、methods、computed)可以访问到setup中的属性、方法
  • 但在setup中不能访问到vue2.x配置
  • 如果有重名,setup优先
  1. setup不能是一个async函数。如果加上async,返回值不再是return 的对象,而是一个被promise包裹的对象,模板看不到return对象中的属性

2.ref函数

vue2 中ref 相当于id,作为一个标识

vue3中作为函数

2.1 作用:

定义一个响应式的数据

2.2 语法

const xxx = ref(initValue)

想要实现响应式,要把数据丢给ref,让ref加工生成一个引用对象(RefImpl引用实现的实例对象);

通过xxx .value'获取;

在模板中读取数据,不需要.value

//引入ref
import {ref} from 'vue'

//使用ref 生成引用对象
let name = ref('张三')
let age = ref(18)
let job = ref({
    type:'前端',
    salary: '30k'
})
    
//修改数据
function change(){
	name.value = '张三三'
	age.value = 48
	job.value.type = '后端'
    job.value.salary = '100k'
}

//在模板中展示
<h1>{{name}} {{age}}</h1>

getter (读)、setter(改)

2.3.vue3 对不同的数据类型有不同的处理方法
  • 基本类型的数据----》 RefImpl{……value} get、set(数据劫持)

​ 响应式依靠Objec.defineProperty()的get与set数据劫持的方式完成的

  • 对象类型的数据----》RefImpl{……value} ———》Proxy{type:‘’}

​ 内部“求助于”Vue3.0中的一个新函数——reactive函数(ES6中的Proxy)

3.reactive函数

  • 作用:定义一个对象类型的响应式数据(基本类型用ref函数)

  • 语法:const 代理对象 = reactive(源对象)

    接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象)

  • reactive定义的响应式数据是"深层次的"

  • 内部基于ES6的Proxy实现,通过代理对象操作源对象内部数据进行操作,可以被vue所捕获到(数据劫持)

let job = reactive({
      type:'前端',
      salary: '30k',
      a:{
         b: {
            c: 777
         }
      }
    })
   
function sayHello(){
        job.type = '后端'
        job.salary = '100k'
        job.a.b.c = 999
        console.log(job.a.b.c)
    }  

4.Vue3.0中的响应式原理

####4.1 vue2.x的响应式

#####实现原理:

​ 对象类型: 通过Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)

​ 数组类型:通过重写更新数据的一系列方法来实现拦截(对数组的变更方法进行了包裹)

// 源数据
        let person = {
            name:'张三',
            age: 18
        }
        // 模拟vue2中实现响应式
        let p = {}
        Object.defineProperty(p, 'name', {
            configurable:true,
            get(){//有人读取name时调用
                return person.name
            },
            set(value){//有人修改name时调用
                console.log('有人修改了name属性!')
                person.name = value
            }
        })

VUE2中响应式存在的问题:

  • 新增属性、删除属性,界面不会更新

  • 直接通过下标修改数组,界面不会自动更新

新增:通过一个api(给一个数据里追加一个响应式的属性): this.$set() / Vue.set()

删除:(给一个数据里移除一个响应式的属性)this.$delete()

通过下标修改数组: 1. this.$set 2.splice

####4.2 Vue3.0的响应式

tips: #region …… #endregion 折叠

1.window.Proxy 内置的构造函数
function person ={
	name: '李四',
	age:18
}

// person 源数据,p代理对象
const p = new Proxy(person,{
	//读 有人读取p的某个属性时调用
    get(target,propName){
        return target[propName];//obj[字符串];obj.变量
    },
    //改 、 增  有人修改p的某个属性或给p追加某个属性时调用
    set(target,propName,value){//value是修改的值
       target[propName] = value //改数据
    },
    //删
    deleteProperty(target,propName){
       return delete target[propName]
    }
})
2.Reflect.defineProperty()
let obj={a:1,b:2}
Reflect.set(obj,'a',666)
Reflect.get(obj,'a')
Reflect.deleteProperty(obj,'a')
const x = Reflect.defineProperty()

成功与否有返回值,对框架封装友好

通过Object.defineProperty 去操作捕获不到错误,对封装框架不友好,

通过Reflect.defineProperty去操作,返回值可判断是否错误

const p = new Proxy(person,{
	//读 有人读取p的某个属性时调用
    get(target,propName){
        return Reflect.get(target,propName);
    },
    //改/增  有人修改p的某个属性或给p追加某个属性时调用
    set(target,propName,value){
       Reflect.set(target,propName, value) //改数据
    },
    //删
    deleteProperty(target,propName){
       return Reflect.deleteProperty(target,propName)
    }
})
4.3 总结:Vue3.0的响应式原理
  • 通过Proxy(代理):拦截对象中任意属性的变化,包括:属性值的读写、属性的添加、属性的删除等

  • 通过Reflect(反射):对源对象的属性进行操作

5.Reactive对比ref

  • 从定义数据角度对比:

​ ref 用来定义:基本类型数据

​ reactive用来定义:对象或数组类型数据

​ 注意:ref也可以用来定义对象(数组)类型数据,它内部会自动通过reactive转为代理对象

  • 原理角度对比:

​ ref通过Object.defineProperty()的get与set来实现响应式(数据劫持)

​ reactive通过使用Proxy来实现响应式(数据劫持),并通过Reflect操作源对象内部的数据

  • 使用角度对比:

​ ref定义的数据:操作数据需要.value,读取数据时模板中直接读取

reactive定义的数据:操作数据与读取数据都不需要.value

6.setup的两个注意点

6.1 vue2的$attrs
props: ['msg','school'] ;//若props没声明,则元素出现在$attrs上
6.2 vue2的插槽$slots
//具名插槽的使用
<template slot="test1">
	<span>123</span>
</template>
6.3 setup的两个注意点:
setup执行时机:

在beforeCreate之前执行一次,this是undefined ,所以setup中无法取到this

setup的参数:

1.props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性

2.context: 上下文对象

  • attrs: 值为对象,包含:组件外部传递过来,但是没有在props配置中声明的属性,相当于vue2中this.$attrs

  • slots:收到的插槽内容,相当于: this.$slots

     <Demo @hello="showHello" msg="你好啊" school="尚硅谷">
     	<template v-slot:qwe></template>
     	<template v-slot:asd></template>
     </Demo>
     
    

  • emit:触发自定义事件的函数,相当于this.$emit

     //父组件
     <Demo @hello="showHello" msg="你好啊" school="尚硅谷"></Demo>
     
     setup(){
          function showHello(value){
            alert(`触发了hello事件,收到的参数是${value}`)
          }
          return {
            showHello
          }
     }
     
     //Demo组件
     <button @click="test">点击</button>
     
     export default {
        name:'Demo',
        emits:['hello'],//绑定了hello事件
        setup(props, context) {
            function test(){
                context.emit('hello',666)
            }
            return {
                test
            }
        }
    }
    

7.计算属性与监视

与vue2.x中computed配置功能一致

写法:

<h1>一个人的信息</h1>
    姓:<input type="text" v-model="person.firstName">
    <br>
    名:<input type="text" v-model="person.lastName">
    <br>
    <span>全名:{{person.fullName}}</span>
    <br>
    全名: <input type="text" v-model="person.fullName">
    
    setup() {
        //数据源
        let person = reactive({
            firstName:'张',
            lastName:'三'
        })
        //计算属性(简写形式)——不可修改
       /*  person.fullName= computed(() => {
            return person.firstName +'-'+ person.lastName
        }) */
        // 计算属性——可修改
        person.fullName = computed({
            get(){
                return person.firstName +'-'+ person.lastName
            },
            set(value){
                const FULLNAME = value.split('-')
                person.firstName = FULLNAME[0]
                person.lastName = FULLNAME[1]
            }
        })
       
        return {
            person,
        }
    }

8.watch函数

与Vue2.x中watch配置功能一致

两个小坑:

​ 监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)

​ 监视reactive定义的响应式数据中某个属性(对象)时:deep配置有效

Vue2
watch:{
	//简单写法
    sum(newValue,oldValue){
        console.log('sum的值变化了')
    }
    //复杂写法
    sum:{
    	immediate:true,//立即监听
    	deep:true,//深度监视
        handler(newValue,oldValue){//监视的回调 
        }
    }
}

Vue3  组合api
1.引入
2.内置的函数
let sum = ref(0)
//情况一:监视ref所定义的响应式数据
watch(sum, (newValue,oldValue) => {
    
},{immediate:true})

//情况二:监视ref所定义的多个响应式数据
let sum = ref(0)
let msg = ref('你好啊')
watch([sum,msg],(newValue,oldValue) => {}, {immediate:true})

//情况三:监视reactive所定义的一个响应式数据的全部属性,
//注意:1.此处无法正确的获取oldValue(暂时无法解决)
	   2.强制开启了深度监视(deep配置无效)
let person = reactive({
    name:'张三'
    age:18,
    job:{
        j1:{
           salary:20
        }
    }
})
watch(person,(newValue,oldValue) => {
    //oldValue无法正确获取
})

//情况四:监视reactive所定义的一个响应式数据的某个属性,写成一个函数
watch(()=>person.age,(newValue,oldValue) => {})

//情况五:监视reactive所定义的一个响应式数据的某些属性,写成一个数组
watch([()=>person.age,()=>person.name],(newValue,oldValue) => {})

//特殊情况:如果监视的是reactive所定义的对象中的某个属性,所以deep配置有效,需要开启深度监视
watch(() => person.job,(newValue,oldValue) => {},{deep:true})

注意

  • Vue2中watch是一个配置项,只能写一个watch;Vue3中是一个函数,可以多次调用;

  • watch(监视的值,监视的回调,配置项)

  • .value的使用

    监视的是ref定义的数据(RefImpl) 不需要.value

    监视的是ref里面求助于reactive所定义的数据 要.value或者开启深度监视

     let sum = ref(0)
    let person = ref({
                name:'张三',
                age: 18,
                job: {
                    j1:{
                        salary: 20
                    }
                }
    })
    
    //sum里存的是基本类型的值 不能.value
    watch(sum, (newValue,oldValue) => {
                console.log('sum的值变化了', newValue, oldValue)
    })
    //监视的是ref里面求助于reactive所定义的数据 —— person
     watch(person.value, (newValue,oldValue) => {
                console.log('sum的值变化了', newValue, oldValue)
    })
     watch(person, (newValue,oldValue) => {
                console.log('sum的值变化了', newValue, oldValue)
    },{deep:true})
    

9.watchEffect函数

watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。

watchEffect(() => {
     let x1 = sum.value
     let x2 = person.job.j1.salary
     console.log('wacthEffect所指定的回调执行了')
})
  • watch:既要指明监视的属性,也要指明监视的回调

  • watchEffect:不用指明监视的属性,监视的回调中用到哪个属性,就监视哪个属性

  • watchEffect有点像computed

​ computed注重计算出来的值(回调函数的返回值),所以必须要写返回值

​ watchEffect注重的是过程(回调函数的函数体),所以不用写返回值

10.Vue3生命周期

  • Vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有两个被更名:

beforeDestroy ==》 beforeUnmount

destroyed ==》Unmounted

export default {
    name:'Demo',
    setup() {     
        return { }
    },
    // 通过配置项的形式使用什么周期钩子
    beforeCreate() {
        console.log('-----beforeCreate------')
    },
    created() {
        console.log('------created-------')
    },
    beforeMount() {
        console.log('------beforeMount-------')
    },
    mounted() {
        console.log('------mounted-------')
    },
    beforeUpdate() {
         console.log('------beforeUpdate-------')
    },
    updated() {
        console.log('------updated-------')
    },
    beforeUnmount() {
        console.log('------beforeUnmount-------')
    },
    unmounted() {
        console.log('------unmounted-------')
    },
}
  • Vue3.0提供了组合式API形式的生命周期钩子,与Vue2中钩子对应关系如下:

beforeCreate ===> setup()

created ===> setup()

beforeMount ===> onBeforeMount

mounted ===> onMounted

beforeUpdate ===> onBeforeUpdate

updated ===> onUpdated

beforeUnmount ===> onBeforeUnmount

unmounted ===> onUnmounted

onBeforeMount(() => {
   console.log('------onBeforeMount-------')
})

用组合式API写的生命周期钩子比用配置项写的优先级靠前

11.自定义hook函数

  • 本质是一个函数,把setup函数中使用的Composition API进行了封装

  • 类似于Vue2中的mixin

  • 自定义hook的优势:复用代码,让setup中的逻辑更清楚易懂

12.toRef

  • 作用:创建一个ref对象,其value值指向另一个对象中的某个属性
  • 语法:const name = toRef(person,'name')
  • 应用:要将响应式对象中的某个属性单独提供给外部使用
    toRefs与toRef功能一致,但可以批量创建多个ref对象,语法:toRefs(person)
  • toRef 是引用一个,ref是复制一个
let person = reactive({
    name:'张三',
    age: 18,
    job: {
        j1: {
            salary: 20
        }
    }
})

return {
    name:toRef(person,'name'),
    salary: toRef(person.job.j1,'salary')
}
//toRefs 返回值是一个对象
return {
    ...toRefs(person)
}

三、其他Composition API

1.shallowReactive和shallowRef

  • shallowReactive 浅层次的响应式,只考虑对象类型的第一层的响应式

  • shallowRef 只处理基本数据类型的响应式,不进行对象的响应式处理

基本类型:ref与shallowRef没有区别

1.2使用场景

对象数据,结构比较深,变化的只是最外层属性————shallowReactive

对象数据,后续功能不会修改该对象中的属性,而是用新的对象直接替换源对象—————shallowRef

2.readonly (只读)和shallowReadonly

2.1readonly() 只读

让一个响应式的数据变为只读(深只读)

person = readonly(person)
2.2 shallowReadonly 对象中的第一层数据只读

让一个响应式的数据变为只读(浅只读)

2.3.应用场景

不希望数据被修改时、数据是其他组件传过来的

3.toRaw与markRaw

响应式数据 =====》普通数据

3.1 toRaw (原始)

作用:将一个由reactive生成的响应式数据转为普通对象

使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新

3.2 markRaw

作用:标记一个对象,使其永远不会再成为响应式对象;源数据在变,但是不是响应式的

应用场景:

​ 有些值不应该被设置为响应式的,例如复杂的第三方类库等

​ 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能

4.customRef

作用:创建一个自定义的ref,并对其依赖项跟踪和更新触发进行显式控制

应用:防抖

setup() {
        //数据源
        // let keyWord = ref('hello') //使用vue提供的ref
        let keyWord = myRef('hello',500) //使用程序猿自己定义的ref

        //自定义一个ref ——名为:myRef
        function myRef(value, delay){
            //使用customRef将自定义的ref函数交出去
            return customRef((track, trigger) => {
                let timer 
                //写自定义的逻辑,必须返回一个对象
                return {
                    //有人读取数据时调用
                    get(){
                        console.log(`有人从myRef中读取了数据,我把${value}给他了`)
                        track() //3.通知vue,追踪数据的改变
                        return value 
                    },
                    // 有人修改数据时调用
                    set(newValue){
                        console.log(`有人修改了myRef中的数据,改为了${newValue}`)
                        clearTimeout(timer)
                        timer = setTimeout(() => {
                            value = newValue //1.修改数据
                            trigger() //2.通知vue去重新解析模板
                        },delay)
                    }
                }
            })
        }
        
        return {
           keyWord
        }
    }

5.provide 与inject

作用:实现祖孙组件间的通信

父组件有一个provide选项来提供数据,后代组件组件有一个inject选项来开始使用这些数据

祖组件:

let car = reactive({name:'奔驰',price:'40万'})
provide('car',car)

后代组件:

const car = inject('car')
return {car}

6.响应式数据的判断

isRef: 检查一个值是否为一个ref对象

isReactive: 检查一个对象是否由reactive创建的响应式代理

isReadonly: 检查一个对象是否由readonly创建的只读代理

isProxy:检查一个对象是否是由reactive 或者 readonly方法创建的代理

四、组合式API的优势

传统的Options API ,新增或者修改一个需求,需要分别在data、methods、computes中更改

可以更优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起。

五、新的组件

1.Fragment

Vue2中:组件必须有一个根标签

Vue3中:组件可以没有跟标签,内部会将多个标签包含在一个Fragment虚拟元素中

优点: 减少标签层级,减少内存占用

2.Teleport

是一种能够将组件html结构移动到指定位置的技术

<teleport to="body">
		
</teleport>
// position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)

3.Suspense

等待异步组件时渲染一些额外的内容,提高用户体验

使用步骤:

​ 1.异步引入组件

import {defineAsyncComponent} from 'vue'
const Child = defineAsyncComponent(() => import('./components/Child.vue'))

​ 2.使用Suspense包裹组件,并配置好default与fallback

<Suspense>
	<template v-slot:default>
		<Childe/>
	</template>
	<template v-slot:fallback>
		<h3>加载中……</h3>
	</template>
</Suspense>

六、Vue3其他变化

1.全局API的转移

将全局的API ,即Vue.xxx调整到应用实例 app上

移除了Vue.config.productionTip

2.其他改变

  • data选项始终被声明为一个函数

过度类名的更改:

.v-enter,
.v-leave-to {}

.v-enter-from,
.v-leave-to {}
  • 移除keyCode作为v-on的修饰符,同时也不再支持config.keyCodes

  • 移除v-on.native 修饰符

父组件中绑定事件,子组件中声明自定义事件

  • 移除过滤器filter

过滤器有使用成本,建议用计算属性或者methods替代

Vue的使用

  1. 通过CDN的方式引入

    CDN: 内容分发网络,通过相互连接的网络系统,利用最靠近每个用户的服务器,更快、更可靠的将音乐、图片、应用程序及其他文件发送给用户。

  2. 下载Vue的js文件,并且手动引入

  3. 通过npm包管理工具安装使用(webpack)

  4. 通过Vue CLI 创建项目并使用

声明式和命令式 两种不同的编程范式

1.声明式(Vue开发):what to do 声明数据、声明方法、将数据绑定到模板中

2.命令式(原生开发):how to do 给浏览器命令,让浏览器完成

MVC和MVVM : 软件的体系结构(架构模式)

1.MVC:

View 指 div

Controller:

Model: 数据,从服务器请求的大量的数据、简单的数据

2.MVVM

Vue是一个MVVM的框架,虽然没有完全遵守MVVM的模型,但是整个设计是受到它的启发。

View(DOM) :template中的代码

ViewModel:

Model: data、methods中的代码……

View和Model互相绑定

template属性

Vue需要帮助我们渲染的模板信息

1.template写法

方式一:使用script标签

<script type="x-template" id="why">
	模板的内容{{}}
</script>

<script>
	Vue.createApp({
        template:'#why',
        data:function(){
            return{}
        },
        methods:{}
	})
</script>

方法二:使用template

<template id="why">
	<div></div>
</template>

<script>
	Vue.createApp({
        template:'#why',
        data:function(){
            return{}
        },
        methods:{}
	})
</script>

html原生提供的template元素 ,不会被浏览器渲染,是为了在js中使用的。

data属性

data属性是传入一个函数,并且该函数需要返回一个对象,返回的对象会被Vue的响应式系统劫持

vue3.x中如果不传入一个函数,就会在浏览器中报错

methods属性

是一个对象,在对象中定义很多方法

Vue的源码

1.在github.com上搜索vue-next,并下载源代码;推荐通过git clone

2.安装Vue源码项目相关的依赖; yarn install

3.对项目执行打包操作 : yarn dev

"scripts":{
    "dev": "node scripts/dev.js --sourcemap"
}

sourcemap 代码映射

Vue项目需要掌握的技术:

1.Vue3全家桶、

2.Vue3组件库(AntDesignVue+Element-Plus)、

3.可视化库(ECharts+Three.js+G2、L7)

4.TypeScript

5.其他技术

webpack、Vite

Axios等网络请求库

Less、Sass等css预处理器

TypeScript

一、初识TypeScript

1JS是什么

JavaScript是一种运行在客户端(浏览器)中的编程语言,node.js 让JavaScript摆脱了浏览器的束缚,可以实现服务端/桌面端编程。

2 TS是什么

TypeScript是微软开发的开源编程语言,设计目标是开发大型应用,可以在任何浏览器、计算机和操作系统上运行。TypeScript是JavaScript的超集(JS有的TS都有),为JS增加了类型系统、接口、枚举……

3 TypeScript相比JS的优势

JS的类型系统存在“先天缺陷”,绝大部分错误都是类型错误(Uncaugth TypeError)

优势一: 类型化思维方式,使得开发更加严谨,提前发现错误,减少改bug时间

优势二:类型系统提高了代码可读性,并使维护和重构代码更加容易

优势三:补充了接口、枚举等开发大型应用时JS缺失的功能

Vue3源码使用了TS重写

Angular默认支持TS

React与TS完美配合,是很多大型项目的首选

二、TypeScript开发工具准备

1 安装VSCode 和 Node.js

2 安装解析TS的工具包

node.js和浏览器只认识js代码

需要将ts代码转换为js代码

安装TypeScript (4.4.3)

npm install -g typescript

typescript: 就是用来解析TS的工具包,提供了tsc命令,实现了TS===>JS的转化

tsc -V

三、TypeScript的使用

1 第一个TS文件

  • 创建ts文件: 文件名.ts

  • 解析ts文件: tsc 文件名.ts

使用tsc命令的监视模式

只要重新保存了ts文件,就会自动调用tsc将ts转化为js tsc --watch index.ts

  • 执行JS文件: node 文件名.js

2 简化执行TS文件

使用ts-node包 ,在内部偷偷的将ts–js,然后执行js

安装: npm i -g ts-node

提供了ts-node命令

ts-node hello.ts 执行报错,需安装:npm install -D tslib @types/node

3.注释和输出语句

3.1 注释+
//  单行注释  : ctrl + /

/*
	多行注释 : ctrl + shift + /
*/
3.2 输出语句

console.log()

总结:

1.TypeScript 是JS的超集,为JS添加了类型系统。相比于JS,开发体验更友好,提前发现错误,BUG更少,增加开发的幸福度。

2.JavaScript的两个运行环境? node.js和浏览器

3.TypeScript不能直接在浏览器或Node.js中执行

4.如何将ts编译为js? 安装typescript包 ,tsc 文件名.ts ,node 文件名.js

5.如何简化执行ts代码? ts-node 文件名.ts

四、变量和数据类型

1 TS中的变量

1.1 变量的声明方式

声明变量并指定类型

let age:number;

给变量赋值

age = 18

简化方式:

let age:number = 20

TS中的类型注解

:number

类型注解:是一种为变量添加类型约束的方式

约定了什么类型,就只能给变量赋值什么类型的值

1.2 变量的命名规则
  • 变量名称只能出现:数字、字母、下划线(_)、美元符号($)且不能用数字开头
  • 变量名称区分大小写
1.3 变量的命名规范
  • 变量名要顾名思义
  • 使用驼峰命名法

2 TS中的数据类型

2.1 数据类型概述

TypeScript中的数据类型分为两大类: JS已有类型、TS新增类型

JS已有类型:
  • 原始类型:number、null、undefined、string、boolean、symbol
  • 对象类型:object(对象、数组、函数等)
TS新增类型:
  • 联合类型、自定义类型(类型别名)、接口、元组、字面量类型、枚举、void、any等
2.2 原始类型的使用

let age:number = 10

let myName = '刘老师'

let a:null = null

let b: undefined = undefined

let isLoading:boolean = false

2.3 数组类型的写法

数值类型的数组:let numbers: number[]=[1,2,3] <===>let numbers: Array<number> = [1,2,3]

字符串类型的数组let numbers:string[] = ['a','b','c'] <===> let strings: Array<string> = ['a','b','c']

布尔类型的数组let b:boolean[] = [true,false,true]

混合写法:let arr:(number|string)[]=[1,2,'a','b']

解释: | (竖线)在TS中叫做联合类型(由两个或多个其他类型组成的类型,表示可以是这些类型中的任意一种)

2.4 类型别名(自定义类型)

为任意类型起别名,简化该类型的使用

type CustomArray = (number | string)[]
let arr1: CustomArray = [1, 'A']
let arr2: CustomArray = ['x', 'y']
console.log('hello', arr1, arr2)
  • 通过type关键字来创建类型别名
  • 类型别名可以是任意合法的变量名称
  • 创建类型别名后,直接使用该类型别名作为变量的类型注解即可
2.5 函数类型

函数的类型实际指:函数参数和返回值的类型

为函数指定类型的两种方式:

  • 单独指定参数、返回值类型

    方式一:
    function add(num1: number, num2: number): number {
        return num1 + num2
    }
    方式二:
    const add = (num1: number, num2: number): number => {
        return num1 + num2
    }
    
  • 同时指定参数、返回值类型 :(num1: number, num2: number) => number

    const add  = (num1, num2) => {
        return num1 + num2
    }
    
    const add: (num1: number, num2: number) => number = (num1, num2) => {
        return num1 + num2
    }
    

    只适用于函数表达式

2.5.1 void类型 (ts新增)

如果函数没有返回值,那么函数返回值的类型为void

function add(name: string): void {
    console.log(name)
}
2.5.2 可选类型

函数参数可传可不传时,在给函数参数指定类型时,用可选参数

可选参数:在可传可不传的参数名称后面添加 ? (问号)

function add(num1: number, num2: number, myName?: string): void {
    console.log(num1, num2, myName)
}

注意:可选参数只能出现在参数列表的最后

2.6 对象类型

JS中的对象是由属性和方法构成的,而TS中对象的类型就是在描述对象的结构(有什么类型的属性和方法)

写法一:
let person: { name: string; age: number; sayHi(): void; greet(name: string): void } = {
    name: 'jack',
    age: 18,
    sayHi() { }, 
    greet(name) { }
}
写法二:
let person: {
    name: string
    age: number
    sayHi:() => void //箭头函数写法
    greet(name: string): void
} = {
    name: 'jack',
    age: 18,
    sayHi() { },
    greet(name) { }
}

总结:

  • 直接使用{} 来描述对象结构时。属性采用属性名:类型的形式;方法采用方法名():返回值类型的形式

  • 如果方法中有参数,再方法名后面的小括号中指定参数类型

  • 在一行代码中指定对象的多个属性类型时,使用分号来分隔

    ​ 如果一行只指定一个属性类型(通过换行来分隔多个属性类型),去掉分号

    ​ 方法的类型也可以使用箭头函数的形式sayHi:() => void

2.6.1对象的可选属性
function myAxios(config: { url: string; methods?: string }) { }
myAxios({
    url: ''
})
2.7 接口

当一个对象类型被多次使用时,一般会使用接口来描述对象的类型,达到复用的目的。

使用 interface 关键字来声明

接口名称可以是任意合法的变量名称

每一行只有一个属性类型,属性类型后没有分号

interface IPerson {
    name: string
    age: number
    sayHi(): void
}
let person: IPerson = {
    name: 'jack',
    age: 19,
    sayHi() { },
}
2.7.1 接口和类型别名的区别

interface 和 type的对比:

相同点: 都可以给对象指定类型

不同点:

  • 接口只能为对象指定类型
  • 类型别名不仅可以为对象指定类型,实际上可以为任何类型指定别名
//类型别名
type IPerson = {
    name: string
    age: number
    sayHi():void
}
//接口
interface IPerson {
    name: string
    age: number
    sayHi(): void
}
let person: IPerson = {
    name: 'jack',
    age: 19,
    sayHi() { },
}
2.7.2 接口的继承extends

extends

interface Point2D {x:number; y: number}
interface Point3D extends Point2D {
    z: number
}
let p3: Point3D = {
    x:
    y:
    z:
}
2.8 元组

元组类型是另外一种类型的数组,它确切的知道包含多少个元素,以及特定索引对应的类型

let position :[number,number] = [39,70]

元组类型可以确切的标记出有多少个元素,以及每个元素的类型

2.9字面量

某个特定的字符串也可以作为TS中的类型。除字符串外,任意的JS字面量(对象、数字等)都可以作为类型使用。

使用模式: 字面量类型配合联合类型一起使用

使用场景:用来表示一组明确的可选值列表

function changeDirection(direction:'up'|'down'|'left'|'right'){
    console.log(direction)
}
changeDirection('up')

相比于string类型,使用字面量类型更加精确、严谨.

2.10 枚举enum

枚举的功能类似于字面量类型+联合类型组合的功能,也可以表示一组明确的可选值

枚举:定义一组命名常量。它描述一个值,该值可以是这些命名常量中的一个。

enum Direction { Up, Down, Left, Right}
function changeDirection(direction: Direction){}

解释:

  • 使用enum 关键字定义枚举。
  • 约定枚举名称、枚举中的值以大写字母开头
  • 枚举中多个值之间通过逗号分隔
  • 定义好枚举后,直接使用枚举名称作用类型注解

其他类型会在编译为JS代码时自动移除,但是枚举类型会被编译为JS代码。一般情况下,推荐使用字面量+联合类型的方式,更直观、简洁、高效

2.10.1 访问枚举成员

直接通过 . 语法访问枚举成员changeDirection(Direction.Up)

2.10.2 枚举成员的值

枚举成员是有值的,默认从0开始递增。

可以给枚举中的成员进行初始化

enum Direction {Up =10, Down, Left, Right}

2.10.3 数字枚举

枚举成员的值为数字的枚举称为数字枚举.

2.10.4 字符串枚举

字符串枚举没有自增长行为,字符串枚举的每个成员必须有初始值

2.11 any

不推荐使用any类型

当值的类型为any时,可以对该值进行任意操作并且不会有代码提示。

let obj: any = {x:0}

2.12 TS中的typeof运算符

js中的typeof 获取数据的类型

ts中的typeof 可以在类型上下文中引用变量或属性的类型,进行类型查询。

使用场景:根据已有变量的值,获取该值的类型,来简化类型书写。

let p ={ x:1,y: 2}
function formatPoint(point: typeof p){}
formatPoint({x:1, y: 200})

解释:

  • typeof出现在类型注解的位置(参数名称的冒号后面)所处的环境就在类型上下文中

  • typeof 只能用来查询变量或属性的类型,无法查询其他形式的类型(函数调用的类型)

    //查询变量或属性的类型
    let p = {x: 1, y: 2}
    let num: typeof p.x // number
    //查询函数调用的类型
    function add(num1: number,num2: number){
        return num1 + num2
    }
    let ret: typeof add(1,2)// 报错
    

VSCode 断点调试配置

步骤:

  1. 准备要调试的ts文件

  2. 添加调试配置

    {
                "type": "node",
                "request": "launch",
                "name": "调试TS代码",
                // ts-node命令: 直接运行ts代码
                //调试时加载ts-node包
                "runtimeArgs": [
                    "-r",
                    "ts-node/register"
                ],
                // 指定调试的ts文件
                "args": [
                    "${workspaceFolder}/a.ts"
                ]
            }
    
  3. 安装调试用的包

在调试时需在当前目录安装这两个包
npm i ts-node typescript

3.TS的补充

3.1 TS的类型推论

在ts中某些没有明确指出类型的地方,TS的类型推论机制会帮助提供类型。

发生类型推论的两种场景:

  • 声明变量并初始化时

    如果先声明变量后赋值,此时应该在声明变量之前指定类型

  • 决定函数返回值时

    根据函数的返回值,推论出该函数的类型

3.2 TS的类型断言

调用querySelector() 通过id选择器获取DOM元素时,拿到的元素类型都是Element。

因为无法根据id来确定元素的类型,所以,该方法就返回一个宽泛的类型:元素(Element)类型。

Element类型只包含所有元素共有的属性和方法.

使用类型断言指定更加具体的类型

方式一:
const aLink = document.getElementById('link') as HTMLAnchorElement
方式二:react中用不了
const aLink= <HTMLAchorElement>documentById('link')

注释:

  • 使用as关键字实现类型断言
  • 关键字as后面的类型是一个更加具体的类型(HTMLAnchorElement 是 HTMLElement的子类型)
  • 在浏览器控制台,通过console.dir($0)打印DOM元素,在属性列表的最后面可以看到该元素的类型

**技巧:**通过console.dir()打印dom元素,在属性的最后面可以看到该元素的类型。

3.3 TS的配置文件
3.3.1 tsconfig.json 文件

项目文件和项目编译所需的配置项。根据它的信息来对代码进行编译

  • tsconfig.json文件所在的目录为项目根目录
  • tsconfig.json 可以自动生成,命令:tsc --init
{
   // 配置选项
   "include": ["./src/**/*"],
   "target": 'es5',
   "exclude": [],
   "extend":[],
   "files": [],
   "compilerOptions": {
       "target":"ES6",//默认会被转换为ES3版本,兼容性好
       "module": "ES6",
       "lib":["dom"],
       "outDir":"./dist",
       "outFile":"./dist/app.js"
       ……  
   }
   
}

“include”: 用来指定哪些ts文件需要被编译

路径:* 任意文件、** 任意目录

“exclude” : 该目录下的文件不会被编译

“extend”: 定义被继承的配置文件

“files”: 指定被编译文件的列表,只有需要编译的文件少时才会用到

“compilerOptions”

“target” : 用来指定ts被编译为的ES版本

“module” : 指定要使用的模块化的规范

“lib”: 用来指定项目中要使用的库

“outDir” : 用来指定编译后文件所在的目录

“outFile” : 将代码合并为一个文件;设置outFile后,所有全局作用域中的代码会合并到同一个文件中

“allowJS”: 是否对js文件进行编译,默认是false

“checkJS”:是否监测js语法是否符合语法,默认是false

“removeComments” : 是否移除注释 true

“noEmit”:不生成编译后的文件 false

“noEmitOnError”: 当有错误时不生成编译后的文件 true

“alwaysStrict”: 用来设置编译后的文件是否使用严格模式false

“noImplicitAny”:不允许隐式any类型 , false

“noImplicitThis”: 不允许不明确类型的this

“strictNullChecks”: 严格的检查空值

“strict” : 所有严格检查的总开关

……

3.3.2 通过命令行方式使用编译配置

除了在tsconfig.json文件中使用编译配置外,还可以通过命令行方式来使用

tsc hello.ts --target es6

  • tsc后带有输入文件时,将忽略tsconfig.json文件

  • tsc后不带输入文件时,会启用tsconfig.json文件

3.4 webpack 打包ts代码

步骤:

  1. 在项目下生产package.json 文件npm init -y

  2. 安装使用webpack时用到的依赖:npm install -D webpack webpack-cli typescript ts-loader

  3. 编写配置文件: webpack.config.js

ts的文件中如果直接书写js语法的代码,那么可以在html文件中直接引入ts文件。

ts文件中有ts的语法代码,需要先把ts文件编译成js文件(tsc .\文件名.ts),然后在html文件中引入js文件。

ts文件中的变量使用的是let进行修饰,编译而成的js文件中的修饰符就变成了var

五、TS高级类型

1 class类

ts增强了es6中的class,ts中的class不仅提供了class的语法功能也作为一种类型存在。

1.1 构造函数
//构造函数
class Person {
	//指定类的实例属性
    age: number
    gender: string

    constructor(age: number, gender: string) {
    	//实例属性初始化
        this.age = age
        this.gender = gender
    }
}

成员初始化后,才可以通过this.age来访问实例成员

需要为构造函数指定类型注解,否则会被隐式推断为any;构造函数不需要有返回值类型

1.2 实例方法
class Point{
    x = 10
    y = 20
    
    scale(n: number): void {
        this.x *= n
        this.y *= n
    }
}

方法的类型注解(参数和返回值)与函数相同

1.3 类的继承

类继承的两种方式:extends 和 implements(js中只有extends)

1.3.1 extends(继承父类)
class Animal {
    move(){}
}
class Dog extends Animal {
    bark(){}
}
const dog = new Dog()

子类Dog继承父类Animal,则Dog的实例对象dog就同时具有了父类Animal和子类Dog的所有属性和方法

1.3.2 implements (实现接口)
interface Singable {
    sing(): void
}
class Person implements Singable {
    sing() {
        console.log('')
    }
}

Person 类实现接口Singable意味着,Person类中必须提供Singable接口中指定的所有方法和属性。

1.4 类成员的可见性

可以使用TS来控制class的方法或属性 对于class外的代码是否可见

可见性修饰符:

  • public 公有的。公有成员可以被任何地方访问,默认可见性,可省略
  • protected 受保护的。仅对其声明所在的类和子类中(非实例对象)可见。对实例不可见
  • private 私有的。只在当前类中可见,对实例对象以及子类也是不可见的
  • readonly 只读修饰符。用来防止在构造函数外对属性进行赋值。只能修饰属性不能修饰方法。只要是eadonly修饰的属性必须手动提供明确的类型。接口和{}都可以使用readonly修饰
//public
class Animal {
    public move(){}
}

//protected
class Animal {
    protected move(){}
    run(){
        this.move()//可以访问move
    }
}
const a  = new Animal()
a.move//出错,实例对象不可以访问move
clas Dog extends Animal {
    bark(){
        this.move() //可以访问move
    }
}
const b = new Dog()
b.move //出错,实例对象不可以访问move

//readonly
class Person {
    readonly age :number = 18
    constructor(age: number){
        this.age = age
    }
    setAge(){
        this.age = 20 //出错,不能修改只读属性的值
    }
}

interface IPerson {
    readonly name: string
}
let obj : Person = {
    name:'jack'
}

let obj: {readonly name: string } = {
    name: 'jack'
}

不能给只读属性赋值

2 类型兼容

两种类型系统:结构化类型系统和标明类型系统

TS采用的是结构化类型系统,类型检查关注的是值所具有的形状。

在标明类型系统(c# java)中,如果是不同的类,则类型无法兼容

2.1对象直接的类型兼容

成员多的可以赋值给成员少的

2.2接口之间的兼容性

成员多的可以赋值给成员少的

class和interface之间也可以兼容

interface Point {
    x: number
    y: number
}
interface Point2D {
    x:number
    y:number
    z:number
}
let p2: Point2D = {
    x:1,
    y:2,
    z:3
}
class Point3D {
    x:number;
    y:number;
    z:number;
}
let p1: Point 
p1 = p2 
let p3: Point2D = new Point3D()
2.3 函数之间的兼容性

需要考虑:参数个数,参数类型,返回值类型

2.3.1 参数个数

参数多的可以兼容参数少的,参数少的可以赋值给参数多的

type F1 = (a:number) => void
type F2 = (a:number, b:number) => void
let f1: F1
let f2: F2 = f1
2.3.2 参数类型

相同位置的参数类型要相同

2.3.3 返回值类型

只关注返回值类型本身

  • 如果返回值类型是原始类型,如果两个类型相同,则可以兼容
  • 如果返回值类型是对象类型,成员多的可以赋值给成员少的

3 交叉类型 &

功能类似于接口继承,用于组合多个类型为一个类型(常用于对象类型)。新的类型具备两个类型的所有属性和方法。

interface Person { name: string }
interface Contact { phone: string }
type PersonDetail = Person & Contact
let obj: PersonDetail = {
    name: 'jack',
    phone: '123'
}
3.1 交叉类型和接口继承的对比

**相同点:**都可以实现对象类型的组合

**不同点:**两种方式实现类型组合时,对于同名属性之间,处理类型冲突的方式不同

  • 接口继承会报错,类型不兼容
  • 交叉类型没有错误,两者都可

4 泛型和keyof

泛型是可以在保证类型安全的前提下,让函数等与多种类型一起工作,从而实现复用,常用于:函数、接口、class中。

4.1 泛型的基本使用

语法: 在函数名称后面添加<> ,尖括号中添加类型变量

创建泛型函数:

function id<Type>(value: Type): Type {return value}

const num = id<number>(10)

解释:

  1. 类型变量Type,是一种特殊类型的变量,它处理类型而不是值。
  2. 该类型变量相当于一个类型容器,能够捕获用户提供的类型(具体什么类型由用户调用该函数时指定)
  3. 类型变量Type 可以是任意合法的变量名称
// 泛型函数
function id<Type>(value: Type): Type {
    return value
}
// 以number类型调用泛型函数
const num = id<number>(10)
// 以string类型调用泛型函数
const str = id<string>('a')

通过泛型可以使函数支持多种不同的类型,实现了复用的同时保证了类型安全

4.1.1 简化泛型函数调用

let num = id(10)

  • 在调用泛型函数时,可以省略 <类型> 来简化泛型函数的调用
  • TS内部会采用一种叫做类型参数推断的机制,来根据传入的实参自动推断出类型变量Type的类型。
4.2 泛型约束

泛型函数的类型变量Type可以代表多个类型,导致无法访问任何属性;需要为泛型添加约束来收缩类型。

添加泛型约束收缩类型的两种方式:

  1. 指定更加具体的类型
  2. 添加约束
4.2.1 指定更加具体的类型
function id<Type>(value: Type[]): Type[] {
    console.log(value.length)
    return value
}
4.2.2 添加约束
interface ILength { length: number }
function id<Type extends ILength>(value: Type): Type {
    console.log(value.length)
    return value
}
  • 通过extends关键字使用接口,为泛型添加约束
  • 该约束表示:传入的类型必须具有length属性
4.2.3多个泛型变量的约束

类型变量之间可以约束(第二个变量受第一个类型变量约束)

function getProp<Type ,Key extends keyof Type>(obj: Type, key: Key){
	return obj[key]
}
let person = {name: 'jack', age: 18}
getProp(person, 'name')
  • keyof关键字接收一个对象类型,生成其键名称(可能是字符串或数字)的联合类型
  • 本实例中的keyof实际上获取的是person对象所有键的联合类型,也就是’name’|‘age’
  • 类型变量Key受Type的约束,Key只能是Type所有键中的任意一个
4.3 泛型接口

接口也可以配合泛型来使用。

interface IdFunc<Type>{
    id: (value: Type) => Type
    ids:()=>Type[]
}

let obj: IdFunc<number> = {
    id(value){return value},
    ids(){}
}
  • 在接口名称后添加<类型变量>,接口就变成了泛型接口。
  • 使用泛型接口时,需要显式指定具体的类型

JS的数组在TS中就是一个泛型接口

4.4 泛型类

class也可以配合泛型来使用

//创建泛型类
class GeberuNumber<NumType> {
    defaultValue: NumType
    add:(x: NumType, y:NumType) => NumType
}
//使用泛型类
const myNum = new GeberuNumber<number>()
myNum.defaultValue = 10
4.5 泛型工具类型

泛型工具类型: TS内置了一些常用的工具类型,来简化TS中一些常见的操作。

4.5.1 Partial

语法:Partial<Type> 用来构造一个类型,将Type的所有属性设置为可选

interface Props {
    id: string
    children: number[]
}
type PartialProps = Partial<Props>

构造出来的新类型PartialProps结构和Props相同,但所有属性都变为可选的。

4.5.2 Readonly

语法:Readonly<Type> 用来构造一个类型,将Type的所有属性都设置为readonly只读

4.5.3 Pick

语法:Pick<Type, Keys> 从Type中选择一组属性来构造新类型

interface Props {
    id: string
    title: string
    children: number[]
}
type PartialProps = Pick<Props, 'id'|'title'>
  • Pick工具类型有两个变量:1表示选择谁的属性;2表示选择哪几个属性。
  • 其中第二个类型变量,如果只选择一个则只传入该属性名称即可。
  • 第二个类型变量传入的属性只能是第一个类型变量中存在的属性。
  • 构造出来的新类型PickProps,只有id和title两个属性类型。
4.5.4 Record

语法:Record<Keys,Type> 构造一个对象类型,属性键为Keys,属性类型为Type

type RecordObj = Record<'a'|'b'|'c', string[]>
let obj: RecordObj = {
    a:['1'],
    b:['2'],
    c:['3']
}

Record工具类型有两个变量:1表示对象有哪些属性;2表示对象属性的类型。

构建的新对象类型RecordObj表示:这个对象有三个属性分别为:a/b/c,属性值的类型都是string[]

5 索引签名类型 和索引查询类型

5.1 索引签名类型

使用场景:当无法确定对象中有哪些属性(对象中可以出现任意多个属性),可以使用索引类型实现。

interface AnyObject {
    [key: string]: number
}

let obj: AnyObject ={
    a:1,
    b:2,
}

//数组的使用-索引签名类型
interface myArray<Type>{
    [index: number]: Type
}
let arr1:myArray<number> = [1,3,5]
arr1[0]

JS对象中键是string类型的

5.2 索引查询类型

T[P]——索引查询类型 。 作用:用来查询属性的类型

type Props = {a:number,b:number,c:number}
type TypeA = Props['a']

Props[‘a’]表示查询类型Props中属性‘a’对应的类型number,所以,typeA的类型为number

[]中的属性必须存在于被查询类型中,否则会报错

5.2.1 同时查询多个索引的类型
type Props = {a:number,b:string,c:boolean}
type TypeA = Props['a'|'b'] //number/string
type TypeB = Props[keyof Props]  //number/string

使用keyof操作符获取props中所有键对应的类型

6 映射类型

基于旧类型创建新类型(对象类型),减少重复,提升开发效率。

6.1 根据联合类型创建
type PropKeys = 'x'|'y'|'z'
type Type1 = {x:number,y:number,z:number}
type Type2 = {[Key in PropKeys]: number}
  • Key in PropKeys 表示Key可以是PropKeys联合类型中的任意一个,类似于for in
  • 映射类型只能在类型别名中使用,不能在接口中使用
  • 使用映射类型创建的新对象类型Type2和类型Type1结构完全相同
6.2 根据对象类型创建

映射类型除了根据联合类型创建新类型外,还可以根据对象类型来创建新类型

type Props = {a:number,b:number,c:number}
type Type3 = {[Keys in keyof Props]: number }
  • keyof Props 获取到对象类型Props中所有键的联合类型,即a|b|c
  • Key in……表示Key可以是Props中所有的键名称中的任意一个
6.3 泛型工具栏Partial 的实现

七 TS的类型声明文件

类型声明文件:用来为已存在的JS库提供类型信息;在TS项目中使用这些库时,就像用TS一样,都会有代码提示、类型保护机制。

1 TS的两种文件类型

两种文件类型:.ts 文件 和 .d.ts 文件

.ts是代码实现文件,编写程序代码的地方,既包含类型信息又可执行代码。可以被编译为.js文件

.d.ts是类型声明文件,为JS提供类型信息,只包含类型信息的类型声明文件。不会生成.js文件

2 类型声明文件的使用说明

2.1 使用已有的类型声明文件
  • 内置类型声明文件:TS为JS运行时可用的所有标准化内置API都提供了声明文件。 Ctrl + 鼠标左键

  • 第三方库的类型声明文件:库自带类型声明文件或由DefinitelyType提供

    ​ 可以通过npm下载该仓库提供的TS类型声明包,包格式为 @types/*

    typescriptlang.org—>Tools---->Type Search

2.2 创建自己的类型声明文件

两种情况下需要创建自己的类型声明文件:项目内共享类型和为已有JS文件提供类型声明

2.2.1 项目内共享类型

如果多个.ts文件中都用到同一个类型,可以创建.d.ts文件提供该类型,实现类型共享。

操作步骤:

  1. 创建 index.d.ts 类型声明文件
  2. 创建需要共享的类型,并使用export导出(TS中的类型也可以使用import/export实现模块化功能)
  3. 在需要使用共享类型的.ts文件中,通过import导入(.d.ts 可省略)
2.2.2 为已有的JS文件提供类型声明
  • 将JS项目迁移到TS项目时,为了让已有的.js文件有类型声明。
  • 成为库作者,创建库给其他人使用

类型声明文件的编写与模块化方式相关,不同的模块化方案有不同的写法。

开发环境准备: 使用webpack 搭建,通过ts-loader 处理.ts文件。

declare 关键字:用于类型声明,为其他地方已存在的变量声明类型,而不是创建一个新的变量。

  • 对于type、interface等这些明确就是TS类型的(只能在TS中使用的),可以省略declare关键字
  • 对于let、function等具有双重含义(在JS、TS中都能用),要用declare关键字,明确指定此处用于类型声明。
//utils.d.ts
declare let count: number
declare let songName: string
interface Point {
    x: number
    y: number
}
declare let position: Point
declare function add(x: number,y: number): number
declare function changeDirection(direction: 'up'|'down'|'left'|'right'):void
type FomartPoint = (point: Point) => void
declare const fomartPoint: FomartPoint
……
//注意:类型提供好以后,需要使用模块化方案中提供的模块化语法来导出声明好的类型。然后才能在其他的.ts文件中使用
export {count, songName, poisition, add, changeDirection, fomartPoint……}

//其他.ts文件
import {count, songName, poisition, add, changeDirection, fomartPoint……}  from './utils'

取props中所有键对应的类型

6 映射类型

基于旧类型创建新类型(对象类型),减少重复,提升开发效率。

6.1 根据联合类型创建
type PropKeys = 'x'|'y'|'z'
type Type1 = {x:number,y:number,z:number}
type Type2 = {[Key in PropKeys]: number}
  • Key in PropKeys 表示Key可以是PropKeys联合类型中的任意一个,类似于for in
  • 映射类型只能在类型别名中使用,不能在接口中使用
  • 使用映射类型创建的新对象类型Type2和类型Type1结构完全相同
6.2 根据对象类型创建

映射类型除了根据联合类型创建新类型外,还可以根据对象类型来创建新类型

type Props = {a:number,b:number,c:number}
type Type3 = {[Keys in keyof Props]: number }
  • keyof Props 获取到对象类型Props中所有键的联合类型,即a|b|c
  • Key in……表示Key可以是Props中所有的键名称中的任意一个
6.3 泛型工具栏Partial 的实现
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue 3.0 和 TypeScript 配合使用可以提供更好的类型检查和开发体验。Vue 3.0 在设计上更加注重了对 TypeScript 的支持,提供了更好的类型推断和类型声明支持。 首先,确保你的项目中已经安装了 TypeScript。可以使用以下命令进行安装: ```shell npm install typescript --save-dev ``` 然后,创建一个 `tsconfig.json` 文件来配置 TypeScript。可以使用以下命令创建: ```shell npx tsc --init ``` 在 `tsconfig.json` 文件中,你可以根据自己的项目需求进行配置,例如设置输出目录、编译选项等。 接下来,在 Vue 3.0 项目中使用 TypeScript,需要将文件后缀名改为 `.ts`。例如,将 `.js` 文件改为 `.ts` 文件。 在 Vue 单文件组件中,可以使用 `<script lang="ts">` 标签来指定 TypeScript。在这个标签中,你可以编写 TypeScript 代码,并且通过类型声明来提供类型信息。 例如,你可以在组件中声明 props 的类型: ```typescript <script lang="ts"> import { defineComponent, PropType } from 'vue'; interface Props { message: string; } export default defineComponent({ props: { message: { type: String as PropType<Props['message']>, required: true, }, }, }); </script> ``` 使用 TypeScript 还可以获得更好的代码提示和智能补全。通过声明类型,编辑器可以更好地理解你的代码,并提供相关的提示信息。 除了在单文件组件中使用 TypeScript,你还可以在其他地方使用,例如在 Vue Router、Vuex 和其他库的配置中。 总之,Vue 3.0 和 TypeScript 配合使用可以提供更好的类型检查和开发体验,帮助你写出更可靠和易于维护的代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值