Vue3的理解或者了解
主要就是几点
-
性能更好了
相应是的原理变成了Proxy,VNode Diff算法进行了优化 -
体积更小了
所有的API都是按需导入的 -
对TS的支持更好了 本身就是TS写的
-
Composition API(组合API)
对于大型项目更利于代码的组织和复用 -
新增加了一些特性(Fragment、Suspense、Teleport…)
1.Vue3之所以设计为按需导入,是为了使得开发中尽可能减小对源码的依赖,减少源码的文件大小,是的B/S加载文件速度加快,这就称为tree-shaking
2.Vue3速度快的原因
- 按需导入(tree-shaking),能够在加载时不用加载那么多的源码
- 静态提升(hoistSatic,源于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中的内容
对比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'
//reactive 内部的对象也是一个 reactive类型的数据
console.log(isReactive(state.hobby))
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对象
const { name, age, address } = toRefs(state)
- 第二种方式
- 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>
组件间的数据传递
注意
-
两种操作会失去响应式
对数据的直接解构赋值
//下面的操作也会是state是取响应式
return {
name:state.name,
age:state.age,
updateAge,
}
- 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 特性结合起来了)
- 几种写法等价
<Son :modelValue="age" @update:modelValie="age = $event"/>
<Son v-model:modelValue = "age" />
<Son v-model="age" />
子传父 过程中的自定义事件
如果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的替代方案
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 数组本身发生变化的时候监听到, 但是如果是数组内部的数据发生变化时监听不到; 所以需要在第二处添加深度监听 (这样就不仅能监听数组发生, 还能监听到数组内部的变化)