1. vue3的组合API函数
1.1. setup函数
在script标签里面,声明的变量或者函数写在setup函数里面,要return返回,模版当中才能使用
<script lang="ts">
import { ref } from 'vue'
export default {
setup() {
const age = ref('123')
const fn = () => {}
return {
age
}
}
}
</script>
假设声明了一个fn函数,但是没有return,在模版中使用了,就会报错
1.2 setup语法糖
如果在script标签上面写了setup关键字,就开启了script语法
1.2.1 响应式数据在模板中能直接使用
<script lang="ts" setup>
import { ref } from 'vue'
+const age = ref('123')
+const fn = () => {} // 下面template要使用,既不需要return,也不需要setup函数了
</script>
<template>
<div id="App">
{{ age }}
<button @click="fn">点击触发fn</button>
<router-view></router-view>
</div>
</template>
1.2.2 import导入的内容也能够在顶层使用
setup语法糖,引入组件,template里面能够直接用,不需要注册
<script lang="ts" setup>
import Helloworld from '@/components/Helloworld.vue'
</script>
<template>
<div id="App">
<Helloworld></Helloworld> // 这里能够直接使用
</div>
</template>
如果不是用setup语法糖,用的是setup函数,引入组件,还是需要注册
<script lang="ts">
import { ref } from 'vue'
+import helloworld from './views/helloworld.vue'
export default {
+ components: {
helloworld
},
setup() {
const age = ref('123')
const fn = () => {}
return {
age,
fn
}
}
}
</script>
1.2.3 props和emits的使用
(1) setup函数的props
setup钩子函数里面,要在setup(props)这样去接受props,然后打印props.变量名的方式来打印,
<script lang="ts">
export default {
+ props: {
name: {
type: String,
default: '小航哥'
}
},
+ setup(props) {
console.log('name', props.name)
return {}
}
}
</script>
<template>
<div class="hello-page">hellohellohellohellohellohello</div>
</template>
<style lang="scss" scoped></style>
解构变量会让变量失去响应式,使用toRefs能够让每个属性都变成ref的数据
<script lang="ts">
export default {
props: {
name: {
type: String,
default: '小航哥'
}
},
setup(props) {
+ const {name} = props
+ const {name} = toRefs(props)
+ console.log(name.value) // 后续访问,就要这样去访问,是ref数据
return {}
}
}
</script>
<template>
<div class="hello-page">hellohellohellohellohellohello</div>
</template>
<style lang="scss" scoped></style>
(2) setup函数里面的emits
在子组件helloworld.vue里面setup函数的第二个参数去接受,context是一个对象,里面有emit属性
<script lang="ts">
import { toRefs } from 'vue'
export default {
props: {
name: {
type: String,
default: '小航哥'
}
},
setup(props, context) {
+ context.emit('fn')
return {}
}
}
在父组件App.vue里面,@自定义事件名="事件函数",这个还是和以前类似的操作
<script lang="ts" setup>
import helloworld from './views/helloworld.vue'
const name = '123'
const fn = () => {
console.log('123123')
}
</script>
<template>
<div id="App">
<helloworld :name="name" @fn="fn"></helloworld>
<router-view></router-view>
</div>
</template>
<style scoped lang="scss">
#App {
display: flex;
height: 100vh;
}
</style>
(3) setup语法糖里面的props的使用
js的写法
<script setup>
const props = defineProps({
name: String
age: Number
})
</script>
ts的写法:
- ts写法,defineProps<>()跟上了一个尖括号,然后在尖括号里面使用了{}花括号,defineProps<{}>(),然后在里面写类型
- 注意,string在下面是小写,在上面的js写法是大写
<script setup lang='ts'>
const props = defineProps<{
name: string
age: number
}>()
</script>
(4) setup语法糖里面的emit的使用
js的写法
<script setup>
const emits = defineEmits(['change', 'delete'])
</script>
ts的写法:
- ts写法,defineEmits后面跟上了一个尖括号,在尖括号里面使用了{}花括号,defineEmits<{}>(),在里面写类型
- 注意,e:'自定义事件名',是固定写法,自定义事件名可以任意的取
<script setup lang='ts'>
const emits = defineEmits<{
(e: 'changeName', id: string):void
}>()
emits('changeName')// 触发这个自定义事件名
emits('changeAge') // changeAge没有在上面的defineEmits注册,会报错
</script>
(5) emits和props的注意点
- setup语法糖里面的父传子,和子传父,可以直接使用
defineProps和defineEmits的API,不需要额外的引入。并且defineProps和defineEmits只能在setup语法糖里面去写 - defineProps里面的变量,在template里面是可以直接用的
<script setup lang='ts'>
const props = defineProps<{
name: string
age: number
}>()
// 但是注意,在script里面要使用props里面的变量,必须用props.变量名的方式
console.log(props.name)
</script>
<template>
<!--这里name可以直接使用-->
<div>{{name}}</div>
</template>
- 父传子如果用defineProps,如何给默认值?目前最好是用
withDefaults
使用泛型,定义props的时候,上面这样我们无法给出默认值
const props = defineProps<{ name: string age: number }>()
withDefaults方法,不需要引入
withDefaults方法的参数1,就是defineProps<{ age: number; name: string; }>(),,参数2是对象,里面写默认值
const props = withDefaults(defineProps<{ age: number; name: string; }>(),
{ age: 123, name: '小航哥' })
具备的优势:
- 代码更加的简洁
- 能够使用纯ts来声明props和编译事件(下面我们会讲props的内容)
- 更好的运行时性能
2. 生命周期钩子函数
setup钩子函数第一
onBeforeMount
onMounted
onBeforeUpdate
onUpdated
onBeforeUnmounted
onUnmounted
onActivated
onDeactivated
因为有setup语法糖,现在一般在onMounted()钩子函数里面去调用获取数据的接口
3. ref函数
vue3,最常用的是通过ref函数来声明响应式的数据,类似于vue2里面的data里面写的数据
let num = ref(1231)
num.value = 456
注意: 1.修改ref声明的变量的值的时候一定用
.value的形式
ref能够声明各种的数据类型
let str = ref(string)
let bool = ref(false)
let und = ref(undefined)
let nul = ref(null)
let arr = ref([])
let obj = ref({name: '小航哥'})
注意:在模板中,ref的数据,会被自动解包,直接使用,不需要
.value
4. reactive函数
该函数用来定义对象,是响应式数据
let obj = reactive({
name: '小航哥',
age: 21
})
明确一个变量是对象就这样定义。有时候我们觉得不确定是什么数据类型,就用ref来定义
let num = ref(null)
reactive定义的变量,修改不需要通过.value的形式
let obj = reactive({
name: '小航哥',
age: 21
})
obj.name = '小帅哥' // 这样可以直接修改
5. toRefs函数
如果解构reactive函数里面的属性,会导致变量失去响应式,使用toRefs函数能够恢复响应式
let obj = reactive({
name: '123',
age: 19
})
const { name } = obj // 解构name变量,放到template里面去,
假设我们用一个按钮,去修改obj的值,发现会修改失败
{{ name }} <button @click="obj.name = '456'">点击修改obj.name</button>
修改后
let obj = reactive({
name: '123',
age: 19
})
+const { name } = toRefs(obj) // 这样修改后就好了
使用ref定义的对象,解构,也会失去响应式,用toRefs,也能够恢复响应式
let obj = ref({ // 用的是ref
name: '123',
age: 19
})
const { name } = toRefs(obj.value)
6. toRef函数
把对象里面的单个属性拎出来,包装成为ref响应式数据,但是这个数据修改后,视图不会更新。就不是响应式数据了
父组件定义了
<script setup lang="ts">
const name1 = '我是name1'
const obj = {
age: 456,
name: '1234'
}
</script>
<template>
<main>
<TheWelcome />
+ <SonView :name="name1" :obj="obj"></SonView>
</main>
</template>
子组件接受
<script setup lang="ts">
import { toRef } from 'vue';
const props = defineProps<{
name: string
obj: {name: string, age:number}
}>()
+const age = toRef(props.obj, 'age')
console.log(age.value, 'age');
const changeAge = () => {
+ age.value = 555
console.log(age.value, 'age');
}
</script>
<template>
+ <div class="sonPage">son我是儿子组件{{ name }}---{{ age }}</div>
<button @click="changeAge">点击修改name的值</button>
</template>
<style lang="scss" scoped></style>
当试图点击按钮,修改age的值的时候,值修改成功,但是页面上的age没能够成功修改
7.计算属性 computed
简单写法
const startIndex = computed(() => {
return (currentPage.value - 1) * pageSize.value
}) // 开始的页码
复杂写法
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
const fullName = computed({
// getter
get() {
return firstName.value + ' ' + lastName.value
},
// setter
set(newValue) {
// 注意:我们这里使用的是解构赋值语法
[firstName.value, lastName.value] = newValue.split(' ')
}
})
</script>
注意:
计算属性,返回的也是ref数据。模板中会自动解包,script中需要通过
.value来访问复杂写法,才可以,修改计算属性的值。
8. watch侦听器
侦听器可以侦听:
- ref数据
- getter函数
- 数组,里面有多个数据
侦听ref数据
const x = ref(0)
watch(x, (newVal, oldVal) => {
console.log(newVal, oldVal)
})
侦听整个对象
const obj = reactive({
count: 123
})
watch(obj, (newVal, oldVal) => {
console.log(newVal, oldVal, newVal === oldVal) // newVal和oldVal是一样的值
})
obj.count = 456
如果是替换整个对象
let obj = reactive({
count: 123
})
watch(
obj,
(newVal, oldVal) => {
console.log(newVal, oldVal, newVal === oldVal)
},
{
immediate: true // 需要开启深度监听,会监听里面的所有的嵌套属性,开销也许会很大
}
)
obj = reactive({
count: 456 // 把整个对象都替换了
})
侦听getter函数
const obj = reactive({
count: 0
})
// 错误写法
watch(obj.count, (newVal, oldVal) => {
console.log(newVal, oldVal)
})
// 正确写法 -> 这样写能够返回一个getter函数
watch(() => obj.count, (newVal, oldVal) => {
console.log(newVal, oldVal)
})
侦听多个数据源
const x = ref(0)
const obj = reactive({
count: 0
})
// 正确写法 -> 这样写能够返回一个getter函数
watch(() => [x, obj.count], (newVal, oldVal) => {
console.log(newVal, oldVal)
})
2. TypeScript的基础知识
如果是vue项目,记得插件安装: TypeScript Vue Plugin
2.0 为什么要用TypeScript
- 能够提供很多的类型提示(具体后面会展开),写代码更加的高效
- 先编译,后执行。编译阶段,写代码时就能减少很多错误,进而减少排错的成本。
Uncaught TypeError这个错误,能够极大程度避免
2.1 简单数据类型
声明类型,采用格式:变量名:类型名的方式来声明
2.1.1 数字number
let num:number
num = 1
// 或者
let num:number = 1
2.1.2 字符串string
let str:string
str = '123'
// 或者
let str:string = '123'
2.1.3 布尔值boolean
let bool:boolean = true
2.1.4 undefined
let und:undefined = undefined // 就只能赋值为undefined 不能赋值为其它值
2.1.5 null
let na:null = null // 只能赋值为null
number,string,boolean,undefined,NaN跟在变量名的后面,表示类型,必须是小写
2.2 联合类型
如果我们希望一个变量,既可以是字符串,也可以是数字
let num:number | string = '1'
num = 2
2.3 数组的声明方式
第一种
let arr:number[] = [1,2,3]
这种方式,数组里面只能是数字,不能是字符串
如果希望数组里面既可以是字符串,也可以是数字,或者其它,依然可以用联合类型
let arr: (number | string)[] = [1, 2, 3, '4']
注意,可别写成这样啦
let arr: number | string[] = [1, 2, 3, '4'] // 这样表示,数字或者是“字符串型的数组”
第二种方式
let arr: Array<number> = [1, 2, 3]
联合类型
let arr: Array<number | string> = [1, 2, 3, '123']
2.4 对象
对象的类型的格式就是{变量键: 变量值;变量键:变量值}
let obj: {name: string} = {
name: '小航哥'
}
如果我把name的类型改为数字,就会报错
let obj: {name: number} = {
name: '小航哥'
}
对象有多个属性,如果还有函数呢
let obj: { name: string; age: number; fn: Function } = {
name: '123',
age: 19,
fn: () => {}
}
但是我们一般使用type关键字声明一个类型,再赋值给变量名
// type关键字
// 变量名之间换行的时候可以没有标点符号
type objType = {
name: string
age: number
fn: Function
}
let obj: objType = {
name: '123',
age: 19,
fn: () => {}
}
2.5 type关键字实现继承
如果我们已经写了一个typeA,里面有很多类型,一个新的类型typeB有很多和typeA相似的,同时又有不一样的类型,简言之就是B包含A,可以使用继承
type typeA = {
name: string
age: number
fn: Function
}
type typeB = typeA & {
height: number
}
let obj: typeB = {
name: '123',
age: 19,
fn: () => {},
height: 180
}
当我们把鼠标经过typeB的时候会有提示
2.6 函数类型
函数类型,第一是要给函数的参数写类型,第二是要给函数的返回值类型。
2.6.1 箭头函数的写法
如下是,限制函数的num1和num2参数类型为number,同时限制它们的返回值必须是number类型
const fn = (num1:number, num2: number):number => {
return num1 + num2 // 这里会有类型推断,num1和num2我们都限制为number,所以当我们返回num1 + num2的时候,会自动推断为number类型
}
上面的函数如果这样写
const fn = (num1:number, num2: number):number => {
return 1 + '1'
}
显示如下:
2.6.2 函数声明的写法
function fn(num1:number, num2:number):number {
return num1 + num2
}
2.6.3 函数的类型别名
type fnType = (num1: number, num2: number) => number // 定义一个类型
const fn:fnType = (num1, num2) => {
return 2
} // 类型直接跟在函数名的后面
2.6.4 函数的可选参数
在参数名字后面加一个问号,就是可选参数,表示这个参数可以不传递。
const fn = (num1:number, num2?: number):number => {
return 1 + '1'
}
可选参数必须在必选参数的后面,下面这样就是错误的
const fn = (num1?:number, num2: number):number => {
return 1 + '1'
}
2.6.5 函数返回void和返回undefined
如果函数返回类型啥也没有限定,默认返回的是void,默认是返回任何值都可以
const fn = (num1:number, num2: number) => {
return 1 + '1'
}
注意:在没有写ts的时候,我们的函数里面没有return任何值时,返回的就是undefined,但是在写ts的时候,我们不能给函数指定返回undefined,不然函数就只能返回undefiend
const fn = (num1: number, num2: number): undefined => {
return 1 + '1'
}
2.7 泛型
如下泛型的写法,当我们传入number时,number给到T,num形参的值就是number类星星,返回值也是number类型,是string就会报错(如图) Fn(num:T):T
function Fn<T>(num: T): T {
return num
}
Fn<number>(1)
Fn<number>(['123'])
我们可以传入复杂一点的类型:
type fnType = {
name: string
age: number
}
function Fn<T>(num: T): T {
return num
}
Fn<fnType>({
name: '123',
age: 19
})
如果定义的类型里面没有height属性,就会出现报错,
2.8 type关键字和interface的区别
1.type 可以基本的数据类型,interface不可以
2.type 可以定义联合类型,interface不可以
3.type 定义类型,继承用&,interface定义类型,继承用extends
2.9 类型推断和类型断言
含义:明确告诉,ts,当前变量是什么类型 => 我们比ts更加清楚,这个是什么的类型。
let a = document.querySelector('a') // 使用这个选择器,我们能够明确
// 它的类型
鼠标经过a变量,有如下提示
但是,如果我给一个a标签添加了class值,通过getElementByClassName获取这个元素
最终鼠标经过a变量,类型就是,HTMLCollectionOf
类型断言
在封装一些通用的组件的时候,一个组件被很多页面使用,那么变量是一个对象的话,可能属性是不一样的,我们不能轻易的写死,这个时候,类型断言就非常重要的
使用as关键字,进行类型断言
let a = document.getElementsByClassName('target') as HTMLAnchorElement
console.log(a)
2.10 组合式API如何写ts
2.10.1 ref
const name = ref<string>('123')
2.10.2 reactive
type objType = {
name: string
}
const name = reactive<objType>({
name: '123'
})
2.10.3 provide
import echarts from 'echarts'
provide('echarts', 'echarts')
子组件接受
const name = inject<echarts>('echarts')
2.11 axios如何封装ts
// 引入axios
// 配置基地址和超时时间
// 配置请求拦截器和相应拦截器
// 配置实例方法
// 导出
import axios, { type Method, type ResponseType } from 'axios'
import { ElMessage } from 'element-plus'
const instance = axios.create({
baseURL:
process.env.NODE_ENV === 'development'
? '/ss'
: 'http://' + window.location.host,
timeout: 30000
})
const language = localStorage.getItem('language')
instance.interceptors.request.use(
(config) => {
if (!language && config.headers) {
// 如果本地存储没有记录语言,就用浏览器的默认习惯
config.headers['Accept-Language'] = navigator.language
} else if (config.headers) {
config.headers['Accept-Language'] = language
}
return config
},
(err) => {
return Promise.reject(err)
}
)
instance.interceptors.response.use(
(response) => {
return response
},
(error) => {
// 如果有status属性,判断是否是403 404 500 401的状态码,如果是,返回data.detail提示;
// 如果没有,直接返回error.response属性
if (error.response && error.response.status) {
const { status } = error.response
if (
status === 403 ||
status === 404 ||
status === 500 ||
status === 401
) {
ElMessage({
type: 'error',
message: error.response.data.detail
})
return Promise.reject(error.response.data.detail)
}
} else if (error.response) {
return Promise.reject(error)
} else {
return Promise.reject(error)
}
}
)
+type Data<T> = {
data: T
status: number
}
+const request = <T>(
url: string,
method: Method = 'GET',
submitData?: object | string,
type?: ResponseType,
headers?: object
) => {
+ return instance.request<T, Data<T>>({
url,
method,
[method.toLowerCase() === 'get' ? 'params' : 'data']: submitData,
responseType: type,
headers: headers
})
}
export default request
T会在调用接口的时候传递进来,给到request函数,形成Data
鼠标按住点击instance.request,进入到axios的index.d.ts文件,我们发现,泛型T是可以传递进来,最终影响返回值promise的值
封装接口的时候
// 获取摄像机
getCameraParams是请求的参数类型
CameraResponse是我们希望的返回值的类型
export const getCameraDataApi = (params: getCameraParams) => {
return request<CameraResponse>('/xxx/xxx/xxx', 'get', params)
}
2.12 枚举
某种场合下,参数只有数字,导致不是很语义化。
<template>
<!-- 我们就不知道1是什么意思了 -->
<div v-if="id === 1">
</div>
</template>
export enum OrderType {
// 问诊订单
/** 待支付 */
ConsultPay = 1,
/** 待接诊 */
ConsultWait = 2,
/** 问诊中 */
ConsultChat = 3,
/** 问诊完成 */
ConsultComplete = 4,
/** 取消问诊 */
ConsultCancel = 5,
// 药品订单
/** 待支付 */
MedicinePay = 10,
/** 待发货 */
MedicineSend = 11,
/** 待收货 */
MedicineTake = 12,
/** 已完成 */
MedicineComplete = 13,
/** 取消订单 */
MedicineCancel = 14
}
<template>
<!-- 我们就不知道1是什么意思了 -->
<div v-if="id === OrderType.ConsultPay">
</div>
</template>