Vue3 新特性
Vue3 新特性
createApp
在 Vue 3 中,改变全局 Vue 行为的 API 现在被移动到了由新的 createApp 方法所创建的应用实例上。
import { createApp } from 'vue'
const app = createApp({})
调用 createApp 返回一个应用实例。该实例提供了一个应用上下文。应用实例挂载的整个组件树共享 相同的上下文,该上下文提供了之前在 Vue 2.x 中“全局”的配置。
另外,由于 createApp 方法返回应用实例本身,因此可以在其后链式调用其它方法 vue3.0 中使用 createApp 来创建 vue 实例
import { createApp } from 'vue'
const app = createApp({})
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.mount('#app')
main.js 下加载 router、vuex
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
const app = createApp(App)
app.use(store)
app.use(router)
app.mount('#app')
// 合并之后的代码:
createApp(App).use(store).use(router).mount('#app')
组合式 API
Vue2 是 选项式 API(Option API),一个逻辑会散乱在文件不同位置(data、props、computed、watch、生命周期函数等),导致代码的可读性变差,需要上下来回跳转文件位置。Vue3 组合式 API(Composition API)则很好地解决了这个问题,可将同一逻辑的内容写到一起。
除了增强了代码的可读性、内聚性,组合式 API 还提供了较为完美的逻辑复用性方案,举个 🌰,如下所示公用鼠标坐标案例。
// main.vue
<template>
<span>mouse position {{ x }} {{ y }}</span>
</template>
<script setup>
import { ref } from 'vue'
import useMousePosition from './useMousePosition'
const {x, y} = useMousePosition()
}
</script>
// useMousePosition.js
import { ref, onMounted, onUnmounted } from 'vue'
function useMousePosition() {
let x = ref(0)
let y = ref(0)
function update(e) {
x.value = e.pageX
y.value = e.pageY
}
onMounted(() => {
window.addEventListener('mousemove', update)
})
onUnmounted(() => {
window.removeEventListener('mousemove', update)
})
return {
x,
y
}
}
</script>
setup 函数
- 简介
setup 函数是 vue3 中专门为组件提供的新属性。 - 执行时机
创建组件实例,然后初始化 props,紧接着就调用 setup 函数,会在 beforeCreate 钩子之前被调用。 - 模板中使用
如果 setup 返回一个对象,则对象的属性将会被合并到组件模板的渲染上下文。 - 如何使用
<template>
<div>
{{ name }}
</div>
</template>
<script>
import { reactive } from "vue"
export default {
props: {
item: String
}
//setup函数会在beforeCreate之后,created之前执行 setup相当于是预设配置
//setup函数的第一个形参,接收props数据,通过props.item获取
setup(props) {
//创建响应式数据对象
const state = reactive({
name: 'abc'
})
//setup函数中将响应式数据对象return出去供template使用
return state
}
}
</script>
注意:在 setup()函数中无法访问到 this
reactive 函数
- 简介
reactive()函数接收一个普通对象,返回一个响应式的数据对象 - 基本语法
// 按需导入reactive函数
import { reactive } from 'vue'
// 创建响应式数据对象
const state = reactive({ id: 1 })
- 定义响应式数据供 template 使用
<script lang="ts">
import { defineComponent } from 'vue'
// 1)按需导入reactive函数
import { reactive } from 'vue'
export default defineComponent({
// 2)在setup()函数中调用reactive()函数,创建响应式数据对象
setup() {
//创建响应式数据对象
const state = reactive({
name: 'Jack',
})
//setup函数中将响应式数据对象return出去供template使用
return state
},
})
</script>
// 3)在template中访问响应式数据
<template>
<div>
{{ name }}
</div>
</template>
ref 的使用
- 简介
ref()函数用来根据给定的值创建一个响应式的数据对象,ref()函数调用的返回值是一个对象,这个对象上只包含一个 value 属性 - 基本用法
<template>
<div>
<p>{{ count }}</p>
<button @click="change()">click</button>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
var count = ref(10) //初始化值为10
const change = () => {
//方法的定义
count.value += 1 //想改变值或获取值 必须.value
}
return {
count,
change,
}
},
}
</script>
在 reactive()函数中的使用
<template>
<div>
<p>{{ count }}</p>
</div>
</template>
<script>
import { ref, reactive, onMounted } from 'vue'
export default {
setup() {
const state = reactive({
count: ref(99),
})
onMounted(() => {
//生命周期-挂载完成
setInterval(function () {
state.count += 10
}, 1000)
})
return state
},
}
</script>
ref()和 reactive 的不同
reactive 的用法与 ref 的用法相似,也是将数据变成响应式数据,当数据发生变化时 UI 也会自动更新。不同的是 ref 用于基本数据类型,而 reactive 是用于复杂数据类型
<template>
<div>
<p>{{ count }}</p>
<button @click="change()">click</button>
</div>
</template>
<script>
import { reactive } from 'vue'
export default {
setup() {
let count = reactive(10)
// let state = reactive({ count: 10 }); //对象可同步渲染
function change() {
console.log(count)
count += 1 //页面不会同步渲染
// state.count += 1
}
return { count, change }
},
}
</script>
运行发现,基本数据传递给 reactive,reactive 并不会将它包装成 porxy 对象,并且当数据变化时,界面不会同步渲染变化
toRefs
- 简介
toRefs()函数可以将 reactive()创建出来的响应式对象,转换为普通对象,只不过这个对象上的每个属性节点,都是 ref()类型的响应式数据
比如:当想要从一个组合逻辑函数中返回响应式对象时,用 toRefs 是很有效的,该 API 让消费组件可以解构 / 扩展(使用 ... 操作符)返回的对象,并不会丢失响应性 - 使用
<template>
<div>
<p>{{ count }}</p>
</div>
</template>
<script>
import { reactive, toRefs } from 'vue'
export default {
setup() {
let state = reactive({ count: 10 })
return {
...toRefs(state),
}
},
}
</script>
computed
简介
computed()用来创建计算属性,computed()函数的返回值是一个 ref 的实例使用
<template>
<div>
<p>{{ money }}</p>
<button @click="change()">click</button>
</div>
</template>
<script>
import { reactive, toRefs, computed } from 'vue'
export default {
setup() {
let state = reactive({
id: 10,
money: computed(() => state.id + 10), //计算属性的方式
})
function change() {
state.id += 1
console.log(state.id)
}
return {
...toRefs(state),
change,
}
},
}
</script>
watch
- 简介
watch() 函数用来监视某些数据项的变化,从而触发某些特定的操作 - 使用
<template>
<div>
<p>{{ id }}</p>
<p>{{ type }}</p>
<button @click="change()">click</button>
</div>
</template>
<script>
import { reactive, toRefs, watch } from 'vue'
export default {
setup() {
let state = reactive({
id: 10,
type: '偶数',
})
//监听state.id的值的变化
// 创建监听,并得到停止函数
const stope = watch(
() => state.id,
(cur, old) => {
if (cur % 2 == 0) {
state.type = '偶数'
} else {
state.type = '奇数'
}
}
)
function change() {
state.id += 1
if (state.id == 15) {
// 调用停止函数,清除对应的监听
stop()
}
}
return {
...toRefs(state),
change,
}
},
}
</script>
生命周期钩子函数
用法
// 1)新版的生命周期函数,可以按需导入到组件中,且只能在 setup() 函数中使用
import { onMounted, onUpdated, onUnmounted} from "vue";
// 2)在setup()函数中调用computed()函数
setup(){
onMounted(() => {
console.log('mounted!')
})
onUpdated(() => {
console.log('updated!')
})
onUnmounted(() => {
console.log('unmounted!')
})
}
新旧对比
Vue2.x | Vue3 |
---|---|
beforeCreate | Not needed* |
created | Not needed* |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeDestroy | onBeforeUnmount |
destroyed | onUnmounted |
errorCaptured | onErrorCaptured |
- 使用
<template>
<div>
<p>{{ num }}</p>
<p>{{ type }}</p>
</div>
</template>
<script>
import { reactive, toRefs, onMounted, onUpdated, onUnmounted } from 'vue'
export default {
setup() {
var timer = null
let state = reactive({
num: 1,
type: '奇数',
})
const autoPlay = () => {
state.num++
if (state.num == 5) {
state.num = 0
}
}
const play = () => {
timer = setInterval(autoPlay, 1000)
}
onMounted(() => {
//挂载完成
play()
})
onUpdated(() => {
if (state.num % 2 == 0) {
state.type = '偶数'
} else {
state.type = '奇数'
}
})
onUnmounted(() => {
//销毁
clearInterval(timer)
})
return {
...toRefs(state),
}
},
}
</script>
提示
setup 是围绕 beforeCreate 和 created 生命周期钩子运行的,所以不需要显式地去定义。
provide 和 inject
- 简介
provide()和 inject()可以实现嵌套组件之间的数据传递。这两个函数只能在 setup()函数中使用。父级组 件中使用 provide()函数向下传递数据;子级组件中使用 inject()获取上层传递过来的数据 - 使用 父组件
<template>
<div id="app">
<h1>根组件</h1>
<Demo1 />
<Demo2 />
</div>
</template>
<script>
import Demo1 from '@/components/demo1'
import Demo2 from '@/components/demo2'
// 1. 按需导入 provide
import { reactive, toRefs, provide } from 'vue'
export default {
setup() {
// 父级组件通过 provide 函数向子级组件共享数据
//provide('要共享的数据名称', 被共享的数据)
provide('globalColor', 'red')
},
components: {
Demo1,
Demo2,
},
}
</script>
子组件 1
<template>
<div>{{ name }}----{{ color }}</div>
</template>
<script>
import { reactive, computed, provide, inject, toRefs } from 'vue'
export default {
setup(props) {
//创建响应式数据对象
const state = reactive({
name: 'demo1',
//调用 inject 函数时,通过指定的数据名称,获取到父级共享的数据
color: inject('globalColor'),
})
return state
},
}
</script>
子组件 2
<template>
<div>{{ name }}----{{ color }}</div>
</template>
<script>
import { reactive, computed, provide, inject, toRefs } from 'vue'
export default {
setup(props) {
//创建响应式数据对象
const state = reactive({
name: 'demo2',
//调用 inject 函数时,通过指定的数据名称,获取到父级共享的数据
color: inject('globalColor'),
})
return state
},
}
</script>
Teleport
Teleport 组件可将部分 DOM 移动到 Vue app 之外的位置。比如项目中常见的 Dialog 组件
<button @click="dialogVisible = true">点击</button>
<teleport to="body">
<div class="dialog" v-if="dialogVisible">
</div>
</teleport>
Suspense 异步组件
- 简介
Suspense 组件用于在等待某个异步组件解析时显示后备内容。如 loading ,使用户体验更平滑。使用它,需在模板中声明,并包括两个命名插槽:default 和 fallback。Suspense 确保加载完异步内容时显示默认插槽,并将 fallback 插槽用作加载状态。 - 什么时候使用
- 在页面加载之前显示加载动画
- 显示占位符内容
- 处理延迟加载的图像
- 使用
// 插槽包裹异步组件
<Suspense>
<template #default>
<Async/>
</template>
</Suspense>
// 具名插槽的缩写是在vue2.6.0新增,跟 v-on 和 v-bind 一样,v-slot
缩写,替换为字符 #。 // 例如 v-slot:header 可以被重写为 #header
真实的项目中踩过坑,若想在 setup 中调用异步请求,需在 setup 前加 async 关键字。这时,会受到警告 async setup() is used without a suspense boundary。
解决方案:在父页面调用当前组件外包裹一层 Suspense 组件。
// 插槽包裹渲染异步组件之前的内容
<Suspense>
<template #fallback>
<h1>Loading...</h1>
</template>
</Suspense>
- 如何运用 父组件中定义
<Suspense>
<template #default>
<List />
</template>
<template #fallback>
<div>loading......</div>
</template>
</Suspense>
List 子组件中的处理
import { getPage } from '@/api/http'
export default {
async setup() {
const res = await getPage()
const state = reactive({
items: res.data.data,
})
return {
...toRefs(state),
}
},
}