vue3.0_脚手架
官网 提供了两种构建方式,一种是vite,另一种是vue-cli。本文采用的是前者vite,至于为什么选择 vite,主要是因为它的构建速度更快。 我们依次执行命令:
npm init vite vue3-demo
cd vue3-demo
npm install
npm run dev
浏览器端打开localhost:3000可以看到,vite+vue3的项目启动成功了:
vue3.0_新特性
一、组合式API (Composition API)
组合式 API的作用是将同一个逻辑关注点代码收集在一起,这样增强了组件的易读性和可维护性,不必在处理单个逻辑关注点时,不断地跳转相关代码的选项块。
# setup 选项
- vue3中将
setup作为组合式API的入口,在组件创建之前执行 setup选项是一个接收两个参数props和context的函数- 它返回的所有内容都可用于组件的其余部分(生命周期、计算属性、方法等)以及组件的模板 setup的结构大致如下:
export default {
components: { },
props: {
user: {
type: String,
required: true
}
},
setup(props) {
console.log(props) // { user: '' }
// 暴露给 template
return {} // 这里返回的任何内容都可以用于组件的其余部分
}
// 组件的“其余部分”
}
# ref 函数
ref为我们的值创建一个响应式引用,返回一个{value: xxx}格式的对象,可以通过该对象的value属性访问或者更改响应式变量的值- 它和组合式API结合使用
- 从
setup返回的ref在模板中访问时是被自动浅解包,不需要在模板中使用.value
<template>
<div>{{ counter }}</div>
</template>
import { ref } from 'vue'
setup(){
const counter = ref(0)
console.log(counter) // { value: 0 }
console.log(counter.value) // 0
counter.value++
console.log(counter.value) // 1
return {
counter
}
}
# reactive 函数
- 接受一个对象,返回对象的响应式副本,可以将对象做“深层”的响应式转换
- 可以解包所有深层的
refs,同时维持ref的响应性
const count = ref(1)
const obj = reactive({ count })
// ref 会被解包
console.log(obj.count === count.value) // true
// 它会更新 `obj.count`
count.value++
console.log(count.value) // 2
console.log(obj.count) // 2
// 它也会更新 `count` ref
obj.count++
console.log(obj.count) // 3
console.log(count.value) // 3
# toRefs 函数
toRefs可以获取setup选项中参数props中某一个prop的响应式引用
import { watch, toRefs } from 'vue'
setup (props) {
// 使用 `toRefs` 创建对prop的 `user` property 的响应式引用
const { counter } = toRefs(props)
watch(counter, (newValue, oldValue) => {
console.log('The new counter value is: ' + counter.value)
})
...
return {
...
}
}
# 加 on 前缀的生命周期函数
- 生命周期我们并不陌生,然而vue3中,我们可以将生命周期钩子写在setup选项中,只需在钩子前加前缀:on,例如
mounted在setup中写成onMounted - 加
on前缀的生命周期函数都接受一个回调,当钩子被组件调用时,该回调将被执行
import { ref, onMounted } from 'vue'
setup (props) {
const repositories = ref([])
const getUserRepositories = async () => {
repositories.value = await fetchUserRepositories(props.user)
}
onMounted(getUserRepositories) // 在 `mounted` 时调用 `getUserRepositories`
return {
repositories,
getUserRepositories
}
}
# watch 函数的响应式更改
从vue中导出的watch函数可以接受3个参数:
- 一个想要侦听的响应式引用或 getter 函数
- 一个回调
- 可选的配置选项
import { ref, watch } from 'vue'
const counter = ref(0)
// 在 counter prop 的响应式引用上设置一个侦听器
watch(counter, (newValue, oldValue) => {
console.log('The new counter value is: ' + counter.value)
})
当counter被修改时,例如counte.value=5,侦听将触发并执行回调 (第二个参数)。 以上等效于下面代码:
export default {
data() {
return {
counter: 0
}
},
watch: {
counter(newValue, oldValue) {
console.log('The new counter value is: ' + this.counter)
}
}
}
# watchEffect 函数
- 响应式追踪依赖
- 自动应用和重新应用副应用
- 自动应用:立即执行传入的一个函数
- 重新应用副应用:依赖变更时重新运行该函数
- 组件卸载时自动停止侦听
- 将在组件更新前执行副作用
onInvalidate函数用来清除副应用flush选项可以在组件更新后重新执行副应用
# 独立的computed属性
computed输出的是一个响应式引用
- 返回一个不可变的响应式
ref对象
const count = ref(1)
const plusOne = computed(() => count.value + 1)
console.log(plusOne.value) // 2
plusOne.value++ // 错误
- 用来创建可写的
ref对象
const count = ref(1)
const plusOne = computed({
get: () => count.value + 1,
set: val => {
count.value = val - 1
}
})
plusOne.value = 1
console.log(count.value) // 0
# 总结
以上可以将组合式API概述为:将每一个逻辑关注点(即单独的功能模块)中的逻辑被提取到一个独立组合式函数中,每一个组合式函数都是由从vue中导出的函数(ref、toRefs、watch、computed等)组合而成,它们就像一个个积木一样,被引入到组件中,然后我们可以在组件的setup函数中将这些单独的功能模块里的所有内容(属性,方法等)收集起来以类似API的方式暴露给组件的其余部分(生命周期、计算属性、方法等)以及组件的模板。
二、Teleport
Teleport字面意思为心灵运输(物体、人);远距离传送,所以我们不难猜测它的作用效果类似于slot,可以在模板中传输/挂载/注入组件
- 有两个参数
to和disabled - 作用是通过
to属性将A组件挂载到B组件中需要渲染的位置,而A组件的逻辑不依赖于B组件,保持A组件的独立性
<body>
<div style="position: relative;">
<h3>Tooltips with Vue 3 Teleport</h3>
<div>
<modal-button></modal-button>
</div>
</div>
</body>
const app = Vue.createApp({});
app.component('modal-button', {
template: `
<button @click="modalOpen = true">
Open full screen modal!
</button>
<div v-if="modalOpen" class="modal">
<div>
I'm a modal!
<button @click="modalOpen = false">
Close
</button>
</div>
</div>
`,
data() {
return {
modalOpen: false
}
}
})
以上例子中可以 看到,利用Teleport将弹框组件中的按钮与弹框的触发逻辑绑定在一个组件中,只需要通过to指定需要挂载到的地方即目标元素body,目标元素也可以是一个id或者class查询选择器,modal-button组件就可以被正常地渲染在模板中了,这样逻辑上或者说结构上就比较清爽干净了。
三、片段
vue3中支持在组件内包含多个根节点,直白点来讲,就是在组件内template标签下不需要再包裹一个DOM标签
<!-- demo.vue -->
<template>
<header>...</header>
<main v-bind="$attrs">...</main>
<footer>...</footer>
</template
四、emits 选项
组件事件中的 emits
1、 emits 选项用来定义一个组件可以向其父组件触发的事件
<template>
<div>
<p>{{ text }}</p>
<button v-on:click="$emit('accepted')">OK</button>
</div>
</template>
<script>
export default {
props: ['text'],
emits: ['accepted']
}
</script>
2、 任何未在emits中声明的事件监听器都会被算入组件的$attr作为原生事件,被默认绑定到组件的根节点的原生事件监听器上
// 父组件
<my-button v-on:click="handleClick"></my-button>
// 子组件
<template>
<button v-on:click="$emit('click', $event)">OK</button>
</template>
<script>
export default {
emits: [] // 不声明事件
}
</script>
以上如果不在emits选项中声明click事件,则该事件会被触发两次:
- 一次来自子组件的
$emit - 一次来自根元素上的原生事件监听器
3、
emits选项可以接受一个数组或者对象,后者允许配置事件验证(验证函数应返回布尔值)
app.component('custom-form', {
emits: {
// 没有验证
click: null,
// 验证submit 事件
submit: ({ email, password }) => {
if (email && password) {
return true
} else {
console.warn('Invalid submit event payload!')
return false
}
}
},
methods: {
submitForm(email, password) {
this.$emit('submit', { email, password })
}
}
})
五、v-model 的新用法
v-model参数的改变:vue2中:组件上的v-model使用value作为 prop 和input作为事件
<ChildComponent v-model="pageTitle" /> <!-- 是以下的简写: --> <ChildComponent :value="pageTitle" @input="pageTitle = $event" />vue3中:组件上的v-model使用modelValue作为 prop 和update:modelValue作为事件
<ChildComponent v-model="pageTitle" /> <!-- 是以下的简写: --> <ChildComponent :modelValue="pageTitle" @update:modelValue="pageTitle = $event" />- 可以在同一组件上使用多个v-model绑定
<ChildComponent v-model:title="pageTitle" v-model:content="pageContent" />
- 可以自定义
v-model的修饰符- vue3中使用
modelModifiers对象来自定义修饰符,也就是说v-model的修饰符将通过modelModifiersprop 提供给组件 - 当组件的
created生命周期钩子触发时,modelModifiersprop 会包含capitalize,且其值为true
- vue3中使用
下面提供一个实例,实现每当 <input/> 元素触发 input 事件时,我们都将字符串大写:
// html
<div id="app">
<my-component v-model.capitalize="myText"></my-component>
{{ myText }}
</div>
// vue
const app = Vue.createApp({
data() {
return {
myText: ''
}
}
})
app.component('my-component', {
props: {
modelValue: String,
modelModifiers: {
default: () => ({})
}
},
emits: ['update:modelValue'],
created() {
console.log(this.modelModifiers) // { capitalize: true }
},
methods: {
emitValue(e) {
let value = e.target.value
if (this.modelModifiers.capitalize) { // true
value = value.charAt(0).toUpperCase() + value.slice(1)
}
this.$emit('update:modelValue', value)
}
},
template: `<input
type="text"
:value="modelValue"
@input="emitValue">`
})
app.mount('#app')