vue3 介绍
-
vue3 之所以设计成按需导入, 是为了使得开发中对源码的依赖尽可能小, 减少源码的文件大小, 能使得 b/s 加载文件的速度加快, 这就称为 tree-shaking
-
vue3 速度快的原因
- 按需导入(tree-shaking), 能够在加载时不用加载那么多源码
- 静态提升(hoistStatic, 源于 diff 算法的优化), 对那些动态变化的节点标记, 在更新时就只需要对比标记节点的元素(就是说原来 vue2 会比较逐层比较所有节点, vue3 就不会比较全部了)
- 事件侦听器缓存(cacheHandlers), vue3 中, 事件函数如果执行过一次, 那么就会缓存起来等待复用
- vue3 的源码都是 ts 写的
注意
- vue3 中将同一功能的数据和处理数据的业务逻辑放在一起
- vue2 适合开发小项目, vue3 组合api 更加适合开发大型项目
- vue3 不必只有一个根节点(这种特性称为Fragment, 内部操作会套一层虚拟节点)
使用 Vite 创建 Vue 项目
npm init vite-app <project-name>
cd <project-name>
npm install
npm run dev
vite 打包和 webpack 打包的区别
main.js 中内容
在src 目录下创建 main.js 和 App.vue 文件
对比 vue2 中的main.js
import Vue from 'vue'
import App from './App'
new Vue({
el: '#app',
render: h => h(App)
})
组合API
setup 入口函数
- 执行时机比beforeCreate 函数早
- this 为undefined
- vue3 可以在setup()中渲染dom, 也就是说可以不用通过在template 标签中书写dom
- 建议数据和方法都写在 setup 函数中,并通过 return 进行返回可在模版中直接使用(一般情况下 setup 不能为异步函数)
- return 的值要用
{}包起来 - setup 中的第一个参数能够拿到 props 中的数据
reactive
reactive 是一个函数,用来将普通
对象/数组包装成响应式式数据使用(基于 Proxy)
- 需要从vue中导入再使用
import { reactive } from 'vue'
ref
也是用来包装响应式数据, 如果不确定数据是什么数据类型, 一般就用ref, 建议就都用ref 包装所有数据, 可以替代reactive
- 需要从vue中导入再使用
import { ref } from 'vue'
- 如果要拿到ref 包装的数据, 要加上
.value - 这种写法会比reactive 调用数据多写一点, 如果是确定的数据类型, 可以用reactive
- ref 的底层是利用 reactive 进行包装 ( 比如将 ref(12), 等价于, reactive({value:12}) ), 所以在调用 ref 数据时需要加一个 .value ; ref 数据在视图中的调用不用加 .value
toRef 让数据具备响应式
- 对state.age 的修改会影响转换后的 age , 对 age 的修改会影响原来的 state.age
toRefs 可以转换响应式对象中的所有的属性为单独的响应式的 ref 对象
-
第一种方式
-
第二种方式
-
toRefs用来把响应式对象转换成普通对象,把对象中的每一个属性,包裹成ref对象
补充
也就是说这里的hobby 是ref中包裹的复杂数据类型, 也就具备reactive类型, 但如果同级有一个简单数据类型如
name:'zs', 那么这里的name 就不具备reactive 类型
ref、toRef、toRefs 小结
建议就用 ref 来设置响应式数据, 用 toRefs 对对象中所有属性进行响应式设置
-
ref、toRef、toRefs 都可以将某个对象中的属性变成响应式数据
-
ref的本质是拷贝,修改响应式数据,不会影响到原始数据,视图会更新
-
toRef、toRefs的本质是引用,修改响应式数据,会影响到原始数据,视图不会更新
-
toRef 一次仅能设置一个数据,接收两个参数,第一个参数是哪个对象,第二个参数是对象的哪个属性
-
toRefs接收一个对象作为参数,它会遍历对象身上的所有属性,然后挨个调用toRef执行
watch
- 监听 reactive 内部数据时,强制开启了深度监听,且配置无效;监听对象的时候 newValue 和 oldValue 是全等的。
- reactive 的【内部对象】也是一个 reactive 类型的数据。
- 对 reactive 自身的修改则不会触发监听(比如说对于侦听对象本身赋值新对象不会触发监听, 因为新对象不具备 reactive 的环境, 如果是对于对象中的数据进行修改则能监听到)
- 监听多个变量时, 可以写在一个数组中
- 设置immediate : true 能够在第一次调用数据时就执行
ref 监听
-
对ref 数据的第一层进行修改 (相当于修改的是 obj.value)
-
对ref 数据开启深度侦听
deep: true
watch(
obj,
(newValue, oldValue) => {
console.log(newValue, oldValue)
console.log(newValue === oldValue)
},
{
deep: true
}
)
- 还可以通过监听 ref.value 来实现同样的效果 (ref不是 reactive 类型, ref.value 是 reactive 类型, reactive 或 ref对象中的第一层复杂数据类型都是reactive 数据, 简单数据类型不是reactive 数据)
<template>
<p>{{ obj.hobby.eat }}</p>
<button @click="obj.hobby.eat = '面条'">修改 obj</button>
</template>
<script>
import { watch, ref } from 'vue'
export default {
name: 'App',
setup() {
const obj = ref({
hobby: {
eat: '西瓜'
}
})
watch(obj.value, (newValue, oldValue) => {
console.log(newValue, oldValue)
console.log(newValue === oldValue)
})
return { obj }
}
}
</script>
组件间的数据传递
注意
- 两种操作会失去响应式
- 对数据的直接解构赋值
- ref 和 toRef 区别 ( ref 是用来将数据变为响应式的数据, 而 toref 是用来将数据和数据源形成响应式连接)
- watch 可以监听
函数 / ref / 数组 / 对象 / reactive, 但不能监听到普通值, 可以写在箭头函数中返回的方式监听普通值
- vue3 中同一个生命周期钩子能够重复调用, 不会覆盖
- vue3 中用setup() 替代了之前的created 和 beforeCreate
- reactive 或 ref对象中的第一层复杂数据类型都是reactive 数据, 简单数据类型不是reactive 数据
补充
- 通过isRef 和 isReactive 方法判断变量
- setup 中参数 attrs 能拿到 非props 数据
- setup 中参数 slots 能拿到 插槽 信息
provide/inject (跨层级组件数据传递)
当涉及到多层组件数据传递时, 可以通过
provide('变量或方法名', 变量或方法)将数据传递出去, 能够在任意层级通过inject('变量或方法名')调用
- 这里的 provide 中的数据可以写成对象的形式, 就不用写多个 provide 进行抛出了
vue3 中的 v-model
- vue2 与 vue3 中 v-model 比较
这里的 $event 拿到的是子组件中 $emit('父组件中触发事件名', 传递参数) 传递过来的第二个参数, 这两种写法都等价于
v-model="msg"(给标签一个属性和一个事件)
<Son :value="msg" @input="msg=$event" />
<Son :modelValue="msg" @update:modelValue="msg=$event" />
- vue3 的 v-model 拆解
这里的v-model:text 相当于修改传到子组件值的名字, 默认为 modelValue ,有冒号就修改为 text, 在子传父的时候也就不再写成 update:modelValue , 而是 update:text
- 父组件
<template>
<!-- 组件绑定 v-model -->
<hy-input v-model="message" v-model:text="inputText"></hy-input>
<h2>{{message}}</h2>
<h2>{{inputText}}</h2>
</template>
<script>
import { ref } from '@vue/reactivity'
import HyInput from "../components/HyInput.vue"
export default {
components: {HyInput },
setup(){
let message = ref("嘿嘿嘿ヽ(*^ー^)(^ー^*)ノ")
let inputText = ref("嘻嘻嘻嘻")
return{
message,
inputText
}
}
}
</script>
- 子组件
<template>
<button @click="handelClick">O(∩_∩)O哈哈~</button>
<br>
<input type="text" v-model="customText">
<br>
</template>
<script>
import {computed} from "vue"
export default {
props:{
modelValue:String,
text:String
},
emits:['update:modelValue',"update:text"],
setup(props,context){
function handelClick() {
context.emit("update:modelValue","O(∩_∩)O哈哈~")
}
let customText = computed({
set(value){
context.emit("update:text",value)
},
get(){
return props.text
}
})
return{
handelClick,
customText,
}
}
}
</script>
-
vue3 中的 v-model 可以在一个标签中多次使用(将 vue2 中的 v-model 和 .sync 特性结合起来了)
-
几种写法等价
子传父 过程中的自定义事件
如果App 中的自定义事件中绑定的事件没有括号, 那么能够拿到右边组件中传递过来的所有参数; 如果有括号, 那么括号中的接收参数就只能拿到右边组件中的第二个参数 (也就是说图中的情况只能拿到'world'字符串, 而不能拿到 $event , 如果要拿到右边组件的所有参数, 可以对右边参数的第二个参数设置为对象或数组)
利用 ref 绑定并调用组件中方法
- 创建 ref 实例
- 导出实例
- 在组件上绑定 ref 实例
- 点击触发子组件
组件上绑定的ref值.value拿到子组件的数据和方法
teleport
能够将 teleport 标签中的 dom 内容移动到 to 设置的标签页中
<teleport to="body">
<dialog v-if="bBar" />
</teleport>
ref 和 toref
import { createApp, ref } from 'vue'
export default {
name: 'demo',
setup () {
let obj = {x: 1, y: 2} // 原数据
let x = ref(obj.x)
let y = toRef(obj, 'y')
const change = () => {
x.value = 2
y.value = 1
console.log(obj)
// 输出 obj{x: 1, y: 1},x, y分别在UI界面上显示2 ,2
}
return {
x,
y
}
}
}
- 注意这里的 ref 操作只是拷贝了 obj 中属性的值并赋值给 x, 而 toref 将 obj 的属性变为, 它会保持对其源 property 的响应式连接
- reactive 用于为
对象添加响应式状态。 - ref 用于为
数据添加响应式状态。 - toRef 用于为源响应式对象上的属性新建一个ref,从而保持对其源对象属性的响应式连接。
- toRefs 用于将响应式对象转换为结果对象,其中结果对象的每个属性都是指向原始对象相应属性的ref。
vue3 生命周期中钩子函数
vue2 与 vue3 钩子函数对应
注意 beforeCreate 和 created 在 vue3 中不再使用, 中间的四个函数没怎么变, 然后销毁函数变为 unmounted, 捕捉错误的函数都没怎么用过, 所有的钩子函数都写在 setup 函数中
- beforeCreate -> use setup()
- created -> use setup()
- beforeMount -> onBeforeMount
- mounted -> onMounted
- beforeUpdate -> onBeforeUpdate
- updated -> onUpdated
- beforeDestroy -> onBeforeUnmount
- destroyed -> onUnmounted
- errorCaptured -> onErrorCaptured
注意
- 在
setup()内部,this不是该活跃实例的引用,因为setup()是在解析其它组件选项之前被调用的,所以setup()内部的this的行为与其它选项中的this完全不同。这使得setup()在和其它选项式 API 一起使用时可能会导致混淆。
解决路由缓存问题
- 给
router-view添加key属性
<router-view :key="$route.fullPath"/>
- 使用watch监听id变化重新拉取接口
watch(
()=>{ return route.params.id },
()=>{ loadCategoryList() } // 在id变化的时候重新使用最新的路由id拉取最新数据
)
- 使用
onBeforeRouteUpdate钩子函数
async function loadCategoryList (id) {
const res = await findTopCategory(id)
categoryData.value = res.result
breadName.value = res.result.name
}
onMounted(() => {
loadCategoryList(route.params.id)
})
// 在路由跳转之后更新之前自动执行
// beforeEach((to,from,next)=>{})
onBeforeRouteUpdate((to) => {
// to指代的是目标路由对象 to拿到最新的路由参数id
// 使用最新id获取数据
loadCategoryList(to.params.id)
})
Vue3中this的替代方案
import {defineComponent, getCurrentInstance} from 'vue'
export default defineComponent ({
setup(){
const { proxy, ctx } = getCurrentInstance()
console.log('getCurrentInstance()中的proxy:', proxy)
return {}
}
})
- 打印结果
vue3 中全局挂载 axios 方法
- 下 axios 包
- 在main.js中配置全局引入
const app = createApp(App);
import axios from "axios";
app.config.globalProperties.$axios = axios;
app.use(store).use(router).mount("#app");
- 在组建中使用axios,这里是要是用setup()
<template>
<div class="home">
{{ data.name }}
</div>
</template>
<script>
// @ is an alias to /src
import { reactive, getCurrentInstance, onMounted } from "vue";
export default {
setup() {
let { proxy } = getCurrentInstance();
let data = reactive({
name: "hello"
});
onMounted(() => {
console.log(proxy.$axios);
});
return { data, proxy };
}
};
</script>
vue3 中通过 ref 获取元素
vue3 需要借助生命周期方法,原因很简单,在setup执行时, template中的元素还没挂载到页面上,所以必须在
onMounted之后才能获取到元素。
<template>
<div ref='box'>I am DIV</div>
</template>
<script>
import {ref,onMounted}
export default{
setup(){
let box = ref(null);
onMounted(()=>{
console.log(box.value)
});
return {box}
}
}
</script>
补充
!!数据能将数据转化成布尔值
注意
- vue3 中 computed 计算属性的写法, 如果不用双向绑定时, 就直接在 computed 中包裹一个函数就行, 如果是双向绑定(一般用于 input 标签中), 就需要在 computed 中包裹一个对象, 对象中写 get 和 set 函数
const isShowClear = computed(() => {
return state.list.some((item) => item.flag === true)
}),
const isAll = computed({
get(){
return state.list.every(item=>item.flag)
},
set(val){
state.list.forEach(item=>item.flag = val)
}
})
第一处能够在 list 数组本身发生变化的时候监听到, 但是如果是数组内部的数据发生变化时监听不到; 所以需要在第二处添加深度监听 (这样就不仅能监听数组发生, 还能监听到数组内部的变化)