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.新的特性:
- Composition API(组合api)
- 新的内置组件
- 其他改变
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')
}
}
注意:
- vue2 和vue3 的配置不要混用
- vue2.x的配置(data、methods、computed)可以访问到setup中的属性、方法
- 但在setup中不能访问到vue2.x配置
- 如果有重名,setup优先
- 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的使用
- 通过CDN的方式引入
CDN: 内容分发网络,通过相互连接的网络系统,利用最靠近每个用户的服务器,更快、更可靠的将音乐、图片、应用程序及其他文件发送给用户。 - 下载Vue的js文件,并且手动引入
- 通过npm包管理工具安装使用(webpack)
- 通过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 断点调试配置
步骤:
- 准备要调试的ts文件
- 添加调试配置
{
"type": "node",
"request": "launch",
"name": "调试TS代码",
// ts-node命令: 直接运行ts代码
//调试时加载ts-node包
"runtimeArgs": [
"-r",
"ts-node/register"
],
// 指定调试的ts文件
"args": [
"${workspaceFolder}/a.ts"
]
}
- 安装调试用的包
在调试时需在当前目录安装这两个包
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代码
步骤:
- 在项目下生产package.json 文件
npm init -y
- 安装使用webpack时用到的依赖:
npm install -D webpack webpack-cli typescript ts-loader
- 编写配置文件: 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)
解释:
- 类型变量Type,是一种特殊类型的变量,它处理类型而不是值。
- 该类型变量相当于一个类型容器,能够捕获用户提供的类型(具体什么类型由用户调用该函数时指定)
- 类型变量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可以代表多个类型,导致无法访问任何属性;需要为泛型添加约束来收缩类型。
添加泛型约束收缩类型的两种方式:
- 指定更加具体的类型
- 添加约束
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文件提供该类型,实现类型共享。
操作步骤:
- 创建 index.d.ts 类型声明文件
- 创建需要共享的类型,并使用export导出(TS中的类型也可以使用import/export实现模块化功能)
- 在需要使用共享类型的.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 的实现