Vue3优点
- 首次渲染更快
- diff 算法更快
- 内存占用更少
- 打包体积更小
- 更好的 Typescript 支持
Composition API组合式 API
vite创建项目并运行
创建项目:npm create vite@latest
项目运行:npm run dev
vue3使用 volar 插件 进行语法提示和代码高亮,需要禁用 vetur插件(和vue2有冲突)
注意:vue3 每个组件中可以有多个根节点
组合式API
- 通过data、methods、watch 等配置选项组织代码逻辑是
选项式API写法- 所有逻辑在setup函数中,使用 ref、watch 等函数组织代码是
组合式API写法
setup函数
- 从组件生命周期看,它在
beforeCreate之前执行 - 函数中
this不是组件实例,是undefined,所以vue3中中几乎用不到this - 将模板中使用到的数据、函数,定义在
setup中,并返回
<template>
<div>
<h1 @click="say()">{{msg}}</h1>
</div>
</template>
<script>
export default {
setup () {
// 定义数据和函数
const msg = 'hi vue3'
const say = () => {
console.log(msg)
}
// 返回给模板使用
return { msg , say}
},
}
</script>
setup语法糖
- 省略 export default,return,将 setup 写在 script 标签内
<script setup>
const say = () => console.log('hi')
</script>
ref函数
通常使用 ref 定义响应式数据,不限类型(简单or复杂)
使用 ref 创建的数据,在 js文件中需要 .value(因为ref()返回的是一个响应式==对象==,数据放在value属性中) ,template 中可省略
<script>
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
// 该函数每次将count的值加10
const increment = () => {
count.value += 10
}
return { count, increment}
}
}
</script>
修改 ref 声明的响应式数据可以用赋值号
reactive函数
定义复杂类型的响应式数据
<script>
// 1. 导入函数
import { reactive } from "vue";
export default {
setup() {
// 2. 创建响应式数据对象
const state = reactive({ name: 'tom', age: 18 })
// 3. 返回数据
return { state }
}
};
</script>
注意:修改reactive声明的响应式数据,不能用赋值号,只能用点语法
import { reactive } from 'vue'
let obj = reactive({
name: '小小',
age: 19,
})
// obj = {
// name: '哈哈',
// age: 20,
// }
obj.name = '哈哈' // 修改reactive定义的数据只能用点语法
computed函数
-------------------------> 定义计算属性
在setup中使用 computed 函数,传入回调函数,该函数返回计算好的数据
<script setup>
// 导入computed
import { computed, ref } from "vue";
const scoreList = ref([80, 100, 90, 70, 60]);
// 计算属性
const betterList = computed(() => scoreList.value.filter((item) => item >= 90));
// 改变数据,计算属性改变
</script>
watch函数
-----------------> 监听数据变化
总括:
监听简单数据类型
一个,直接写变量;多个,变量放到数组里
监听复杂数据类型
watch 写第三个参数,开启深度监听
监听对象中的某个属性(精确监听)
() => 对象.属性
语法:watch(需要监听的数据, 数据改变执行函数, 配置对象)
-
监听多个响应式数据,简单数据类型(多个写在数组里)
watch([num, user], () => { console.log('count改变了', num.value) }) -
监听对象数据中的一个属性(简单数据类型) ( 第一个参数为回调函数,监听的数据写在他的返回值里)
watch(() => user.name, ()=> { console.log('name改变了') }) -
监听响应式对象数据中的一个属性(复杂数据类型)。(写法同上,最后一个参数为对象,添加deep,开启深度监听)
还可以写
immediate: true,组件开启默认执行一次watch(() => user, ()=> { console.log('user改变了') }, { deep: true, immediate: true })注意:
用reactive声明的对象,去监听整个对象的变化,不用开启深度监听,可以直接监听到。但是ref不行。
监听复杂数据类型变化后的值,旧值和新值打印出来的都是新值,指向的是同一块内存。
生命周期钩子函数
注意:生命周期钩子函数可以调用多次,按照代码从上往下的顺序执行
- beforeCreate、created 被 setup替代
- beforeDestroyed、destroyed 被 onBeforeUnmount、onUnmounted 替代
- 其余的都是在原来vue2的名字前面加 on (例如:onMounted、onActivated……)
- vue3组合式API在onMounted中发请求
| 选项式API下的生命周期函数使用 | 组合式API下的生命周期函数使用 |
|---|---|
| beforeCreate | 不需要(直接写到setup函数中) |
| created | 不需要(直接写到setup函数中) |
| beforeMount | onBeforeMount |
| mounted | onMounted |
| beforeUpdate | onBeforeUpdate |
| updated | onUpdated |
| beforeDestroyed | onBeforeUnmount |
| destroyed | onUnmounted |
| activated | onActivated |
| deactivated | onDeactivated |
ref获取DOM元素
\1. 创建 ref => const hRef = ref(null) null为固定写法
\2. 模板中建立关联 =>
我是标题
\3. 使用 => hRef.value
<script setup>
import { ref } from 'vue'
const hRef = ref(null)
const clickFn = () => {
hRef.value.innerText = '我不是标题'
}
</script>
ref操作组件-defineExpose
使用 setup函数使得 组件是默认关闭的,组件实例获取不到它内部的数据和函数。所以需要子组件通过 defineExpose() 函数将自身的数据、方法暴露给父组件。
父组件
<template>
// abc 为子组件名
<abc ref="formRef"></abc>
<button @click="fn">按钮</button>
</template>
<script setup>
import { ref } from 'vue'
import abc from './components/form.vue'
const formRef = ref(null)
const fn = () => {
console.log('获取子组件中的num的数据',formRef.value.num)
formRef.value.validate()
}
</script>
注:父组件中通过ref获取实例,在js文件中使用里面的数据和方法,要加.value
子组件
<script setup>
import { ref } from 'vue'
const num = ref(0)
const validate = () => {
console.log('表单校验函数')
}
// 要想在父组件中获取子组件的数据和方法,需要在子组件中使用 defineExpose 暴露给子组件实例
defineExpose({
num,
validate
})
</script>
父传子子传父
父组件通过自定义属性传值
子组件
<script setup>
// defineProps: 接收父组件传递的数据
// 想要在 script 中也操作 props 属性,应该接收返回值
const props = defineProps({
money: Number,
car: String
})
// 子组件通过 defineEmits获取 emit 函数
const emit = defineEmits(['changeMoney'])
const change = () => {
// 通知父组件触发自定义事件changeMoney,且传值10
emit('changeMoney', 10)
}
</script>
跨级组件通讯provide与inject
祖先组件通过 provide 提供后代组件需要依赖的数据或函数
import { provide } from 'vue';
// 要传递的数据
const count = ref(0)
provide('count', count)
// 修改数据的方法 (谁提供的数据,谁负责修改)
const updateCount = (num) => {
count.value += num
}
provide('updateCount', updateCount)
孙子组件通过 inject 注入祖级provide提供的数据或函数
<script setup>
import { inject } from 'vue';
// inject 注入祖级provide提供的数据或函数
const count = inject('count')
const updateCount = inject('updateCount')
</script>
保持响应式-toRefs函数
在使用 reactive 或者 ref 创建的响应式数据被 展开 或 解构 的时候会失去响应式功能。
对象的多个属性都变成响应式数据,并且要求响应式数据和原始数据相关联,并且更新响应式数据时更新界面,这时候toRefs()就派上用场了,它用于批量设置多个响应式数据。
import { reactive, toRefs } from "vue";
const user = reactive({ name: "tom", age: 18 });
// 对每一个属性做包装
const { name, age } = toRefs(user)
toRef(): 和toRefs功能相似,将对象中的某个属性转换为响应式数据
ref、toRef 和 toRefs的区别
ref
接收一个参数,返回一个响应式数据
toRef
接收两个参数,第一个参数是哪个对象,第二个参数是对象的哪个属性。浅拷贝响应式数据里的一个属性,并保留响应式。用于为对象中的一个属性添加响应式。
toRefs
接收一个对象,遍历对象所有属性,挨个调用toRef。用于批量设置多个响应式数据。
toRefs 和 toRef
toRef 和 toRefs 可以用来复制 reactive 里面的属性然后转成 ref,而且它既保留了响应式,也保留了引用,也就是你从 reactive 复制过来的属性进行修改后,除了视图会更新,原有 ractive 里面对应的值也会跟着更新,如果你知道 浅拷贝 的话那么这个引用就很好理解了,它复制的其实就是引用 + 响应式 ref
小结
toRef/toRefs创建的数据改变都会使原数据改变 ;
ref、toRef、toRefs在js中取值需要加 ‘.value’
补充
获取DOM元素类型
浏览器console 输入document.querySelector('元素名').__proto__
获取到元素的原型对象,名字就是该元素的类型。
获取proxy对象的原数据
vue3使用proxy代替vue2的object.defineProperty,相当于在对象前设置了“拦截” ,所以当我们打印一些值的时候是proxy代理之后的是Proxy 对象,Proxy对象里边的[[Target]]才是真实的对象。
解决方法:
- 使用序列化获取
对原数据的深拷贝
JSON.parse(JSON.stringify(Proxy)
-
使用vue3中的 toRaw() 方法获取
注:该方法返回的数据任会保持原数据的响应和引用
import { toRaw } from 'vue' const res = toRaw(Proxy)
pinia解构失去响应式
pinia仓库做解构拿到的数据会失去响应式,要使用storeToRefs来保持响应式,这只针对简单数据类型来说。复杂数据类型解构出任保持响应式。
import { storeToRefs } from 'pinia'
const { age } = storeToRefs(store)
axios使用泛型
当使用配置写法axios()时,可以换成axios.request()。axios.request 是axios插件提供的一个支持泛型的方法,可以在request后面传入泛型,指定 data 返回值中的类型。
当使用方法别名写法axios.get()时,默认支持泛型。
路由的使用
创建路由对象
import { createRouter, createWebHistory } from 'vue-router'
// createRouter 创建路由实例,===> new VueRouter()
// history 是路由模式,hash模式,history模式
// createWebHistory() 是开启history模块 http://xxx/user
// createWebHashHistory() 是开启hash模式 http://xxx/#/user
// vite 的配置 import.meta.env.BASE_URL 是路由的基准地址,默认是 ’/‘
// https://vitejs.dev/guide/build.html#public-base-path
// 如果将来你部署的域名路径是:http://xxx/my-path/user
// vite.config.ts 添加配置 base: my-path,路由这就会加上 my-path 前缀了
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: []
})
export default router
小结:
- 如何创建实例的方式?
createRouter()
- 如何设置路由模式?
createWebHistory()或者createWebHashHistory()
import.meta.env.BASE_URL值来自哪里?vite.config.ts的base属性的值
base作用是什么?- 项目的基础路径前缀,默认是
/
- 项目的基础路径前缀,默认是
路由跳转
跳转
import { useRouter } from 'vue-router'
const router = useRouter()
// 跳转传参
router.push({ name: 'user', params: { userId: '123' }})
router.push({ path: 'register', query: { userId: '123' }})
接收参数
import { useRoute } from 'vue-router'
const route = useRoute()
//query
let userId=route.query.userId;
//params
let userId=route.params.userId;
路由守卫权限控制
vue3中该回调函数中只有两个参数to , from。没有next。不返回或者返回true,就是放行。返回一个路径,就是强制跳转到该页面
router.beforeEach((to, from) => {
const store = useUserStore()
const whiteList = ['/login']
if (!store.user?.token && !whiteList.includes(to.path)) return '/login'
})
TS中的Omit、Pick
Pick: 从类型对象中取出指定的属性类型 Omit: 从类型对象中排出指定的属性类型,得到剩余的
type OmitUser = Omit<User, 'token'>
TS中的Partial 、Required
Required: 转换为全部必须
Partial: 转换为全部可选
type Image = {
id: string
url: string
}
type changeImage = Partial<Image>
TS中的InstanceType
获取组件实例的类型
InstanceType<typeof 组件>
history路由对象
是全局的路由对象
// 查看当前可以回退的历史记录
console.log(history.state.back)
场景:顶部导航栏,左侧返回按钮添加返回功能时,判断当前是否有可以回退的历史记录
组件类型校验无法生效问题
当我们使用插件自动导入组件时,会发现组件内定义的类型检查没有生效。
新建 components.d.ts文件,如下配置
// 核心代码
// 1. 导入组件实例
import NavBar from './NavBar.vue'
// 2. 声明 vue 类型模块
declare module 'vue' {
// 3. 给 vue 添加全局组件类型,interface 和之前的合并
interface GlobalComponents {
// 4. 定义具体组件类型,typeof 获取到组件实例类型
// typeof 作用是得到对应的TS类型
VanNavBar: typeof NavBar;
}
}
v-model
vue2 => :value="show" + @input="show = $event"
vue3,支持写多个,去掉了sync修饰符。
<!-- vue3父组件中 -->
<!-- 前一个是默认的,后一个是自定义的,类似sync写法 -->
<son-dialog v-model="show" v-model:my="show"></son-dialog>
// vue3子组件中
defineProps<{
// 属性名固定 modelValue
modelValue: boolean,
my: boolean
}>()
const emit = defineEmits<{
// 事件名固定写成 update:modelValue
(e: 'update:modelValue', data: boolean): void,
(e: 'update:my', data: boolean): void
}>()
组件路由守卫
onBeforeRouteLeave 组件内的路由离开守卫
// onBeforeRouteLeave vue-router组合式API,组件内的离开守卫,可以通过返回false 来取消这次页面跳转。
onBeforeRouteLeave(() => {
// 业务需要:已经创建订单了,就不能回到上一页了
if (orderId.value) return false
})
WebSocket
原生js建立连接
// 创建ws实例,建立连接
var ws = new WebSocket("wss://javascript.info/article/websocket/demo/hello");
// 连接成功事件
ws.onopen = function(evt) {
console.log("Connection open ...");
// 发送消息
ws.send("Hello WebSockets!");
};
// 接受消息事件
ws.onmessage = function(evt) {
console.log( "Received Message: " + evt.data);
// 关闭连接
ws.close();
};
// 关闭连接事件
ws.onclose = function(evt) {
console.log("Connection closed.");
};
项目中常用 socket.io-client 来实现客户端通讯。
socket.io建立连接
如何使用客户端js库?
pnpm add socket.io-client
如何建立连接?浏览器和服务器建立双向通信(类似打电话)
import { io } from 'socket.io-client'
// 参数1:不传默认是当前服务域名,开发中传入服务器地址
// 参数2:配置参数,根据需要再来介绍
const socket = io(url, options)
确定连接的状态
socket.on('connect', () => {
// 建立连接成功会执行该回调函数
console.log('连接成功!')
})
socket.on('error', (event) => {
// 错误异常消息
console.log('error')
})
socket.on('disconnect', () => {
// 已经断开连接
console.log('disconnect')
})
如何发送消息?浏览器向服务器发消息
// chat message 发送消息事件,由后台约定,可变
socket.emit('chat message', '消息内容')
如何接收消息?接收服务器发送的消息
// chat message 接收消息事件,由后台约定,可变
socket.on('chat message', (ev) => {
// ev 是服务器发送的消息
})
如何关闭连接?
// 离开组件需要使用
socket.close()
小结:
- sockt.io 在前端使用的js库需要知道哪些内容?
- 如何建立链接
io('地址') - 连接成功的事件
connect - 如何发消息
emit+ 事件 - 如何收消息
on+ 事件 - 如果关闭连接
close()
- 如何建立链接