# Vue3过渡内容
Vue2到Vue3的过渡版内容,也就是刚开始的setup函数式写法,兼容Vue2与组合式写法,学习Vue3最好直接看最新的文档,不要使用此版本。
# 声明周期变化
vue3.0生命周期最后两个周期有所改变,beforeDestroy
和 destroyed
两个钩子变为 beforeUnmount
和 unmounted
。
beforeUnmount
: 在卸载组件实例之前调用。unmounted
: 卸载组件实例后调用。调用此钩子时,组件实例的所有指令都被解除绑定,所有事件侦听器都被移除,所有子组件实例被卸载。
# 组合式API
组合式API思想就是把原本分散在data、created、methods等相关的逻辑,通过setup集中到一起,这样每个代码块就划分成一个逻辑区,并且能像函数一样抽离公共setup片段,让代码更好的封装和重复使用。在setup中就像写原生js一样,通过return把要使用的数据返回给vue使用。
# 1、组合式写法
<template>
<div @click="toggleShow">点击</div>
<div v-if="isShow">显示内容</div>
</template>
<script>
import { defineComponent, ref } from "vue"
export default defineComponent({
name: 'Name',
components: {},
setup() {
// 通过ref定义一个响应式变量
const isShow = ref(false)
// 定义一个函数
const toggleShow = () => {
isShow.value = false
}
// 向父组件提交事件
const triggerEvent = () => {
context.emit('TRIGGLE_EVENT')
}
// return 出去
return {
isShow,
toggleShow,
}
}
});
</script>
# 2、响应式式数据
在setup中声明变量只能在setup中使用,无法像在data中的数据,可以动态渲染响应,如果要定义响应式数据必须要使用 ref
和 reactive
来定义基础数据类型、数组和对象。基础类型通过ref声明,然后通过.value
进行赋值操作,数组也一样,对象通过reactive来声明,赋值可以直接赋值就可以了。不论在setup中怎么操作,通过return返回后会自动解包,然后直接使用就可以了。
<template>
<div @click="toggleShow">点击</div>
<div v-if="isShow">显示内容</div>
</template>
<script>
import { defineComponent, ref, reactive } from "vue"
export default defineComponent({
name: 'Name',
components: {},
setup() {
// ref定义
const bool = ref(false)
const str = ref('')
const num = ref(0)
const arr = ref([])
// 赋值 使用.value进行赋值,数组也一样,通过
const toggleValue = () => {
bool.value = false
str.value = 'test'
num.value = 18
arr.value.push('1111')
}
// reactive定义
const obj = reactive({
name: '',
age: 18
})
const changeAge = () => {
obj.age = 20
}
// return 出去
return {
isShow,
toggleValue,
}
}
});
</script>
# 3、setup参数和自定义事件
setup接收两个参数 props
和 context
, props为组件传入的props,props是响应式的不能使用使用ES6进行解构操作,如果使用解构的可以获取到值,但不会动态更新了。context为一个普通对象,包含attrs
、slots
、emit
、expose
等属性,emit
相当于$emit
。
在setup使用自定义事件可以使用 context.emit
进行定义,在使用自定义事件前,最好在emits
中先定义自定义事件。
vue3废除了$on
和$off
等属性,所以事件总线不能直接使用了,可以使用插件 mitt 或 tiny-emitter来替代。
<script>
import { defineComponent, ref } from "vue"
export default defineComponent({
name: 'Name',
components: {},
emits: ['TRIGGLE_EVENT'], // 定义自定义事件
setup(props, context) {
const isShow = ref(false)
// 向父组件提交事件
const triggerEvent = () => {
context.emit('TRIGGLE_EVENT', isShow)
}
// return 出去
return {
isShow,
toggleShow,
}
}
});
</script>
# 4、响应式解构
像如果对 props 参数或者 reactive 定义的对象使用ES6解构的话,会失去响应性,再次赋值就无法更新视图。我们可以使用 toRefs
把他转换成响应式的数据再赋值。
props中的值虽不能被解构,但是可以直接使用,直接props.xxx调用接收传入的值就OK了,props使用toRefs解构就是单纯的被解构使用。
一般reactive中的属性直接用 . 调用赋值就行了,除非属性又是一个对象,也可以使用toRefs解构进行动态改变值。
<script>
import { defineComponent, ref, reactive, toRefs, toRef } from "vue"
export default defineComponent({
name: 'Name',
components: {},
props: {
author: {
type: String
},
age: {
type: Number
}
},
setup(props, context) {
let { author, age } = toRefs(props)
// 如果author是一个可选的props,可以使用toRef替代让其变为响应性的
const author = toRef(props, 'author')
console.log(author.value)
const obj = reactive({
name: '',
title: ''
})
let { name, title } = toRefs(obj)
name.value = 'changz'
console.log(obj.name) // changz
// return 出去
return {
author,
obj,
}
}
});
</script>
# 5、在setup获取this
在setup中无法使用 this 的,所以要使用挂载在vue实例上的对象,比如UI插件的全局方法,我们可以通过 getCurrentInstance 方法获取
import { defineComponent, getCurrentInstance } from "vue"
export default defineComponent({
name: 'Name',
components: {},
setup(props, context) {
const golbal = getCurrentInstance().appContext.config.globalProperties
golbal.$message.success('操作成功')
}
});
# 6、使用生命周期
在setup中没有 created 钩子,因为setup就相当于created一样,所有写在生命周期的函数都应该写在setup中,只是一些特殊情况才用生命周期钩子。
beforeMount --> onBeforeMount
mounted --> onMounted
beforeUpdate --> onBeforeUpdate
updated --> onUpdated
beforeUnmount --> onBeforeUnmount
unmounted --> onUnmounted
errorCaptured --> onErrorCaptured
renderTracked --> onRenderTracked
renderTriggered --> onRenderTriggered
activated --> onActivated
deactivated --> onDeactivated
# 7、监听和计算属性
computed 计算属性和vue2一样返回的值不是响应性的,计算好的值最好不要再参与计算。
vue3在setup中监听数据一般使用watchEffect
// 计算
const count = ref(1)
const test = computed(() => count * 2)
const data = reactive({
count: computed(() => count * 2)
})
// 监听value
watch(value, (new, old) => {
/* ... */
})
// 监听多个值
watch([firstName, lastName], (newValues, prevValues) => {
console.log(newValues, prevValues)
})
// 监听某个值,执行。。。
watchEffect(value, getValue)
const getValue = () => {
/* ... */
}
// 停止监听
onst stop = watchEffect(() => {
/* ... */
})
// later
stop()
# 9、provide/inject的使用
提供:provide(name, value) <名称, 值> 接收:inject(name, value) <接收值的名称, 默认值>
通过提供响应性数据,在父组件提供的数据被改变,同样在子组件接收的数据也会被改变。
在父组件中使用provide
<!-- src/components/MyMap.vue -->
<template>
<MyMarker />
</template>
<script>
import { provide, reactive, ref } from 'vue'
import MyMarker from './MyMarker.vue'
export default {
components: {
MyMarker
},
setup() {
const location = ref('North Pole')
const geolocation = reactive({
longitude: 90,
latitude: 135
})
const updateLocation = () => {
location.value = 'South Pole'
}
// provide提供响应性值或者函数
provide('location', location)
provide('geolocation', geolocation)
provide('updateLocation', updateLocation)
}
}
</script>
在子组件中接收inject
<!-- src/components/MyMarker.vue -->
<script>
import { inject } from 'vue'
export default {
setup() {
const userLocation = inject('location', 'The Universe')
const userGeolocation = inject('geolocation')
const updateUserLocation = inject('updateLocation')
return {
userLocation,
userGeolocation,
updateUserLocation
}
}
}
</script>
# ref/reactive的使用
在setup中一般都把数据放在一个reactive对象中,通过toRefs转换多个数据在dom使用,ref一般用来返回dom上的ref。这样reactive里的对象也能全部赋值过去了。
setup() {
const nameRef = ref(null) // dom上的ref
const passRef = ref(null)
// 响应式状态
const state = reactive({
filterInfo: {
limit: '',
size: 10
}
tableData: [],
count: 0,
rules: {}
})
return {
...toRefs(state),
nameRef,
passRef
}
}
# 路由vue-router4
vue3使用路由4.x版本,除了创建的方法变了,其他用法基本一样。
安装
npm install vue-router@4
创建
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/',
name: 'index',
component: () => import(/* webpackChunkName: "home" */ '@/views/home/index.vue')
},
{
path: '/about',
name: 'about',
component: () => import(/* webpackChunkName: "about" */ '@/views/about/index.vue')
},
{
path: '/empower',
name: 'empower',
component: () => import(/* webpackChunkName: "empower" */ '@/views/empower/index.vue')
},
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes: routes
})
export default router
在setup中使用router/route
import { useRouter, useRoute } from 'vue-router'
export default {
setup() {
const router = useRouter()
const route = useRoute()
const pushWithQuery = (query) => {
router.push({
name: 'about',
query: {
...route.query,
},
})
}
},
}
在setup中使用导航卫士
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
import { ref } from 'vue'
export default {
setup() {
// same as beforeRouteLeave option with no access to `this`
onBeforeRouteLeave((to, from) => {
const answer = window.confirm(
'Do you really want to leave? you have unsaved changes!'
)
// cancel the navigation and stay on the same page
if (!answer) return false
})
const userData = ref()
// same as beforeRouteUpdate option with no access to `this`
onBeforeRouteUpdate(async (to, from) => {
// only fetch the user if the id changed as maybe only the query or the hash changed
if (to.params.id !== from.params.id) {
userData.value = await fetchUser(to.params.id)
}
})
},
}
# 状态管理器vuex4
安装
npm install vuex@next --save
创建
import { createStore } from 'vuex'
export default createStore({
state: {
token: '', // token
},
mutations: {
// 设置token
setToken(state, token) {
state.token = token
},
},
actions: {
// 获取用户信息
GetInfo({ commit }) {
return new Promise((resolve, reject) => {
infoApi().then(res => {
commit('setToken', res.token);
resolve(res)
}).catch(error => {
reject(error)
})
})
},
},
getters: {
tokenLength(state) {
return state.token.length
}
}
modules: {
}
})
在setup中使用
import { useStore } from 'vuex'
export default {
setup () {
const store = useStore()
// state和getters
const token = ref(store.state.token)
const count = ref(store.getters.tokenLength)
// mutations
const increment = () => {
store.commit('increment')
}
// actions
const asyncIncrement = () => {
store.dispatch('asyncIncrement')
}
return {
token,
count,
increment,
asyncIncrement
}
}
}
# Teleport
有时候我们封装了一个modal弹框组件,把它放在父组件中引用,但是我们希望它的浮动不只是根据父元素来定位,我们希望它可以在我们想要的祖先元素上定位,或者直接定位在body上。平时需要全局状态或者拆分组件来解决,现在我们可以通过 Teleport
来解决,直接控制在哪个父节点渲染HTML。
// to 可以是DOM元素,或是class和id
<teleport to="body"></teleport>
<teleport to=".box"></teleport>
<teleport to="#box"></teleport>
app.component('modal-button', {
data() {
return {
modalOpen: false
}
},
template: `
<button @click="modalOpen = true">
Open full screen modal! (With teleport!)
</button>
<teleport to="body">
<div v-if="modalOpen" class="modal">
<div>
I'm a teleported modal!
(My parent is "body")
<button @click="modalOpen = false">
Close
</button>
</div>
</div>
</teleport>
`
})
# 函数渲染
vue2函数渲染
// vue2使用createElement创建虚拟DOM VNode
Vue.component('anchored-heading', {
render: function (createElement) {
return createElement(
'h1',
this.blogTitle,
{
// 事件
on: {
'!click': this.clickHandler
},
}
)
}
})
vue3直接使用 h 函数创建
import { h } from "vue"
Vue.component('anchored-heading', {
render() {
return h(
'h1',
{
onClick: this.clickHandler,
// 修饰符
capture: true
},
this.blogTitle // 内容或者子元素
)
}
})
# 全局创建和组件局部创建
# 1、组件的创建
// 全局创建,组件内直接使用
const app = Vue.createApp({...})
app.component('my-component-name', {
/* ... */
})
// 或者
import myComponentName from './myComponentName'
app.component('my-component-name', myComponentName)
// 局部注册
import { defineComponent } from "vue"
import myComponentName from './myComponentName'
export default defineComponent({
components: {
myComponentName
},
})
# 2、函数式组件
/**
* render.js
* 函数式渲染组件
*
* */
import { h } from "vue"
// 动态渲染h标签
export const TitleLevel = {
render() {
return h('h'+this.level)
},
props: {
level: {
type: Number,
required: true
}
}
}
// 全局注册
import { createApp, h } from 'vue'
const app = createApp({})
// 直接在组件中使用<title-level></title-level>
app.component('title-level', {
render() {
return h('h'+this.level)
},
props: {
level: {
type: Number,
required: true
}
}
})
// 或者
import { TitleLevel } from '@/utils/titleLevel.js'
app.component('title-level', TitleLevel)
// 组件内注册
const TitleLevel = {
render() {
return h('h'+this.level)
},
props: {
level: {
type: Number,
required: true
}
}
}
export default defineComponent({
components: {
TitleLevel
},
})
// 或者
import { TitleLevel } from '@/utils/titleLevel.js'
export default defineComponent({
components: {
TitleLevel
},
})
# 3、自定义指令
/**
* directives.js
* 自定义指令
* */
const directives = {
install(app){
// 自动获取焦点
app.directive('focus', {
mounted(el) {
el.focus()
}
})
// app.directive('xxxx', {})
}
}
export default directives
// 全局注册
const app = createApp(App);
app.directive('focus', {
// 当被绑定的元素挂载到 DOM 中时……
mounted(el) {
// 聚焦元素
el.focus()
}
})
// 或者
import directives from '@/utils/directives.js' // 自定义指令
const app = createApp(App);
app.use(directives);
<!-- 局部注册 -->
<input type="text" v-focus>
<script>
export default defineComponent({
directives: {
focus: {
// 指令的定义
mounted(el) {
el.focus()
}
}
}
})
</script>