赞
踩
1).监测机制的改变
Proxy
API 对数据代理,监测的是整个对象,而不再是某个属性。2).Vue3支持碎片(Fragments)
3).API模式不同
最大的
区别:Vue2使用选项式
API(Options API)对比Vue3组合式
API(Composition API)4).建立数据的方式不同
Vue2:这里把数据放入data属性中
Vue3:需要使用一个新的setup()方法,此方法在组件初始化构造的时候触发。
使用以下三步来建立响应式数据:
引入ref或reactive
ref()
方法处理,复杂类型数据用reactive()
处理setup()
方法来返回
我们的响应性数据,从而我们的template
可以获取
这些响应性数据 5).生命周期钩子不同 — Lifecyle Hooks
若组件被<keep-alive>
包含,则多出下面两个钩子函
6).父子传参不同
,子组件通过defineProps()
进行接收,并且接收这个函数的返回值进行数据操作。
总结: vue3 性能更高, 体积更小, 更利于复用, 代码维护更方便
Vue 在实例初始化时遍历 data 中的所有属性,并使用 Object.defineProperty
把这些属性全部转为 getter/setter。并 劫持各个属性 getter 和 setter,在数据变化时发布消息给订阅者,触发相应的监听回调,而这之间存在几个问题
Vue3 使用 Proxy 来监控数据的变化。Proxy 是 ES6 中提供的功能,其作用为:用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。相对于Object.defineProperty()
,其有以下特点:
我们知道在数据变更触发页面重新渲染,会生成虚拟 DOM 并进行 patch 过程,这一过程在 Vue3 中的优化有如下
编译阶段的优化:
由于编译阶段的优化,除了能更快的生成虚拟 DOM 以外,还使得 Diff 时可以跳过"永远不会变化的节点",
Diff 优化如下
根据尤大公布的数据就是 Vue3 update
性能提升了 1.3~2 倍
vue2 采用的就是 optionsAPI
(1) 优点:易于学习和使用
, 每个代码有着明确的位置 (例如: 数据放 data 中, 方法放 methods中)
(2) 缺点: 相似的逻辑, 不容易复用, 在大项目中尤为明显
(3) 虽然 optionsAPI 可以通过mixins 提取相同的逻辑, 但是也并不是特别好维护
vue3 新增的就是 compositionAPI
(1) compositionAPI 是基于 逻辑功能 组织代码的, 一个功能 api 相关放到一起
(2) 即使项目大了, 功能多了, 也能快速定位功能相关的 api
(3) 大大的提升了 代码可读性
和 可维护性
vue3 推荐使用 composition API, 也保留了options API
即就算不用composition API, 用 vue2 的写法也完全兼容!!
从React Hook的实现角度看,React Hook是根据useState调用的顺序来确定下一次重渲染时的state是来源于哪个useState,所以出现了以下限制
而Composition API是基于Vue的响应式系统实现的,与React Hook的相比
虽然Compositon API看起来比React Hook好用,但是其设计思想也是借鉴React Hook的。
setup()
函数是 vue3 中,专门为组件提供的新属性。它为我们使用 vue3的 Composition API
新特性提供了统一的入口, setup
函数会在 beforeCreate
、created
之前执行, vue3也是取消了这两个钩子,统一用setup
代替, 该函数相当于一个生命周期函数,vue中过去的data
,methods
,watch
等全部都用对应的新增api
写在setup()
函数中
setup()
接收两个参数 props
和 context
。它里面不能使用 this
,而是通过 context 对象来代替当前执行上下文绑定的对象,context 对象有四个属性:attrs
、slots
、emit
、expose
里面通过 ref
和 reactive
代替以前的 data 语法,return
出去的内容,可以在模板直接使用,包括变量和方法
- <template>
- <div class="container">
- <h1 @click="say()">{{msg}}</h1>
- </div>
- </template>
-
- <script>
- export default {
- setup (props,context) {
- console.log('setup执行了')
- console.log(this) // undefined
- // 定义数据和函数
- const msg = 'hi vue3'
- const say = () => {
- console.log(msg)
- }
- // Attribute (非响应式对象,等同于 $attrs)
- context.attrs
- // 插槽 (非响应式对象,等同于 $slots)
- context.slots
- // 触发事件 (方法,等同于 $emit)
- context.emit
- // 暴露公共 property (函数)
- context.expose
-
- return { msg , say}
- },
- beforeCreate() {
- console.log('beforeCreate执行了')
- console.log(this)
- }
- }
- </script>
script setup是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖。相比于普通的 script 语法更加简洁
要使用这个语法,需要将 setup
attribute 添加到 <script>
代码块上:
格式:
- <script setup>
- console.log('hello script setup')
- </script>
顶层的绑定会自动暴露给模板,所以定义的变量,函数和import导入的内容都可以直接在模板中直接使用
- <template>
- <div>
- <h3>根组件</h3>
- <div>点击次数:{{ count }}</div>
- <button @click="add">点击修改</button>
- </div>
- </template>
-
- <script setup>
- import { ref } from 'vue'
-
- const count = ref(0)
- const add = () => {
- count.value++
- }
- </script>
使用 setup
语法糖时,不用写 export default {}
,子组件只需要 import
就直接使用,不需要像以前一样在 components 里注册,属性和方法也不用 return。
并且里面不需要用 async
就可以直接使用 await
,因为这样默认会把组件的 setup
变为 async setup
用语法糖时,props、attrs、slots、emit、expose 的获取方式也不一样了
3.0~3.2版本变成了通过 import 引入的 API:defineProps
、defineEmit
、useContext
(在3.2版本已废弃),useContext 的属性 { emit, attrs, slots, expose }
3.2+版本不需要引入,而直接调用:defineProps
、defineEmits
、defineExpose
、useSlots
、useAttrs
reactive
reactive()
函数接收一个普通对象,返回一个响应式的数据对象, 相当于 Vue 2.x
中的 Vue.observable()
API,响应式转换是“深层”的——它影响所有嵌套属性。基于proxy来实现,想要使用创建的响应式数据也很简单,创建出来之后,在setup
中return
出去,直接在template
中调用即可
shallowReactive
创建一个响应式代理,它跟踪其自身属性的响应性shallowReactive
生成非递归响应数据,只监听第一层数据的变化,但不执行嵌套对象的深层响应式转换 (暴露原始值)。
ref
ref()
函数用来根据给定的值创建一个响应式的数据对象,ref()
函数调用的返回值是一个对象,这个对象上只包含一个 value
属性, 只在setup函数内部访问ref
函数需要加.value
,其用途创建独立的原始值
reactive
将解包所有深层的 refs
,同时维持 ref 的响应性。当将 ref
分配给 reactive
property 时,ref 将被自动解包
shallowRef
ref()
的浅层作用形式。shallowRef()
常常用于对大型数据结构的性能优化或是与外部的状态管理系统集成
isRef
isRef()
用来判断某个值是否为 ref()
创建出来的对象
toRefs
使用场景: 如果对一个响应数据, 进行解构 或者 展开, 会丢失他的响应式特性!
原因: vue3 底层是对 对象 进行监听劫持
作用: 对一个响应式对象的所有内部属性, 都做响应式处理
readonly
传入ref
或 reactive
对象,并返回一个原始对象的只读代理,对象内部任何嵌套的属性也都是只读的、 并且是递归只读。
isReadonly
检查对象是否是由 readonly
创建的只读对象
shallowReadonly
shallowReadonly
作用只处理对象最外层属性的响应式(浅响应式)的只读,但不执行嵌套对象的深度只读转换 (暴露原始值)
readonly
和const
有什么区别?const
是赋值保护,使用const
定义的变量,该变量不能重新赋值。但如果const
赋值的是对象,那么对象里面的东西是可以改的。原因是const
定义的变量不能改说的是,对象对应的那个地址不能改变readonly
是属性保护,不能给属性重新赋值computed
该函数用来创造计算属性,和过去一样,它返回的值是一个ref对象。 里面可以传方法,或者一个对象,对象中包含set()
、get()
方法
watch
watch
函数用来侦听特定的数据源,并在回调函数中执行副作用。默认情况是懒执行的,也就是说仅在侦听的源数据变更时才执行回调。
- // 监听单个ref
- const money = ref(100)
- watch(money, (value, oldValue) => {
- console.log(value)
- })
-
- // 监听多个ref
- const money = ref(100)
- const count = ref(0)
- watch([money, count], (value) => {
- console.log(value)
- })
-
- // 监听ref复杂数据
- const user = ref({
- name: 'zs',
- age: 18,
- })
- watch(
- user,
- (value) => {
- console.log('user变化了', value)
- },
- {
- // 深度监听,,,当ref的值是一个复杂数据类型,需要深度监听
- deep: true,
- immediate: true
- }
- )
-
- // 监听对象的某个属性的变化
- const user = ref({
- name: 'zs',
- age: 18,
- })
- watch(
- () => user.value.name,
- (value) => {
- console.log(value)
- }
- )
watch
作用是对传入的某个或多个值的变化进行监听;触发时会返回新值和老值;也就是说第一次不会执行,只有变化时才会重新执行
watchEffect
是传入一个函数,会立即执行,所以默认第一次也会执行一次;不需要传入监听内容,会自动收集函数内的数据源作为依赖,在依赖变化的时候又会重新执行该函数,如果没有依赖就不会执行;而且不会返回变化前后的新值和老值
共同点是 watch
和 watchEffect
会共享以下四种行为:
停止监听
:组件卸载时都会自动停止监听清除副作用
:onInvalidate 会作为回调的第三个参数传入副作用刷新时机
:响应式系统会缓存副作用函数,并异步刷新,避免同一个 tick 中多个状态改变导致的重复调用监听器调试
:开发模式下可以用 onTrack 和 onTrigger 进行调试基本上就是在 Vue2 生命周期钩子函数名基础上加了 on
;beforeDestory 和 destoryed 更名为 onBeforeUnmount 和 onUnmounted;然后用setup代替了两个钩子函数 beforeCreate 和 created;新增了两个开发环境用于调试的钩子
安装插件
yarn add vite-plugin-vue-setup-extend -D
配置 vite.config.ts
- import vueSetupExtend from 'vite-plugin-vue-setup-extend'
-
- export default defineConfig({
- plugins: [vue(), vueSetupExtend()],
- })
- <script setup name="MyCom">
- // 必须在script标签里面写一点类容,这个插件才会生效,哪怕是注释
- </script>
vue3中如果注册的是局部组件,那么props是有类型提示的,但是如果注册的是全局组件,props就没有类型提示了
解决办法
- // 在src目录下新建一个文件 global.d.ts
- import XtxSkeleton from '@/components/XtxSkeleton/XtxSkeleton.vue'
- // 参考:
- declare module 'vue' {
- export interface GlobalComponents {
- XtxSkeleton: typeof XtxSkeleton
- }
- }
- export {}
- app.directive('lazy',{ // app.directive('指令名‘,配置对象)
- mounted(el){
- .......
- }
- })
缺点
pinia和vuex4一样,也是vue 官方 状态管理工具(作者是 Vue 核心团队成员)
pinia相比vuex4,对于vue3的 兼容性 更好
pinia相比vuex4,具备完善的 类型推荐 => 对 TS 支持很友好
pinia同样支持vue开发者工具
Pinia 的 API 设计非常接近 Vuex 5 的提案
pinia核心概念
vuex只能有一个根级别的状态, pinia 直接就可以定义多个根级别状态
- // 父组件
- // 原生写法
- <son :model-value="money" @update:modelValue="val=>money = val" />
- // v-mode语法糖写法
- <son v-model="money" v-mode:house="house" />
-
-
- // 子组件
- <button @click="$emit('update:modelValue',modelValue+100)">点我加钱 </button>
好处是什么
为了整合 .sync和v-model
在Vue2中,v-mode只能绑定一个属性,如果需要绑定多个属性则需要借助.sync修饰符
.sync修饰符在Vue3中已被移除,直接被v-model取代。
TypeScript,简称 ts,是微软开发的一种静态的编程语言,它是 JavaScript 的超集。 那么它有什么特别之处呢?
- let isDone: boolean = false;
- // ES5:var isDone = false;
- let count: number = 10;
- // ES5:var count = 10;
- let name: string = "semliker";
- // ES5:var name = 'semlinker';
- const sym = Symbol();
- let obj = {
- [sym]: "semlinker",
- };
-
- console.log(obj[sym]); // semlinker
- let list: number[] = [1, 2, 3];
- // ES5:var list = [1,2,3];
-
- let list: Array<number> = [1, 2, 3]; // Array<number>泛型语法
- // ES5:var list = [1,2,3];
-
Tuple
数组一般由同种类型的值组成,但有时我们需要在单个变量中存储不同类型的值,这时候我们就可以使用元组。在 JavaScript 中是没有元组的,元组是 TypeScript 中特有的类型,其工作方式类似于数组。
元组可用于定义具有有限数量的未命名属性的类型。每个属性都有一个关联的类型。使用元组时,必须提供每个属性的值。
let position: [number, number] = [39.5427, 116.2317]
默认情况下 null
和 undefined
是所有类型的子类型。 就是说你可以把 null 和 undefined 赋值给 number 类型的变量。
- let age: number = null
- let realName: string = undefined
any
在 TypeScript 中,任何类型都可以被归为 any
类型。这让any
类型成为了类型系统的顶级类型(也被称作全局超级类型)。但是不建议使用 any,不然就丧失了 TS 提供的保护机制,失去了使用TS的意义。
unknown
所有类型也都可以赋值给 unknown
。这使得 unknown
成为 TypeScript 类型系统的另一种顶级类型(另一种是 any
)。它的定义和 any
定义很像,但是它是一个安全类型,使用 unknown
做任何事情都是不合法的。
never
never
类型表示的是那些永不存在的值的类型。
有些情况下值会永不存在,比如,
如果一个函数执行时抛出了异常,那么这个函数永远不存在返回值,因为抛出异常会直接中断程序运行。
函数中执行无限循环的代码,使得程序永远无法运行到函数返回值那一步。
never 类型是任何类型的子类型,也可以赋值给任何类型。
没有类型是 never 的子类型,没有类型可以赋值给 never 类型(除了 never 本身之外)。 即使 any
也不可以赋值给 never 。
函数的类型实际上指的是:函数参数
和返回值
的类型
为函数指定类型的两种方式:
单独指定参数、返回值的类型:
- // 函数声明
- function add(num1: number, num2: number): number {
- return num1 + num2
- }
-
- // 箭头函数
- const add = (num1: number, num2: number): number => {
- return num1 + num2
- }
同时指定参数、返回值的类型:
- type AddFn = (num1: number, num2: number) => number
-
- const add: AddFn = (num1, num2) => {
- return num1 + num2
- }
2.2 void 类型
如果函数没有返回值,那么,函数返回值类型为:void
- function greet(name: string): void {
- console.log('Hello', name)
- }
注意:
void
类型- // 如果什么都不写,此时,add 函数的返回值类型为: void
- const add = () => {}
- // 这种写法是明确指定函数返回值类型为 void,与上面不指定返回值类型相同
- const add = (): void => {}
-
- // 但,如果指定 返回值类型为 undefined,此时,函数体中必须显示的 return undefined 才可以
- const add = (): undefined => {
- // 此处,返回的 undefined 是 JS 中的一个值
- return undefined
- }
slice()
也可以 slice(1)
还可以 slice(1, 3)
- function mySlice(start?: number, end?: number): void {
- console.log('起始索引:', start, '结束索引:', end)
- }
?
(问号)跟 JS 的写法一样,在入参里定义初始值。
和可选参数不同的是,默认参数可以不放在函数入参的最后面
函数重载或方法重载是使用相同名称和不同参数数量或类型创建多个方法的一种能力。
不必太纠结函数重载,知道有这个概念即可,平时一般用泛型来解决类似问题。
JS 中的对象是由属性和方法构成的,而 TS 对象的类型就是在描述对象的结构(有什么类型的属性和方法)
对象类型的写法:
- // 空对象
- let person: {} = {}
-
- // 有属性的对象
- let person: { name: string } = {
- name: '同学'
- }
-
- // 既有属性又有方法的对象
- // 在一行代码中指定对象的多个属性类型时,使用 `;`(分号)来分隔
- let person: { name: string; sayHi(): void } = {
- name: 'jack',
- sayHi() {}
- }
-
- // 对象中如果有多个类型,可以换行写:
- // 通过换行来分隔多个属性类型,可以去掉 `;`
- let person: {
- name: string
- sayHi(): void
- } = {
- name: 'jack',
- sayHi() {}
- }
-
- // 方法的类型也可以使用箭头函数形式
- {
- greet(name: string):string,
- greet: (name: string) => string
- }
axios({ ... })
时,如果发送 GET 请求,method 属性就可以省略?
来表示- type Config = {
- url: string
- method?: string
- }
-
- function myAxios(config: Config) {
- console.log(config)
- }
当一个对象类型被多次使用时,一般会使用接口(interface
)来描述对象的类型,达到复用的目的
解释:
interface
关键字来声明接口I
开头- interface IPerson {
- name: string
- age: number
- sayHi(): void
- }
-
-
- let person: IPerson = {
- name: 'jack',
- age: 19,
- sayHi() {}
- }
- interface Point2D { x: number; y: number }
- // 继承 Point2D
- interface Point3D extends Point2D {
- z: number
- }
interface(接口)和 type(类型别名)的对比:
相同点:都可以给对象指定类型
不同点:
interface :
type:
let arr: (number | string)[] = [1, 'a', 3, 'b']
|
(竖线)在 TS 中叫做联合类型,即:由两个或多个其他类型组成的类型,表示可以是这些类型中的任意一种字面量类型
const str = 'Hello TS'
str 是一个常量(const),它的值不能变化只能是 'Hello TS',所以,它的类型为:'Hello TS'
注意:此处的 'Hello TS',就是一个字面量类型,也就是说某个特定的字符串也可以作为 TS 中的类型
任意的 JS 字面量(比如,对象、数字等)都可以作为类型使用
{ name: 'jack' }
[]
18
20
'abc'
false
function() {}
枚举类型
在任何项目开发中,我们都会遇到定义常量的情况,常量就是指不会被改变的值。
TS 中我们使用 const
来声明常量,但是有些取值是在一定范围内的一系列常量,比如一周有七天,比如方向分为上下左右四个方向。
这时就可以使用枚举(Enum)来定义。
- // 创建枚举
- enum Direction { Up, Down, Left, Right }
-
- // 使用枚举类型
- function changeDirection(direction: Direction) {
- console.log(direction)
- }
-
- // 调用函数时,需要应该传入:枚举 Direction 成员的任意一个
- // 类似于 JS 中的对象,直接通过 点(.)语法 访问枚举的成员
- changeDirection(Direction.Up)
枚举实现原理
- enum Direction {
- Up = 'UP',
- Down = 'DOWN',
- Left = 'LEFT',
- Right = 'RIGHT'
- }
-
- // 会被编译为以下 JS 代码:
- var Direction;
-
- (function (Direction) {
- Direction['Up'] = 'UP'
- Direction['Down'] = 'DOWN'
- Direction['Left'] = 'LEFT'
- Direction['Right'] = 'RIGHT'
- })(Direction || Direction = {})
extends
在 TypeScript 中,我们可以通过 extends
关键字来实现继承
super
子类没有定义自己的属性,可以不写 super ,但是如果子类有自己的属性,就要用到 super 关键字来把父类的属性继承过来。
public
public
,公有的,一个类里默认所有的方法和属性都是 public。
private
private
,私有的,只属于这个类自己,它的实例和继承它的子类都访问不到。
protected
protected
受保护的,继承它的子类可以访问,实例不能访问。
static
static
是静态属性,可以理解为是类上的一些常量,实例不能访问。
abstract
abstract
关键字来定义抽象类和抽象方法
抽象类,是指只能被继承,但不能被实例化的类,就这么简单。
抽象类有两个特点:
# (私有字段)
私有字段与常规属性(甚至使用 private
修饰符声明的属性)不同,私有字段要牢记以下规则:
#
字符开头,有时我们称之为私有名称;在 TS 中,某些没有明确指出类型的地方,TS 的类型推论机制会帮助提供类型 换句话说:由于类型推论的存在,有些场合下的类型注解可以省略不写
发生类型推论的 2 种常见场景:
- // 变量 age 的类型被自动推断为:number
- let age = 18
-
- // 函数返回值的类型被自动推断为:number
- function add(num1: number, num2: number) {
- return num1 + num2
- }
有时候你会比 TS 更加明确一个值的类型,此时,可以使用类型断言来指定更具体的类型。
类型断言好比其他语言里的类型转换,但是不进行特殊的数据检查和解构。它没有运行时的影响,只是在编译阶段起作用。
const aLink = document.getElementById('link') as HTMLAnchorElement
另一种语法,使用 <>
语法,这种语法形式不常用,知道即可:
- // 尖括号语法,知道即可:
- const aLink = <HTMLAnchorElement>document.getElementById('link')
在上下文中当类型检查器无法断定类型时,一个新的后缀表达式操作符 !
可以用于断言操作对象是非 null 和非 undefined 类型。具体而言,x! 将从 x 值域中排除 null 和 undefined 。
- const aLink = document.getElementById('link')!
- //如果没有非空断言,使用aLink时会报错,因为页面可能没有link这个标签,得到的就是undefined
function id(value: number): number { return value }
function id(value: any): any { return value }
定义泛型函数
- function id<Type>(value: Type): Type { return value }
-
- function id<T>(value: T): T { return value }
<>
(尖括号),尖括号中添加类型变量,比如此处的 Type调用泛型函数
- const num = id<number>(10)
- const str = id<string>('a')
解释:
<>
(尖括号),尖括号中指定具体的类型,比如,此处的 number同样,如果传入类型 string,函数 id 参数和返回值的类型就都是 string
这样,通过泛型就做到了让 id 函数与多种不同的类型一起工作,实现了复用的同时保证了类型安全
简化泛型函数调用
- // 省略 <number> 调用函数
- let num = id(10)
- let str = id('a')
解释:
<类型>
来简化泛型函数的调用推荐:使用这种简化的方式调用泛型函数,使代码更短,更易于阅读
说明:当编译器无法推断类型或者推断的类型不准确时,就需要显式地传入类型参数
- function id<Type>(value: Type): Type {
- console.log(value.length)
- return value
- }
-
- id('a')
收缩类型
(缩窄类型取值范围)指定更加具体的类型
比如,将类型修改为 Type[]
(Type 类型的数组),因为只要是数组就一定存在 length 属性,因此就可以访问了
- function id<Type>(value: Type[]): Type[] {
- console.log(value.length)
- return value
- }
添加约束
- // 创建一个接口
- interface ILength { length: number }
-
- // Type extends ILength 添加泛型约束
- // 解释:表示传入的 类型 必须满足 ILength 接口的要求才行,也就是得有一个 number 类型的 length 属性
- function id<Type extends ILength>(value: Type): Type {
- console.log(value.length)
- return value
- }
解释:
extends
关键字使用该接口,为泛型(类型变量)添加约束注意:传入的实参(比如,数组)只要有 length 属性即可(类型兼容性)
泛型的类型变量可以有多个,并且类型变量之间还可以约束(比如,第二个类型变量受第一个类型变量约束) 比如,创建一个函数来获取对象中属性的值:
- function getProp<Type, Key extends keyof Type>(obj: Type, key: Key) {
- return obj[key]
- }
- let person = { name: 'jack', age: 18 }
- getProp(person, 'name')
解释:
,
逗号分隔。keyof Type
实际上获取的是 person 对象所有键的联合类型,也就是:'name' | 'age'
- // Type extends object 表示: Type 应该是一个对象类型,如果不是 对象 类型,就会报错
- // 如果要用到 对象 类型,应该用 object ,而不是 Object
- function getProperty<Type extends object, Key extends keyof Type>(obj: Type, key: Key) {
- return obj[key]
- }
泛型接口:接口也可以配合泛型来使用,以增加其灵活性,增强其复用性
- interface IdFunc<Type> {
- id: (value: Type) => Type
- ids: () => Type[]
- }
-
- let obj: IdFunc<number> = {
- id(value) { return value },
- ids() { return [1, 3, 5] }
- }
解释:
<类型变量>
,那么,这个接口就变成了泛型接口。在 TypeScript 中,typeof
操作符可以用来获取一个变量声明或对象的类型。
- interface Person {
- name: string;
- age: number;
- }
-
- const sem: Person = { name: 'semlinker', age: 33 };
- type Sem= typeof sem; // -> Person
-
- function toArray(x: number): Array<number> {
- return [x];
- }
-
- type Func = typeof toArray; // -> (x: number) => number[]
-
keyof
操作符是在 TypeScript 2.1 版本引入的,该操作符可以用于获取某种类型的所有键,其返回类型是联合类型。
- interface Person {
- name: string;
- age: number;
- }
-
- type K1 = keyof Person; // "name" | "age"
- type K2 = keyof Person[]; // "length" | "toString" | "pop" | "push" | "concat" | "join"
- type K3 = keyof { [x: string]: Person }; // string | number
-
in
用来遍历枚举类型:
- type Keys = "a" | "b" | "c"
-
- type Obj = {
- [p in Keys]: any
- } // -> { a: any, b: any, c: any }
在条件类型语句中,可以用 infer
声明一个类型变量并且对它进行使用。
- type ReturnType<T> = T extends (
- ...args: any[]
- ) => infer R ? R : any;
以上代码中 infer R
就是声明一个变量来承载传入函数签名的返回值类型,简单说就是用它取到函数返回值的类型方便之后使用。
有时候我们定义的泛型不想过于灵活或者说想继承某些类等,可以通过 extends 关键字添加泛型约束。
- interface Lengthwise {
- length: number;
- }
-
- function loggingIdentity<T extends Lengthwise>(arg: T): T {
- console.log(arg.length);
- return arg;
- }
-
Partial<T>
的作用就是将某个类型里的属性全部变为可选项 ?
。
- type Partial<T> = {
- [P in keyof T]?: T[P];
- };
在以上代码中,首先通过 keyof T
拿到 T
的所有属性名,然后使用 in
进行遍历,将值赋给 P
,最后通过 T[P]
取得相应的属性值。中间的 ?
号,用于将所有属性变为可选。
Readonly<T>
将 T 中的所有属性设置为只读
Required<T>
将 T 中的所有属性设置为必须
Omit<T, U>
从类型 T
中剔除 U
中的所有属性
- interface IPerson {
- name: string
- age: number
- }
-
- type IOmit = Omit<IPerson, 'age'>
- // 这样就剔除了 IPerson 上的 age 属性。
- // 用泛型来约束收到的数据
- // TS的defineProps写法 , defineProps<....>()
- const {msg='123'}defineProps<{ //设置默认值需要解构,并且添加全局配置
- msg?: string,
- arr: { name: string }[]
- }>()
- // 用TS来子传父 defineEmits<(...):void>()
- const emit = defineEmits<{
- (e: 'changeMsg', val: string): void
- (e: 'addMsg'): void
- }>()
默认值的全局配置
目前,几乎所有常用的第三方库都有相应的类型声明文件
第三方库的类型声明文件有两种存在形式:
库自带类型声明文件:比如,axios
这种情况下,正常导入该库,TS 就会自动加载库自己的类型声明文件,以提供该库的类型声明。
由 DefinitelyTyped 提供:
@types/*
比如,@types/react、@types/lodash 等装饰器是一种特殊类型的声明,它能够被附加到类声明,方法, 访问符,属性或参数上
是一种在不改变原类和使用继承的情况下,动态地扩展对象功能
同样的,本质也不是什么高大上的结构,就是一个普通的函数,@expression
的形式其实是Object.defineProperty
的语法糖
expression
求值后必须也是一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入
模块
TypeScript
与ECMAScript
2015 一样,任何包含顶级 import
或者 export
的文件都被当成一个模块
相反地,如果一个文件不带有顶级的import
或者export
声明,那么它的内容被视为全局可见的
命名空间
命名空间一个最明确的目的就是解决重名问题
命名空间定义了标识符的可见范围,一个标识符可在多个名字空间中定义,它在不同名字空间中的含义是互不相干的
这样,在一个新的名字空间中可定义任何标识符,它们不会与任何已有的标识符发生冲突,因为已有的定义都处于其他名字空间中
如下两种场景需要提供类型声明文件
将公共的类型定义提取出来,写在index.d.ts文件中 , 并导出
- export interface Token {
- token: string
- refreshToken: string
- }
导入接口并使用
- <script setup lang='ts'>
- import {Token} from '.'
- function fn(token:Token){
-
- }
- </script>
编写同名的.d.ts文件
- demo.ts
- utils/index.js
- utils/index.d.ts // 这里是重点
定义类型声明文件
- export declare let count = number
- export declare let songName = string
- export declare let position = {
- x: number,
- y: number
- }
- export declare function add(x: number, y: number): number {
-
- }
- enum Direction {
- 'top',
- 'right',
- 'bottom',
- 'left'
- }
- export declare function changeDirection(direction: Direction): void
- type FomatPoint = (point: number) => void
- export declare const fomatPoint: FomatPoint
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。