1. vue3的生命周期
vue3
的生命周期一般有2种形式写法,一种是基于vue2
的options API
的写法,一种是vue3
特有的Composition API
options API的生命周期
基本同vue2
的生命周期基础,只是为了与生命周期beforeCreate
和created
对应,将beforeDestroy
和destroyed
更名为beforeUnmount
和unmounted
,使用方法同vue2
<template><p>生命周期</p><p>{{msg}}</p><button @click="changeMsg">修改值</button><button @click="toHome">跳转页面</button>
</template>
<script>
import { useRouter } from 'vue-router'export default {name: 'lifeCycles',data() {return {msg: 'hello vue3'}},setup() {console.log('setup')// 在`setup`函数中创建`router`对象,相当于vue2中的`this.router`const router = useRouter()const toHome = () => {router.push({path: '/'})}return {toHome}},beforeCreate() {console.log('beforeCreate');},created() {console.log('created');},beforeMount() {console.log('beforeMount');},mounted() {console.log('mounted');},beforeUpdate() {console.log('beforeUpdate');},updated() {console.log('updated');},// 由vue2 beforeDestroy改名beforeUnmount() {console.log('beforeUnmount');},// 由vue2 destroyed改名unmounted() {console.log('unmounted');},methods: {changeMsg() {this.msg = 'after changed'}}
}
</script>
composition API的生命周期
composition API
的生命周期钩子函数是写在setup
函数中的,它所有生命周期是在vue2生命周期名字前加on
,且必须先导入才可使用
在这种写法中,是没有onBeforeCreate
和onCreated
周期的,setup
等同于(或者说是介于)这两个生命周期
<template><p>composition API生命周期</p><p>{{msg}}</p><button @click="changeMsg">修改值</button><button @click="toHome">跳转页面</button>
</template>
<script>
import { useRouter } from 'vue-router'
// 必须先导入生命周期
import { onBeforeMount, onMounted, onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted
} from 'vue'
export default {name: 'lifeCycles',data() {return {msg: 'hello vue3'}},setup() {console.log('setup')onBeforeMount(() => {console.log('onBeforeMount');})onMounted(() => {console.log('onMounted');})onBeforeUpdate(() => {console.log('onBeforeUpdate');})onUpdated(() => {console.log('onUpdated');})onBeforeUnmount(() => {console.log('onBeforeUnmount');})onUnmounted(() => {console.log('onUnmounted');})// 在`setup`函数中创建`router`对象,相当于vue2中的`this.router`const router = useRouter()const toHome = () => {router.push({path: '/'})}return {toHome}},methods: {changeMsg() {this.msg = 'after changed'}}
}</script>
2. 如何理解 Composition API 和 options API ?
Composition API
带来了什么:
- 更好的代码组织
- 更好的逻辑复用,避免
mixins
混入时带来的命名冲突和维护困难问题 - 更好的类型推导
options API
使用options API
,当代码很多时,即当data, watch, methods
等有很多内容时,业务逻辑比较复杂,我们需要修改一部分时,可能要分别到data/methods/模板
中对应修改,可能需要我们在页面反复横跳来修改,逻辑块会比较散乱。
Composition API
Composition API
则会将业务相关的部分整合到一起,作为一个模块,当要修改,统一到一处修改,代码看起来会更有条理
它包含的内容包括:
- reactive
- ref相关(ref, toRef, toRefs,后面会具体介绍)
- readonly
- watch和watchEffect
- setup
- 生命周期钩子函数
两者的选择:
- 不建议共用,否则容易引起混乱(思路、组织方式、写法都不太一样)
- 小型项目,业务逻辑简单的,建议用
options API
,对新手也比较友好 - 中大型项目、逻辑复杂,建议使用
Composition API
Composition API
它属于高阶技巧,不是基础必会的,有一定的学习成本,是为了解决复杂业务逻辑而设计,就像hooks
在React
中的地位一样
3. 如何理解ref,toRef,toRefs
ref
- 通过
ref
方式创建响应式的值类型,并赋予初始值,并通过.value
的方式获取值或修改值 - 通过
reactive
方式创建响应式的引用类型,并赋予初始值,修改和获取方式同普通对象一样 - 除了以上两种用法,还可以使用
ref
来声明dom
元素,也就是类似vue2
中的用法
<template><p>ref demo</p><p>{{nameRef}}今年{{ageRef}}岁了</p><p>他喜欢{{hobbies.type}}</p>
</template><script>
// 导入ref, reactive, onMounted
<template><p>ref demo</p><p>{{nameRef}}今年{{ageRef}}岁了</p><p>他喜欢{{hobbies.type}}</p><p ref="eleRef">我是refTemplate使用方式的内容</p>
</template><script>
import { ref, reactive, onMounted } from 'vue'
export default {name: 'ref',setup() {// refconst ageRef = ref(3); // ref创建响应式的值类型,并赋予初始值const nameRef = ref('小花')console.log(ageRef.value) // 通过.value方式获取值ageRef.value = 18 // 通过.value方式修改值// reactiveconst hobbies = reactive({type: 'basketball'})console.log(hobbies.type) // basketball,通过obj[key]方式获取值hobbies.type = 'aaaaa' // 通过obj[key]=xxx方式修改值// refTemplateconst eleRef = ref(null)onMounted(() => {// 跟vue2的区别在于,vue2使用this.$refs['eleRef']方式获取dom,这里通过eleRef.value方式获取console.log(eleRef.value) // domconsole.log(eleRef.value.innerHTML) // 我是refTemplate使用方式的内容})// 注意,这些对象都要return出去,否则就不是响应式return {ageRef,nameRef,hobbies,eleRef}}
}
</script>
PS: 小建议,
ref
定义的数据可以加个 Ref
后缀,这样就能区分 ref
和 reactive
定义的变量了 toRef
看定义有点绕
- 针对一个响应式对象(
reactive
封装)的属性prop
- 通过
toRef
创建一个ref
对象,这个ref
对象和reactive
对象的某属性两者保持引用关系
注意,如果toRef是通过普通对象来生成ref
对象,那么普通对象和ref
对象都将不是响应式的
<template><p>toRef demo</p><p>小花今年 - {{ageRef}}岁 - {{state.age}}岁</p>
</template><script>
import { toRef, reactive } from 'vue'
export default {name: 'toRef',setup() {// 定义一个响应式的reactive引用对象const state = reactive({name: '小花', age: 3})// 如果是普通对象,使用toRef,那么它们将都不是响应式的// 也就是说ageRef和state都不是响应式// const state = {// name: '小花', // age: 3// }// 通过toRef创建一个响应值类型ageRef, 这个ageRef和state.age属性保持双向引用const ageRef = toRef(state, 'age')// 修改state.age值时,ageRef也会跟着改setTimeout(() => {state.age = 25}, 1000)// 修改ageRef值时,state.age也会跟着改setTimeout(() => {ageRef.value = 30}, 2000)return {state, ageRef}}
}
</script>
toRefs
- 将响应式对象(
reactive
)的所有属性prop
,转换为对应prop
名字的ref
对象 - 两者保持引用关系
<template><p>toRef demo</p><!-- 这样,模板中就不用写state.name, state.age了,直接写name和age即可 --><p>{{name}}今年 - {{age}}岁</p>
</template><script>
import { toRefs, reactive } from 'vue'
export default {name: 'toRef',setup() {// 定义一个响应式的reactive引用对象const state = reactive({name: '小花', age: 3})// 相当于// const age = toRef(state, 'age')// const name = toRef(state, 'name')// const stateAsRefs = { age, name }const stateAsRefs = toRefs(state)// 修改state.age值时,就会映射到ref类型的age上setTimeout(() => {state.age = 25}, 1000)// return stateAsRefs 等同于:// const { age: age, name: name } = stateAsRefs// return { age, name }return stateAsRefs}
}
</script>
应用:
当使用composition API时,抽象出一个模块,使用toRefs
返回响应式对象,这样,在接收的时候,我们就可以使用解构的方式获取到对象里面的内容,这也是比较符合我们常用的方式
// 封装一个模块,使用toRefs导出对象
export function useFeature() {const state = reactive({x: 1,y: 2})// ...return toRefs(state)
}
// 导入时,可以使用解构方式
import { useFeature } from './features'export default {setup() {// 可以在不丢失响应式的情况下解构const { x, y } = useFeature()return { x, y }}
}
ref, toRef, toRefs 使用小结:
- 用
reactive
做对象的响应式,用ref
做值类型的响应式 - setup中返回
toRefs(state)
,或toRef(state, prop)
-
ref
变量命名建议用xxxRef
- 合成函数返回响应式对象时,使用
toRefs
为什么需要 ref ?
- 如果没有
ref
,普通的值类型定义,没法做响应式 -
computed,setup,合成函数
,都有可能返回值类型,要保证其返回是响应式的 - 如果vue不定义
ref
,用户可能会自己造ref
,反而更加混乱
为什么需要.value ?
-
ref
是一个对象(保证响应式),value
用来存储值 - 通过
.value
属性的get
和set
实现响应式 - 用于模板、
reactive
时,不需要.value
,这是因为vue
编译会自动识别,其他情况则需要使用
为什么需要 toRef 和 toRefs ?
- 目的:为了不丢失响应式的情况下,把对象数据分散、扩散(或者说是解构)
- 前提:针对的是响应式对象(reactive封装的对象)
- 本质:不创建响应式(创建是ref和reactive的事),而是延续响应式
4. watch和watchEffect的区别
-
watch
和watchEffect
都可以监听data
的变化 -
watch
需要指定监听的属性,默认初始时不会触发,如果初始要触发,需要配置immediate: true
-
watchEffect
是不需要指定监听的属性,而是自动监听其用到的属性,它初始化时,一定会执行一次,这是为了收集要监听的属性
<template><p>watch 的使用</p><p>numberRef: {{numberRef}}</p><p>{{name}}-{{age}}</p>
</template><script>
import { ref, reactive, toRefs, watch, watchEffect } from 'vue'
export default {name:'Watch',setup() {const numberRef = ref(1000)const state = reactive({name: '小花', age: 3})// 监听ref变量watch(numberRef, (newVal, oldVal) => {console.log('watch:', newVal, oldVal); // watch: 1000 undefined// watch: 200 1000}, {immediate: true // 第三个参数可选,是一些配置项,immediate表示初始化时就执行监听})setTimeout(() => {numberRef.value = 200}, 1000)// 监听对象watch(// 第一参数是监听对象,如果是对象需要使用函数返回形式() => state.age,// 第二个参数是监听的变化值(newVal, oldVal) => {console.log('watch:', newVal, oldVal); // watch: 3 undefined// watch: 18 3},// 第三个参数是配置项{immediate: true, // 初始变化就监听deep: true // 深度监听})setTimeout(() => {state.age = 18}, 1000)return {numberRef,...toRefs(state)}}
}
</script>
// watchEffect监听watchEffect(() => {console.log('watchEffect');console.log(numberRef.value);console.log(state.age);})
5. 在setup中怎么获取组件实例
- 在
setup
和其它compostion API
中没有this
- 如果一定要获取,要使用
getCurrentInstance
获取,并且在挂载后才可获取数据 - 如果是
options API
,则可以像vue2
一样正常使用this
<template><p>get instance</p>
</template><script>
import { getCurrentInstance, onMounted } from 'vue'
export default {name: 'GetInstance',data() {return {x: 1,y: 2}},// composition API// 没有this,需要getCurrentInstance来获取组件实例// 且setup本身是beforeCreate和Created生命周期间的钩子,拿不到data,所以要在onMounted中获取setup() {console.log('this', this); // this undefinedconst instance = getCurrentInstance()console.log('instance', instance.data.x); // instance undefinedonMounted(() => {console.log('instance', instance.data.x); // instance 1}) },// options APImounted() {console.log(this.x); // 1}
}
</script>
6. vue3升级了哪些重要的功能
参考官网升迁指南
createApp
// vue2
const app = new Vue({/*options*/})
Vue.use(/*...*/)
Vue.mixin(/*...*/)
Vue.component(/*...*/)
Vue.directive(/*...*/)// vue3
const app = Vue.createApp({/*options*/})
app.use(/*...*/)
app.mixin(/*...*/)
app.component(/*...*/)
app.directive(/*...*/)
emits属性
- 在
setup
中可以使用emit
向父组件发出事件 - 在子组件中,需要
emits
声明向父组件发出的事件集合
<template>parent<Child msg="hello" @log="log" />
</template>
<script>
import Child from './child.vue'
export default {name: 'emits',components:{Child},methods: {log() {console.log('child emit me!')}}
}
</script>
<!-- Child.vue -->
<script>
export default {name: 'child',props: {msg: {type: String}},emits: ['log'], // 需要声明接收的父组件传递的方法// 在setup方法中,可以使用emit方法与父组件通信setup(props, {emit}) {emit('log')},methods: {one(e) {console.log('one');},two(e) {console.log('two');}}
}
</script>
多事件处理
<template><!-- 可以同时触发多个事件 --><button @click="one($event), two($event)">触发多事件</button>
</template>
fragment
vue2
只允许template
中只有一个元素,如果多个元素,必须用一个元素包裹
vue3
则允许template
中可以直接有多个元素,这样就可以减少dom
层级
移除.sync
vue2中的.sync
vue2
中使用.sync
是对以下语句的语法糖,父组件通过v-bind:xxx.sync='xxx'
来向子组件说明这个属性是双向绑定的,子组件中通过$emit('update:xxx', newVal)
来更新这个值
<text-documentv-bind:title="doc.title"v-on:update:title="doc.title = $event"
></text-document><!-- sync语法糖 -->
<text-document v-bind:title.sync="doc.title"></text-document>
在vue3
中,废除了.sync
的写法,换成一种更具有语义的写法v-model:xxx
,在父组件中使用v-model:xxx
方式说明这个属性是双向绑定的,子组件中通过$emit('update:xxx', newVal)
来更新这个值
<template><p>{{name}}-{{age}}</p><UserInfo v-model:name="name"v-model:age="age"/>
</template><script>
import { reactive, toRefs } from 'vue'
import UserInfo from './userInfo.vue'
export default {name: 'vmodel',components: {UserInfo},setup() {const userInfo = reactive({name: '小花',age: 3})return toRefs(userInfo)}
}
</script><!-- userInfo.vue -->
<template><input type="text" :value="name" @input="$emit('update:name', $event.target.value)"><input type="text" :value="age" @input="$emit('update:age', $event.target.value)">
</template>
<script>
export default {props: {name: {type: String},age: {type: String}}
}
</script>
异步组件的导入方式不一样
vue2
: child: () => import('child.vue')
vue3
:需要defineAsyncComponent
导入,child: defineAsyncComponent(() => import('child.vue'))
teleport
teleport
将我们的模板移动到DOM
中 Vue app
之外的其他位置,比如可以使用teleport
标签将组件在body
层
<template><p>这是放在当前组件下的内容</p><teleport to="body"><p>假设这是个弹窗,直接放到body下</p> </teleport>
</template>
喜欢的朋友记得点赞、收藏、关注哦!!!