# Vue3 简述
# Vue3 的一些新特性
必须要夸一夸 Vue3,它真实可谓“千呼万唤使出来”,听说是尤大神每天抱着孩子完成的。故事已经不能说明 Vue3 的伟大了,我们用下面一组数字来了解一下 Vue3。
Vue3:两年开发,99 位程序员成为贡献者,2600 次提交,628 次 PR
这些数字可以看出 Vue3 的工作量之大,我在一年前就开始关注 Vue3 的开发进展,虽然没成为 Vue3 的贡献者之一,但是我热爱的心不可否认。
官方网站地址: v3.vuejs.org
- 首先是向下兼容,Vue3 支持大多数 Vue2 的特性。我们同事甚至开玩笑说,我就拿 Vue2 的语法开发 Vue3,也是没有任何问题的。
- 性能的提升,每个人都希望使用的框架更快,更轻。Vue3 做到了,给开发者一个极致的体验。官方网站给出的数据是:打包大小减少 41%,初次渲染快 55%,更新快 133%,内存使用减少 54%。
- 新推出的
Composition API
,在 Vue2 中遇到的问题就是复杂组件的代码变的非常麻烦,甚至不可维护。说白了就是封装不好,重用不畅。这个Composition API
一推出,立马解决了这个问题,本套课程中也会重点介绍这部分内容。它是一系列 API 的合集。 - 其他新特性:Teleport(瞬移组件)、Suspense(解决异步加载组件问题)和全局 API 的修改和优化。
- 更好
TypeScript
支持,我以前在开发 Vue2 的时候,是不适用TypeScript
的,因为集成时很困难,疼点太多。但 Vue3 解决了这个问题,Vue3 的源代码就是使用TypeScript
进行开发的。所以在新的版本上使用TS
也更加顺畅无阻。
B 站 Vue3 发布会视频:https://www.bilibili.com/video/BV1iA411J7cA?from=search&seid=2979047174353974296
####### ###
# 全局 API 的重大更改
# 全局声明应用更换
Vue 2.x 有许多全局 API 和配置,这些 API 和配置可以全局改变 Vue 的行为。例如,Vue.use,Vue.component,Vue.directive等等。虽然这种声明方式很方便,但它也会导致一些问题。从技术上讲,Vue 2 没有“app”的概念,我们定义的应用只是通过 new Vue()
创建的根 Vue 实例。测试期间,全局配置很容易意外地污染其他测试用例。有些 API 像 Vue.use
以及 Vue.mixin
甚至连恢复效果的方法都没有,这使得涉及插件的测试特别棘手。实际上,vue-test-utils 必须实现一个特殊的 API createLocalVue
来处理此问题,了避免这些问题,在 Vue 3 中我们引入createApp
的一个全新API
import { createApp } from 'vue'
const app = createApp({})
应用实例暴露当前全局 API 的子集,经验法则是,任何全局改变 Vue 行为的 API 现在都会移动到应用实例上,以下是当前全局 API 及其相应实例 API 的表:
2.x 全局 API | 3.x 实例 API (app ) |
---|---|
Vue.config | app.config |
Vue.config.productionTip | removed (见下方 (opens new window)) |
Vue.config.ignoredElements | app.config.isCustomElement (见下方 (opens new window)) |
Vue.component | app.component |
Vue.directive | app.directive |
Vue.mixin | app.mixin |
Vue.use | app.use (见下方 (opens new window)) |
由于 use
全局 API 在 Vue 3 中不再使用,此方法将停止工作并停止调用 Vue.use()
现在将触发警告,于是,开发者必须在应用程序实例上显式指定使用此插件:
const app = createApp(MyApp)
app.use(VueRouter)
# next 函数的语法改变
在 Vue 3 中,全局和内部 API 都经过了重构,并考虑到了 tree-shaking 的支持。因此,全局 API 现在只能作为 ES 模块构建的命名导出进行访问。例如,我们之前的片段现在应该如下所示:
import { nextTick } from 'vue'
nextTick(() => {
// 一些和DOM有关的东西
})
# 模板指令的重大更改
# 组件 v-model
的用法更改
在某些情况下,我们可能需要对某一个 prop 进行“双向绑定”(除了前面用 v-model
绑定 prop 的情况)。为此,我们建议使用 update:myPropName
抛出事件。例如,对于在上一个示例中带有 title
prop 的 ChildComponent
,我们可以通过下面的方式将分配新 value 的意图传达给父级:
this.$emit('update:title', newValue)
<!-- 如果需要的话,父级可以监听该事件并更新本地 data property。例如: -->
<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />
<!-- 为了方便起见,我们可以使用 .sync 修饰符来缩写,如下所示: -->
<ChildComponent :title.sync="pageTitle" />
在 3.x 中,自定义组件上的 v-model
相当于传递了 modelValue
prop 并接收抛出的 update:modelValue
事件:
<ChildComponent v-model="pageTitle" />
<!-- 简写: -->
<ChildComponent
:modelValue="pageTitle"
@update:modelValue="pageTitle = $event"
/>
若需要更改 model
名称,而不是更改组件内的 model
选项,那么现在我们可以将一个 argument 传递给 model
:
<ChildComponent v-model:title="pageTitle" />
<!-- 简写: -->
<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />
这也可以作为 .sync
修饰符的替代,而且允许我们在自定义组件上使用多个 v-model
。
<ChildComponent v-model:title="pageTitle" v-model:content="pageContent" />
<!-- 简写: -->
<ChildComponent
:title="pageTitle"
@update:title="pageTitle = $event"
:content="pageContent"
@update:content="pageContent = $event"
/>
# 模板与其他指令 key 值用法更改
在 Vue 2.x 中 <template>
标签不能拥有 key
。不过你可以为其每个子节点分别设置 key
。
<!-- Vue 2.x -->
<template v-for="item in list">
<div :key="item.id">...</div>
<span :key="item.id">...</span>
</template>
在 Vue 3.x 中 key
则应该被设置在 <template>
标签上。
<!-- Vue 3.x -->
<template v-for="item in list" :key="item.id">
<div>...</div>
<span>...</span>
</template>
类似地,当使用 <template v-for>
时存在使用 v-if
的子节点,key
应改为设置在 <template>
标签上。
<!-- Vue 2.x -->
<template v-for="item in list">
<div v-if="item.isVisible" :key="item.id">...</div>
<span v-else :key="item.id">...</span>
</template>
<!-- Vue 3.x -->
<template v-for="item in list" :key="item.id">
<div v-if="item.isVisible">...</div>
<span v-else>...</span>
</template>
# v-for 中的 Ref 不在返回数组
在 Vue 2 中,在 v-for
里使用的 ref
attribute 会用 ref 数组填充相应的 $refs
property。当存在嵌套的 v-for
时,这种行为会变得不明确且效率低下。
在 Vue 3 中,这样的用法将不再在 $ref
中自动创建数组。要从单个绑定获取多个 ref,请将 ref
绑定到一个更灵活的函数上 (这是一个新特性):
<template>
<div v-for="item in list" :key="item" :ref="setItemRef"></div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
setup() {
const itemRefs: Element[] = [];
const setItemRef = (el: Element) => itemRefs.push(el);
return {
setItemRef
};
}
});
</script>
<style lang="scss"></style>
# 异步组件的重大更改
以前,异步组件是通过将组件定义为返回 Promise 的函数来创建的,例如:
const asyncPage = () => import('./NextPage.vue')
或者,对于带有选项的更高阶的组件语法:
const asyncPage = {
component: () => import('./NextPage.vue'),
delay: 200,
timeout: 3000,
error: ErrorComponent,
loading: LoadingComponent
}
现在,在 Vue 3 中,由于函数式组件被定义为纯函数,因此异步组件的定义需要通过将其包装在新的 defineAsyncComponent
助手方法中来显式地定义:
import { defineAsyncComponent } from 'vue'
import ErrorComponent from './components/ErrorComponent.vue'
import LoadingComponent from './components/LoadingComponent.vue'
// 不带选项的异步组件
const asyncPage = defineAsyncComponent(() => import('./NextPage.vue'))
// 带选项的异步组件
const asyncPageWithOptions = defineAsyncComponent({
loader: () => import('./NextPage.vue'),
delay: 200,
timeout: 3000,
errorComponent: ErrorComponent,
loadingComponent: LoadingComponent
})
# 移除 API
keyCode
支持作为v-on
的修饰符 (opens new window)- $on,$off 和 $once 实例方法 (opens new window)
- 过滤 (opens new window)
- 内联模板 attribute (opens new window)
$destroy
实例方法。用户不应再手动管理单个 Vue 组件的生命周期。
# 支持的库
所有的官方库和工具现在都支持 Vue 3,但大多数仍然处于 beta 状态,并在 NPM 的 next
dist 标签下发布。我们正计划在 2020 年底前稳定所有项目,并将其转换为使用 latest
的 dist 标签。
# Vue CLI
从 v4.5.0 开始,vue-cli
现在提供了内置选项,可在创建新项目时选择 Vue 3 预设。现在可以升级 vue-cli
并运行 vue create
来创建 Vue 3 项目。
# Vue Router
Vue Router 4.0 提供了 Vue 3 支持,并有许多突破性的变化,查看 README (opens new window) 中完整的细节,
# Vuex
Vuex 4.0 提供了 Vue 3 支持,其 API 与 3.x 基本相同。唯一的突破性变化是插件的安装方式 (opens new window)。
# Devtools Extension
我们正在开发一个新版本的 Devtools,它有一个新的 UI 和经过重构的内部结构,以支持多个 Vue 版本。新版本目前处于测试阶段,目前只支持 Vue 3。Vuex 和路由器的集成也在进行中。
- Chrome:从 Chrome web 商店中安装 (opens new window)
- 提示:beta 版本可能与 devtools 的稳定版本冲突,因此你可能需要暂时禁用稳定版本,以便 beta 版本正常工作。
- Firefox:下载签名扩展 (opens new window) (assets 下的
.xpi
文件)
# IDE 支持
推荐使用 VSCode (opens new window) 和我们官方拓展 Vetur (opens new window),它为 Vue 3 提供了全面的 IDE 支持
# Vue 基础命令&对象
# Vue-html指令
v-text
:更新元素的textContentv-html
:更新元素的innerHTMLv-if
:如果为true, 当前标签才会输出到页面v-else
:如果为false, 当前标签才会输出到页面v-show
:通过控制display 样式来控制显示/隐藏v-for
:遍历数组/对象v-on
:绑定事件监听, 一般简写为@v-bind
:标签属性绑定解析表达式, 可以省略v-bindv-model
:双向数据绑定ref
:指定唯一标识, vue 对象通过$els 属性访问这个元素对象v-cloak
:防止闪现, 与css 配合: [v-cloak] { display: none }setup
: 发送ajax请求, 启动定时器等异步任务onBeforeUnmount
: 做收尾工作, 如: 清除定时器
# Vue 切换显示动画
Vue transition 允许我们只设置隐藏或者显示的样式,这样切换的时候,也会有过渡的效果。
<transition name="name"><div v-show="fool">666</div></transition>
.[name]-enter-active, .[name]-leave-active{/*显示/隐藏的过渡样式*/}
.[name]-enter-from, .[name]-leave-to {/*隐藏的样式*/}
.[name]-enter-top, .[name]-leave-from {/*显示的样式*/}
# Vue 括号表达式
<view>
<text class="title">{{title}}</text>
<template v-for="(item, index) in 6">
<div :key="index">{{item}}</div>
</template>
</view>
# Vue 事件修饰符
<!-- 阻止单击事件继续传播 -->
<a @click.stop="doThis"></a>
<!-- 提交事件不再重载页面 -->
<form @submit.prevent="onSubmit"></form>
<!-- 修饰符可以串联 -->
<a @click.stop.prevent="doThat"></a>
<!-- 只有修饰符 -->
<form @submit.prevent></form>
<!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即内部元素触发的事件先在此处理,然后才交由内部元素进行处理 -->
<div @click.capture="doThis">...</div>
<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div @click.self="doThat">...</div>
<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
<!-- 而不会等待 `onScroll` 完成 -->
<!-- 这其中包含 `event.preventDefault()` 的情况 -->
<div @scroll.passive="onScroll">...</div>
# Vue 按键修饰符
<!-- 只有在 `key` 是 `Enter` 时调用 `vm.submit()` -->
<input v-on:keyup.enter="submit">
.enter .tab .delete (捕获“删除”和“退格”键) .esc .space .up .down .left .right
# Vue 定义响应式数据
# 定义数据(define)
import { defineComponent } from "vue";
import { reactive, ref } from "vue";
export default defineComponent({
setup() {
// 定义单个响应式数据
const count = ref(0);
// 通过改变count.value发生响应式变化
count.value++;
// 以对象形式定义响应式数据
const state = reactive({ count: 0 });
// 通过改变对象发生响应式变化
state.count++;
return {
count,
state
}
}
});
# 定义数据(setup)
import { reactive, ref } from "vue";
// 定义单个响应式数据
const count = ref(0);
// 通过改变count.value发生响应式变化
count.value++;
// 以对象形式定义响应式数据
const state = reactive({ count: 0 });
// 通过改变对象发生响应式变化
state.count++;
# 模板中使用
<template>
<span>{{ count }}</span>
<span> {{ state.count }}</span>
</template>
^注意: toRef 可以用来为一个 reactive 对象的属性创建一个 ref。这个 ref 可以被传递并且能够保持响应性。
# Vue 外置组合函数
# 计算属性值 (computed)
# computed 函数 计算属性
<template>
姓:<input type="text" v-model="firstName" /><br />
名:<input type="text" v-model="lastName" /><br />
姓名(单向):<input type="text" v-model="Name" />
</template>
<script setup lang="ts">
import { computed, ref } from "vue";
const firstName = ref("Aaa");
const lastName = ref("bbbB");
const name = computed(() => {
return firstName.value + " " + lastName.value;
});
</script>
# computed 对象 监视读写
<template>
姓:<input type="text" v-model="firstName" /><br />
名:<input type="text" v-model="lastName" /><br />
姓名(单向):<input type="text" v-model="name" />
</template>
<script setup lang="ts">
import { computed, ref } from "vue";
const firstName = ref("Aaa");
const lastName = ref("bbbB");
const name = computed({
// 回调函数,当需要读取当前属性值时回调,根据相关的数据计算并返回当前属性的值
get() {
return firstName.value + "|" + lastName.value;
},
// 回调函数,当属性值发生改变时回调,更新相关的属性数据
// value就是name的最新属性值
set(value: string) {
const names = value.split("|");
firstName.value = names[0] || "";
lastName.value = names[1] || "";
}
});
</script>
# computed 替换过滤器
Filters 已从 Vue 3.0 中删除,不再受支持。在 3.x 中,filters 已删除,不再受支持。相反,官方建议用方法调用或计算属性替换它们。
// filters.ts
import { computed } from "vue";
export function (value: Ref) {
return computed(() => "...")
}
# 收集依赖并监视(effect)
watchEffect会自动收集使用依赖并立即执行传入的一个函数,并响应式追踪其依赖,并在其依赖变更时重新运行该函数。
<script setup lang="ts">
import { ref, watchEffect } from "vue";
const firstName = ref("Aaa");
const lastName = ref("bbbB");
const name = ref("");
// watchEffect会自动收集使用依赖
// 当firstName与lastName改变时执行监视函数
watchEffect(() => {
name.value = firstName.value + "|" + lastName.value;
});
</script>
# 停止监视
watchEffect 返回停止函数,可以调用该函数以显式停止观察程序:
import { ref, watchEffect } from "vue";
const count = ref(0);
// 接收停止函数
const stop = watchEffect(() => {
console.log("count:", count);
});
count.value++;
// 停止监视
stop();
# 废弃钩子
watchEffect 第一个参数接收值为函数钩子,该钩子会在该 watchEffect 废弃时调用。
watchEffect(onInvalidate => {
onInvalidate(() => {})
})
# 更新模式
在组件更新后需要重新运行观察者效果的情况下,我们可以通过选项传递一个附加options
对象flush
(默认为'pre'
):
watchEffect(() => {
/* ... */
}, { flush: 'post'})
# 看守调试
onTrack
和onTrigger
选项可以用来调试观察者的行为。
onTrack
当将响应式属性或ref跟踪为依赖项时将被调用onTrigger
当监视者回调由依赖项的变化触发时将被调用
这两个回调都将接收到一个调试器事件,该事件包含有关所依赖项的信息。
watchEffect(() => {
/* ... */
}, {
onTrack(e) { },
onTrigger(e) { },
})
# 指定数据源监视(watch)
watch 函数用来侦听特定的数据源,并在回调函数中执行副作用。默认情况是懒执行的,也就是说仅在侦听的源变更时才执行回调。
import { ref, watch } from "vue";
const count = ref(0);
// watch(ref, fn, ?options) 对count进行监视, 值改变时触发
watch(count, (value, oldValue) => {
console.log("count值发生改变");
console.log("新值为: ", value);
console.log("旧值为: ", oldValue);
});
count.value++
watch 函数第三个参数为配置参数,是可选项,可选项中带有以下参数
deep: boolean; // 是否深度监视, 及对象或数组内部(false)
immediate: boolean; // 是否初始化执行, 及以当前值执行
# Composition 组合式 API
# reactive 声明响应式对象
要为 JavaScript 对象创建响应式状态,可以使用 reactive
方法:
import { reactive } from 'vue'
// 响应式状态
const state = reactive({
count: 0
})
# ref 声明响应式值
想象一下,我们有一个独立的原始值 (例如,一个字符串),我们想让它变成响应式的。当然,我们可以创建一个拥有相同字符串 property 的对象,并将其传递给 reactive
。Vue 为我们提供了一个可以做相同事情的方法 ——ref
:
import { ref } from 'vue'
const count = ref(0)
ref
会返回一个可变的响应式对象,该对象作为它的内部值——一个响应式的引用,这就是名称的来源。此对象只包含一个名为 value
的 property :
import { ref } from 'vue'
const count = ref(0)
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
# 响应式状态解构
当我们想使用大型响应式对象的一些 property 时,可能很想使用 ES6 解构 (opens new window)来获取我们想要的 property:
import { reactive } from 'vue'
const book = reactive({
author: 'Vue Team',
year: '2020',
title: 'Vue 3 Guide',
description: 'You are reading this book right now ;)',
price: 'free'
})
let { author, title } = book
遗憾的是,使用解构的两个 property 的响应性都会丢失。对于这种情况,我们需要将我们的响应式对象转换为一组 ref。这些 ref 将保留与源对象的响应式关联:
import { reactive, toRefs } from 'vue'
const book = reactive({
author: 'Vue Team',
year: '2020',
title: 'Vue 3 Guide',
description: 'You are reading this book right now ;)',
price: 'free'
})
let { author, title } = toRefs(book)
title.value = 'Vue 3 Detailed Guide' // 我们需要使用 .value 作为标题,现在是 ref
console.log(book.title) // 'Vue 3 Detailed Guide'
# ref | reactive 数据解包
传入响应式数据假如存在 ref | reactive | computed 会自动解包,并具有响应式
const name = ref('Mr.Mao')
const book = ref({
name
})
/* Ref<{
name: string;
}> */
const composition = reactive({
name
})
/* {
name: string;
} */
# Provide / Inject 声明注入
# Provide 提供源数据
为了增加 provide 值和 inject 值之间的响应性,我们可以在 provide 值时使用 ref (opens new window) 或 reactive (opens new window)。
使用 MyMap
组件,我们的代码可以更新如下:
<!-- src/components/MyMap.vue -->
<template>
<MyMarker />
</template>
<script>
import { provide, reactive, ref, readonly } from 'vue'
import MyMarker from './MyMarker.vue
export default {
components: {
MyMarker
},
setup() {
const location = ref('North Pole')
const geolocation = reactive({
longitude: 90,
latitude: 135
})
// 确保通过 provide 传递的数据不会被 inject 的组件更改, 使用 readonly
provide('location', readonly(location))
provide('geolocation', readonly(geolocation))
// 有时我们需要在注入数据的组件内部更新 inject 的数据。在这种情况下,建议 provide 一个方法来负责改变响应式 property。
const updateLocation = () => {
location.value = 'South Pole'
}
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>
# Mixin 混合合并
Mixin 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个 mixin 对象可以包含任意组件选项。当组件使用 mixin 对象时,所有 mixin 对象的选项将被“混合”进入该组件本身的选项。
例子:
// 定义 mixin 对象
const myMixin = {
created() { this.hello() },
methods: {
hello() { console.log('hello from mixin!') }
}
}
// 定义使用改 mixin 的应用
const app = Vue.createApp({ mixins: [myMixin] })
app.mount('#mixins-basic') // => "hello from mixin!"
# 选项合并
当组件和 mixin 对象含有同名选项时,这些选项将以恰当的方式进行“合并”。
比如,数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先。
const myMixin = {
data: ()=> ({
message: 'hello',
bar: 'abc'
}),
}
const app = Vue.createApp({
mixins: [myMixin],
data: ()=> ({
message: 'goodbye',
bar: 'def'
}),
created() { console.log(this.$data) } // => { message: "goodbye", foo: "abc", bar: "def" }
})
同名钩子函数将合并为一个数组,因此都将被调用。另外,mixin 对象的钩子将在组件自身钩子之前调用。
const myMixin = {
created() { console.log('mixin 对象的钩子被调用') }
}
const app = Vue.createApp({
mixins: [myMixin],
created() { console.log('组件钩子被调用') }
})
// => "mixin 对象的钩子被调用"
// => "组件钩子被调用"
# Vue 实例对象生命周期
# 生命周期流程
# 对比 Vue2.x 的声明周期
beforeCreate
-> 使用setup()
created
-> 使用setup()
beforeMount
->onBeforeMount
mounted
->onMounted
beforeUpdate
->onBeforeUpdate
updated
->onUpdated
beforeDestroy
->onBeforeUnmount
destroyed
->onUnmounted
errorCaptured
->onErrorCaptured
# Vue生命周期函数
一、初始化显示可调用函数
setup()
onBeforeMount()
onMounted()
二、更新状态可调用函数
onBeforeUpdate()
onUpdated()
三、销毁Vue实例可调用函数
onBeforeUnmount()
onUnmounted()
一般比较常用的生命周期方法
setup()/onMounted()
: 发送ajax请求, 启动定时器等异步任务onBeforeUnmount()
: 做收尾工作, 如: 清除定时器
# Vue 自定义命令
# Vue所有实例注册
import { createApp } from "vue";
import App from "./App.vue";
const app = createApp(App)
// 元素内所有字符串改变为大写
app.directive("upper-text", (el, binding) => {
const value: string = binding.value;
el.innerText = value;
});
# Vue单个实例注册
export default defineComponent({
directives: {
"upper-text"(el, binding) {
const value: string = binding.value;
el.innerText = value;
}
}
});
# 使用自定义标签属性
<div id="test">
<p v-upper-text='msg'>adsaSAsSasAS</p>
</div>
# Vue命令生命周期执行
const app = Vue.createApp({})
app.directive('highlight', {
beforeMount(el, binding, vnode, prevVnode) {},
mounted() {},
beforeUpdate() {},
updated() {},
beforeUnmount() {}, // 新
unmounted() {}
})
# Vue 源码分析
TODO
# Vue 面向组件编程
# 脚手架搭建环境
cnpm i @vue/cli -g
vue create '项目名' / vue ui
#↓↓↓↓↓↓↓#
? Please pick a preset: (Use arrow keys) ## 选择你的配置, 如果之前保存有配置, 会在此显示
Default ([Vue 2] babel, eslint)
Default (Vue 3 Preview) ([Vue 3] babel, eslint)
> Manually select features
#↓↓↓↓↓↓↓#
? Check the features needed for your project: ## 选择配置项, <空格>表示选择, <a>表示全选, <i>表示反转
>(*) Choose Vue version
(*) Babel ## ES6转为ES5的解析器
(*) TypeScript ## .ts的解析器
( ) Progressive Web App (PWA) Support ## 渐进式Web应用程序
(*) Router ## vue路由
(*) Vuex ## vue状态数据管理
(*) CSS Pre-processors ## css预编译器
(*) Linter / Formatter ## 代码风格检查和格式化
( ) Unit Testing ## 单元测试(unit tests)
( ) E2E Testing ## e2e测试(end to end)
#↓↓↓↓↓↓↓##
? Choose a version of Vue.js that you want to start the project with ## 选择您要用来启动项目的Vue.js版本
2.x
> 3.x (Preview)
#↓↓↓↓↓↓↓#
Use class-style component syntax? (y/N) ## 是否选用class类模式
? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? (Y/n) ## 将Babel与TypeScript一起使用(现代模式,自动检测的polyfill,转化JSX所需)? (是/否)
#↓↓↓↓↓↓↓#
? Check the features needed for your project: Babel, Router, Vuex, CSS Pre-processors, Linter, Unit
? Use history mode for router? (Y/n) ## router是否使用history模式, 否则使用hash默认(建议n)
#↓↓↓↓↓↓↓#
? Pick a CSS pre-processor: ## 选择css 预处理器
> Sass/SCSS (with dart-sass)
Sass/SCSS (with node-sass)
Less
Stylus
#↓↓↓↓↓↓↓#
? Pick a linter / formatter config: ## 选择Eslint 代码验证规则 (通常Prettier用的比较多)
ESLint with error prevention only
ESLint + Airbnb config
ESLint + Standard config
> ESLint + Prettier
#↓↓↓↓↓↓↓#
? Pick additional lint features: ## 选择什么时候检测
>(*) Lint on save ## 保存就检测
( ) Lint and fix on commit ## fix或commit的时候检测
#↓↓↓↓↓↓↓#
? Pick a unit testing solution: ## 选择单元测试方案
> Mocha + Chai ## Mocha测试库+Chai断言库
Jest ## Jest测试库
#↓↓↓↓↓↓↓#
? Where do you prefer placing config for Babel, ESLint, etc.? ## 项目配置文件存放处
In dedicated config files ## 独立文件存放
> In package.json ## 统统放在package.json中
#↓↓↓↓↓↓↓#
? Save this as a preset for future projects? (y/N) ## 是否保存该配置到本地文件, 如果选择Y, 选择需要输入名称
#↓↓↓↓↓↓↓#
Vue CLI v4.3.1 ## 安装相应包, 等待创建项目中
✨ Creating project in D:/......
⚙️ Installing CLI plugins. This might take a while...
#↓↓↓↓↓↓↓#
npm run serve --open ## 内存中打包并开启服务
## 配置run serve自动打开浏览器
package.json --> scripts:{serve: "vue-cli-service serve --open"}
# 项目目录文件解析
## 项目总文件
- node_modules 用node安装的依赖包
- src 资源文件夹以后我们就在这个目录写代码
- public 静态资源html(图片之类)json数据之类
- tests 单元测试,代码测试
- .gitignore 上传需要忽略的文件格式
- babel.config.js babel相关log信息
- package.json 项目基本信息(项目开发所需模块,项目名称,版本, es配置)
- README.md 项目说明(如何使用,有哪些方法等等)
## src目录文件 > 项目资源
- assets 静态资源(js,css之类可以放在这下面)
- components 公用组件编写的地方
- router/index.js 路由配置文件
- store/index.js vuex状态数据管理配置文件
- views 路由组件存放地(视图组件)
- App.vue 项目的主组件,所有页面都是在app.vue下切换的.一个标准的vue文件,分为三部分。
- main.js 页面程序入口文件,加载各种公共组件
# 生产环境的操作
## 编译打包
npm run build
## 模拟后台 (静态服务器工具包)
安装:npm install serve -g
运行dist包文件夹:serve dist
访问: http://localhost:5000
## 修改打包项目名称
webpack.prod.conf.js --> output:{publicPath: '打包名称'}
## 动态web服务器如何开启打包项目
将打包文件拷贝到运行的tomcat(后端服务) 的运行目录下
访问: http://localost:8080/xxx
# 组件基本架构(define)
新版不使用new Vue进行创建应用,而使用createApp进行创建应用,且组件使用defineComponent函数进行创建。Vue在推送新语法的同时,向后兼容对象写法。
# 入口函数 src / main.ts
/* src/main.js */
import { createApp } from "vue"; // 引入创建应用方法
import App from "./App.vue"; // 引入入口Vue文件
createApp(App).mount("#app"); // 创建Vue且挂载至#app元素中
# 主组件格式 src / app.vue
<template>
<!-- 模板页面(支持多跟节点) -->
<span>count: </span>
<span>{{ count }}</span>
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
export default defineComponent({
setup() {
const count = ref(0);
return {
count
};
}
});
</script>
<style lang="scss"></style>
# 引入组件格式
<template>
<!-- 使用组件标签(大小写区分) -->
<HelloWorld></HelloWorld>
<!-- 使用组件标签(中划线区分) -->
<hello-world />
</template>
<script lang="ts">
import { defineComponent } from "vue";
import HelloWorld from "./components/HelloWorld.vue";
export default defineComponent({
components: {
HelloWorld
}
});
</script>
<style lang="scss"></style>
# 组件基本架构(setup)
单文件组件 Composition API 语法糖,用于改善在单个文件组件中使用Composition API时的创作体验。
# 主组件格式 src / app.vue
<template>
<span>count: </span>
<span @click="inc">{{ count }}</span>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const count = ref(0)
const inc = () => count.value++
</script>
<style lang="scss"></style>
# 引入组件格式
<template>
<!-- 使用组件标签(大小写区分) -->
<HelloWorld></HelloWorld>
<!-- 使用组件标签(中划线区分) -->
<hello-world />
</template>
<script lang="ts">
import HelloWorld from "./components/HelloWorld.vue";
</script>
<style lang="scss"></style>
# 父子组件间通信
# 子接收父数据(define)
利用标签名从父组件传输数据到子组件
<!-- 父组件标签(App.vue)传输数据(任意JS属性或方法) -->
<TdoHead :addTask='addTask' />
<!-- 子组件(TdoHead.vue)props接收数据 -->
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
name: "HelloWorld",
props: {
addTask: Array as () => Array<any>
},
setup(props) {
console.log(props.addTask)
}
});
</script>
利用自定义事件传输父组件方法到子组件(暂未找到方法)
# 子接收父数据(setup)
利用标签名从父组件传输数据到子组件
<!-- 父组件标签(App.vue)传输数据(任意JS属性或方法) -->
<TdoHead :addTask='addTask' />
<script setup lang="ts">
<!-- 子组件(TdoHead.vue)props接收数据 -->
import { defineComponent } from "vue";
const props = defineProps({
addTask: Array
})
console.log(props.addTask)
</script>
利用自定义事件传输父组件方法到子组件(暂未找到方法)
# 子接受父标签
一、父组件利用子组件标签,传入实体标签
<!-- 传入子组件需要的标签 -->
<template>
<div slot="xxx">xxx 对应的标签结构</div>
<div slot="yyy">yyy 对应的标签结构</div>
</template>
二、子组件使用父组件传入的标签输出标签
<!-- 输出父组件标签 -->
<template>
<slot name="xxx">父组件对应xxx的标签结构</slot>
<div>组件确定的标签结构</div>
<slot name="yyy">父组件对应yyy的标签结构</slot>
</template>
# 父接受子参数
一、子组件利用 slot 插槽传递任意属性 / 事件
<!-- 输出父组件标签, 传入参数 -->
<template>
<slot name="xxx" :toggle="addCount" :count="count"></slot>
</template>
<script setup lang="ts">
import { ref } from "vue"
const count = ref(0)
const toggle = () => {
count.value++
}
</script>
二、父组件从插槽中获取属性 / 事件
<!-- 传入子组件需要的标签, 接收参数 -->
<template>
<!-- slot-scope接收参数(废弃) -->
<div slot="xxx" slot-scope="{ count, toggle }" @click="toggle"> {{ count }} </div>
<!-- v-slot接收参数 -->
<div v-slot:xxx="{ count, toggle }" @click="toggle"> {{ count }} </div>
</template>
# 子组件间通信
# props通信
- 父组件定义状态数据 父组件定义改变状态数据方法
- 父组件传递状态数据给子组件B 父组件传递改变状态数据方法给子组件A
- 子组件A调用方法改变父组件状态数据
子组件B自动调用
componentWillReceiveProps()
方法并接收状态数据
# 消息订阅系统
- 引入消息订阅系统
import PubSub from 'pubsub-js'
- 发布消息
PubSub.publish('消息名',data)
- 订阅消息(当消息发送改变时执行,并接收数据)
PubSub.subscribe('消息名',(msg, data){...})
# 瞬移与异步组件
# Teleport 瞬移组件
Vue 鼓励我们通过将 UI 和相关行为封装到组件中来构建 UI。我们可以将它们嵌套在另一个内部,以构建一个组成应用程序 UI 的树。
然而,有时组件模板的一部分逻辑上属于该组件,而从技术角度来看,最好将模板的这一部分移动到 DOM 中 Vue app 之外的其他位置。
一个常见的场景是创建一个包含全屏模式的组件。在大多数情况下,你希望模态框的逻辑存在于组件中,但是模态框的快速定位就很难通过 CSS 来解决,或者需要更改组件组合。
考虑下面的 HTML 结构。
<body>
<div style="position: relative;">
<h3>Tooltips with Vue 3 Teleport</h3>
<modal-button></modal-button>
</div>
</body>
让我们来看看 modal-button
组件:
该组件将有一个 button
元素来触发模态框的打开,以及一个带有 class .modal
的 div
元素,它将包含模态框的内容和一个用于自关闭的按钮。
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
}
}
})
当在初始的 HTML 结构中使用这个组件时,我们可以看到一个问题——模态框是在深度嵌套的 div
中渲染的,而模态框的 position:absolute
以父级相对定位的 div
作为引用。
Teleport 提供了一种干净的方法,允许我们控制在 DOM 中哪个父节点下渲染了 HTML,而不必求助于全局状态或将其拆分为两个组件。
让我们修改 modal-button
以使用 <teleport>
,并告诉 Vue “Teleport 这个 HTML 到该‘body’标签”。
app.component('modal-button', {
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>
`,
data() {
return {
modalOpen: false
}
}
})
因此,一旦我们单击按钮打开模态框,Vue 将正确地将模态框内容渲染为 body
标签的子级。
# Suspense 异步组件
Suspense 组件用于在等待某个异步组件解析时显示后备内容。
以下是异步组件有用的一些实例:
- 在页面加载之前显示加载动画
- 显示占位符内容
- 处理延迟加载的图像
以前,在Vue2中,我们必须使用条件(例如 v-if 或 v-else)来检查我们的数据是否已加载并显示后备内容。
但是现在,Suspense随Vue3内置了,因此我们不必担心跟踪何时加载数据并呈现相应的内容。
# 实现异步组件处理
在这个例子中,我们有一个异步的 ArticleInfo.vue 组件。
setup 方法可以像其他方法一样被设置为异步,对于我们的示例,组件将具有异步 setup 方法,该方法将在返回之前加载用户数据。
async function getArticleInfo() {
// 一些异步API调用
return { article }
}
export default {
async setup () {
var { article } = await getArticleInfo()
return {
article
}
}}
然后,假设我们有一个 ArticlePost.vue 组件,其中包含我们的ArticleInfo组件。
如果我们要在等待组件获取数据并解析时显示“正在拼了命的加载…”之类的内容,则只需三个步骤即可实现Suspense。
- 将异步组件包装在
<template #default>
标记中 - 在我们的Async组件的旁边添加一个兄弟姐妹,标签为
<template #fallback>
。 - 将两个组件都包装在
<suspense>
组件中
使用插槽,Suspense将渲染后备内容,直到默认内容准备就绪。然后,它将自动切换以显示我们的异步组件。
所以,看起来会像这样。
<Suspense>
<template #default>
<article-info/>
</template>
<template #fallback>
<div>正在拼了命的加载…</div>
</template>
</Suspense>
# 捕获异步组件错误
当我们开始使用异步组件时,可以捕获错误并向用户显示一些错误消息。
即使在Vue2中,也可以使用 errorCaptured 钩子函数实现,但是在Vue3中,它已重命名为 onErrorCaptured。
无论调用什么,此钩子函数都会在捕获到任何后代组件的错误时运行。如果出现问题,我们可以将其与Suspense一起使用以渲染错误。
如果我们处理了一个错误以显示错误消息,则上面的组件将是这样。
<template>
<div v-if="errMsg"> {{ errMsg }} </div>
<Suspense v-else>
<template #default>
<article-info />
</template>
<template #fallback>
<div>正在拼了命的加载…</div>
</template>
</Suspense>
</template>
<script>
import { onErrorCaptured } from 'vue'
setup () {
const errMsg = ref(null)
onErrorCaptured(e => {
errMsg.value = '呃,出了点问题!'
return true
})}
return { error }
</script>
# 路由组件编程
Vue Router 4.0 提供了 Vue 3 支持,并有许多突破性的变化。
# Vue 路由管理器
# 定义路由组件
路由组件装载着需要的内容,通常这类组件放在 src/views 文件夹内(about.vue、home.vue)
# 定义路由控制器
import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router";
import About from '../views/about.vue'
const routes: Array<RouteRecordRaw> = [
{ // 配置路由地址
path: "/about",
component: About,
children: [ // 其他子路由
// { path:'note', component: aboutl }
],
meta: {} // $route元数据 router.meta
},
{ // 配置异步路由组件
path: "/home", component: () =>
// 中间可添加打包后名称
import(/* webpackChunkName: "home" */ "../views/home.vue")
},
];
const router = createRouter({
// 路由路径模式,
// createWebHashHistory对应hash模式,
// createWebHistory对应history模式
history: createWebHashHistory(),
routes
});
export default router;
# Vue 入口函数引入路由
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
const app = createApp(App);
app.use(router)
.mount("#app");
# 静态组件使用路由组件
<div id="app">
<router-link to="/about">Go to about</router-link>
<router-link to="/home">Go to home</router-link>
<router-view> </router-view>
</div>
# 路由组件缓存
可让显示路由保存值和自身,在刷新页面时或重新启动浏览器不会消失
<keep-alive include="test-keep-alive"> <!-- 利用keep-alive标签包裹显示路由 -->
<router-view class="w"></router-view>
</keep-alive>
# 组件中操作路由
# 组件中访问路由
<script setup lang="ts">
import { useRoute, useRouter } from "vue-router";
// 当前显示路由
const $route = useRoute();
// 路由管理器
const $router = useRouter();
</script>
# 组件中操纵跳转
<script setup lang="ts">
import { useRoute, useRouter } from "vue-router";
const $router = useRouter();
// 页面跳转(推入)
$router.push("/home")
// 页面跳转(替换)
$router.replace("/about")
// 页面回退(前提是前进页面)
$router.back()
// 页面前进(前提是返回页面)
$router.go(1)
</script>
# 路由跳转时传入参数
# 路径中传入占位符参数(params)
// 定义路由时, 定义占位符为id
roates: [{ path: '/home/:id', commponent: Home }]
<!-- 定义标签内跳转, 传入id为6 -->
<router-link :to="/home/6">User</router-link>
/* 定义命令跳转, 传入id为6 */
$router.push("/home/6")
# 对象中传入对象参数(params)
<!-- 定义标签内跳转, 传入id为6 -->
<router-link :to="{path: '/home', params: {id: 60}}">User</router-link>
/* 定义命令跳转, 传入id为6 */
$router.push({path: "/home", params: {id: 60}})
# 对象中传入查询参数(query)
<!-- 定义标签内跳转, 传入id为6 -->
<router-link :to="{path: '/home', query: {id: 60}}">User</router-link>
/* 定义命令跳转, 传入id为6 */
$router.push({path: "/home", query: {id: 60}})
# 监听路由参数变化
# 使用watch进行监听
import { watch } from "vue";
import { useRoute } from "vue-router";
const route = useRoute();
// 全局监听
watch(route, (to, from) => { });
// 单独监听
watch(route.params, (to, from) => {});
# 使用watchEffect进行监听
import { watchEffect } from "vue";
import { useRoute } from "vue-router";
const route = useRoute();
// 全局监听
watchEffect(() => {
console.log("当前路由", route)
})
// 单独监听
watchEffect(() => {
console.log("当前路由参数", route.params)
})
# 使用 beforeRouteUpdate 导航守卫
import { onBeforeRouteUpdate } from "vue-router";
onBeforeRouteUpdate((to, from, next) => {
// 对路线变化做出反应...
// 不要忘记调用 next()
});
# 导航守卫(路由拦截器)
# 全局前置守卫
使用 router.beforeEach
注册一个全局前置守卫:
const router = createRouter({
history: createWebHashHistory(),
routes
});
router.beforeEach((to, from, next) => {
// ...
next();
})
当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中。具体参数参考:
# 全局后置钩子
可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next
函数也不会改变导航本身:
router.afterEach((to, from) => {
// ...
})
# 路由独享守卫
在路由配置上直接定义 beforeEnter
守卫:
const routes: Array<RouteRecordRaw> = [
{ path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
# 组件内的守卫
可以在路由组件内直接定义以下路由导航守卫:
import { onBeforeRouteUpdate, onBeforeRouteLeave } from "vue-router";
// 对路线变化做出反应, 不要忘记调用 next()
onBeforeRouteUpdate((to, from, next) => { });
// 导航离开该组件的对应路由时调用
onBeforeRouteLeave((to, from, next) => { });
# 当前路由路队列信息(matched)
详细内容:
与给定路由地址匹配的标准化的路由记录 (opens new window)数组。
// currentRoutePath
'/user/control/a'
// route.matched
[{path:'/user', ...}, {path: '/user/control',...},......]
#
# 动态路由添加(addRoute)
addRoutes 被 addRoute 替代,添加一条新的路由记录 (opens new window)作为现有路由的子路由。如果路由有一个 name
,并且已经有一个与之名字相同的路由,它会先删除之前的路由。
// currentRoutes
[
{ path: '/login' component: /*....*/ }
]
// addRoute
routes.addRoute({ path: '/user', component: /*....*/ })
// currentRoutes
[
{ path: '/login' component: /*....*/ },
{ path: '/user', component: /*....*/ }
]
# addRoutes 兼容方法
const addRoutes = routes.forEach(route => router.addRoute(route))
# 退出登录后刷新路由方法
location.reload();
# 将 props 传递给路由组件
在你的组件中使用 $route
会与路由紧密耦合,这限制了组件的灵活性,因为它只能用于特定的 URL。虽然这不一定是件坏事,但我们可以通过 props
配置来解除这种行为:
我们可以将下面的代码
const User = {
template: '<div>User {{ $route.params.id }}</div>'
}
const routes = [{ path: '/user/:id', component: User }]
替换成
const User = {
props: ['id'],
template: '<div>User {{ id }}</div>'
}
const routes = [{ path: '/user/:id', component: User, props: true }]
这允许你在任何地方使用该组件,使得该组件更容易重用和测试。
#
# Vuex 集中式状态管理
Vuex 4.0 提供了 Vue 3 支持,其 API 与 3.x 基本相同。唯一的突破性变化是插件的安装方式 (opens new window)。
# 简单store状态管理
# 1. 定义核心管理模块
// 新版创建方式由createStore创建
import { createStore } from "vuex";
// 暴露VuexStore
export default createStore({
state: {}, // 状态对象
mutations: {}, // 包含多个更新state函数的对象
actions: {}, // 包含多个对应事件回调函数的对象
getters: {}, // 包含多个getter计算属性函数的对象
modules: {} // 包含多个store模块的对象
});
# 2. 入口文件中引入
import { createApp } from "vue";
import App from "./App.vue";
import store from "./store";
createApp(App)
.use(store)
.mount("#app");
# 模块化状态管理
# 1. 定义文件目录结构(src / store)
userInfo.ts
/ 状态对象 /order.ts
/ 多个更新state函数的引用对象 /
// 定义 `modules` 中使用的对象模板
import { Module } from "vuex";
const createStore = <S>(store: Module<S, any>) => store
export default createStore({
state: {}, // 状态对象
mutations: {}, // 包含多个更新state函数的对象
actions: {}, // 包含多个对应事件回调函数的对象
getters: {}, // 包含多个getter计算属性函数的对象
modules: {} // 包含多个store模块的对象
})
# 2. 定义Vuex接口 (src / store / index.ts)
import { createStore } from "vuex";
import userInfo from "./userInfo"
import order from "./order"
export default createStore({
modules: { // 包含多个store模块的对象
userInfo,
order
}
});
# 模块访问全局状态
# action 获取全局状态
import { Module } from "vuex";
const createStore = <S>(store: Module<S, any>) => store
export default createStore({
actions: {
incrementRootCount({state, commit, rootState, rootGetters}) {
console.log("全局状态: ", rootState)
console.log("全局计算: ", rootGetters)
}
}
})
# getters 获取全局状态
import { Module } from "vuex";
const createStore = <S>(store: Module<S, any>) => store
export default createStore({
getters: {
allCount(state, getters, rootState) {
console.log("当前储存库: ", state);
console.log("当前储存库计算: ", getters);
console.log("全局储存库: ", rootState);
}
}
})
# Vuex 组件 / 文件中使用
# 字符串模板访问 state
Vuex
在use
中引入后,全局属性将多出一个$store
的属性,在ComponentAPI
中,this
并不能访问,但字符串模板可以访问。
<!-- 任意组件 -->
<div>{{ $store.state.cliNum }}</div>
<div>{{ $store.getters.type }}</div>
# 组件访问 state
在Vue3
中,this
被废弃,获取当前状态由useStore
钩子获取。
import { useStore } from "vuex";
const store = useStore();
console.log(store.state);
console.log(store.getters);
# 字符串模板调用 actions 函数
$store.dispatch
会调用Vuex
中的actions
函数,第一个参数传入函数名称,第二个参数传入自定义参数,这里需要注意,$sotre.dispatch
只能传输两个参数。
<!-- 任意组件 -->
<button @click="$store.dispatch('qaq', [a,b,c])">+</button>
# 组件调用 actions 函数
import { useStore } from "vuex";
const store = useStore();
store.dispatch('qaq', 5555)
# 第三方插件 VeeValidate 4.0
VeeValidate 是基于Vue的表单验证扩展,熟悉后可以很容易的设置声明式验证。带有直观的API和较小的占用空间,可以很快速的构建的表单验证。体积小,复杂度低,且可和任意的UI组件库搭配使用。带有内置规则,拥有全球化语音环境,是一块十分优秀的插件。
安装:npm i vee-validate@next --save
# 全局验证器
// plugins/vee-validate.ts (需初始化执行)
import { defineRule } from 'vee-validate';
// 添加必须项规则
defineRule('required', value => {
if (!value || !value.length) {
return '该选项为必选';
}
return true;
});
// 添加邮箱规则
defineRule('email', value => {
// 字段为空, 应通过
if (!value || !value.length) {
return true;
}
// 监测是否是邮箱
if (!/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}/.test(value)) {
return '邮箱格式不正确';
}
return true;
});
# 使用全局验证器
<template>
<vee-from @submit="onSubmit" v-slot="{ isSubmitting }">
<field name="name" as="input" rules="required" />
<field name="email" as="input" rules="required|email" />
<button :disabled="isSubmitting" :class="{ 'submitting': isSubmitting }">Submit</button>
<error-message :name="name" />
</vee-from>
</template>
<script setup lang="ts">
// 引入并暴露 vee-validate 组件
export { Field, Form as VeeForm, ErrorMessage } from "vee-validate";
// 表单通过验证
const onSubmit = (values: any) => {
console.log(values);
};
</script>
# Vue 常见问题 | 扩展
在 Vue 开发中,遇到的疑难杂症,与发现的 Vue 相关扩展。
# 路由级别权限管理
- 定义好全部的路由地址以及映射地址
- 通过用户不同,向后台请求不同的路由权限数据
- 对用户权限做对比:请求数据 === 全部的路由,取出后添加路由配置
# 按钮级别权限管理
- 定义自定义指令,标签判断当前 route.mete 是否存在权限信息,不存在则移除DOM。
# 虚拟节点创建 (create vnode)
import { h } from 'vue'
// 传入组件内容, 与组件 props 参数, 生成虚拟节点
const vnode = h(Component, props)
// 组件内容可为导入组件, 例如
import Component from './Component.vue'
const vnode = h(Component, props)
// 也可为 template 例如
const vnode = h('<div>hello world!!</div>', props)
// 也可为节点的描述数据 例如
const vnode = h('div', { id: 'app' }, [h('span', '我是 span')])
# 虚拟节点渲染 (render vnode)
import { h, render } from 'vue'
// 渲染虚拟节点至真实节点上 ( 挂载组件 )
const container = document.createElement('div')
render(h(Component, props), container)
document.body.appendChild(container.firstElementChild)
// 销毁真实节点的所有实例 ( 卸载组件 )
render(null, container)
# 其他组合式 API
// 将 ref 转换为 .value
unref
// 浅响应式 ref
shallowRef
// 浅响应式 reactive
shallowReactive
// 将对象转换为只读对象
readonly
// 浅只读
shallowReadonly
# 标记与还原
- toRaw
- 返回由
reactive
或readonly
方法转换成响应式代理的普通对象。 - 这是一个还原方法,可用于临时读取,访问不会被代理/跟踪,写入时也不会触发界面更新。
- 返回由
- markRaw
- 标记一个对象,使其永远不会转换为代理。返回对象本身
- 应用场景:
- 有些值不应被设置为响应式的,例如复杂的第三方类实例或 Vue 组件对象。
- 当渲染具有不可变数据源的大列表时,跳过代理转换可以提高性能。
# 响应式判断
- isRef: 检查一个值是否为一个 ref 对象
- isReactive: 检查一个对象是否是由
reactive
创建的响应式代理 - isReadonly: 检查一个对象是否是由
readonly
创建的只读代理 - isProxy: 检查一个对象是否是由
reactive
或者readonly
方法创建的代理
# 自定义 Ref
let value = ''
const vref = customRef((track, trigger) => {
return {
get() {
// 初次调用 > 追踪依赖
track()
},
set(_value) {
// 修改完毕后, 通知 dep 更新
value = _value
trigger()
}
}
})
# TS 中常用的内置类型
// 所有 style 内容
CSSProperties
// 计算属性
ComputedRef<T>
// 监视源
WatchSource<T = any>
// 是或不是Ref
MaybeRef<T>