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 发布会视频:www.bilibili.com/video/BV1iA…
全局 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 (见下方) |
Vue.config.ignoredElements | app.config.isCustomElement (见下方) |
Vue.component | app.component |
Vue.directive | app.directive |
Vue.mixin | app.mixin |
Vue.use | app.use (见下方) |
由于 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
的修饰符- off 和 $once 实例方法
- 过滤
- 内联模板 attribute
$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 中完整的细节,
Vuex
Vuex 4.0 提供了 Vue 3 支持,其 API 与 3.x 基本相同。唯一的突破性变化是插件的安装方式。
Devtools Extension
我们正在开发一个新版本的 Devtools,它有一个新的 UI 和经过重构的内部结构,以支持多个 Vue 版本。新版本目前处于测试阶段,目前只支持 Vue 3。Vuex 和路由器的集成也在进行中。
- Chrome:从 Chrome web 商店中安装
- 提示:beta 版本可能与 devtools 的稳定版本冲突,因此你可能需要暂时禁用稳定版本,以便 beta 版本正常工作。
- Firefox:下载签名扩展 (assets 下的
.xpi
文件)
IDE 支持
推荐使用 VSCode 和我们官方拓展 Vetur,它为 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 定义响应式数据
定义数据(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";
// 定义单个响应式数据
export const count = ref(0);
// 通过改变count.value发生响应式变化
count.value++;
// 以对象形式定义响应式数据
export const state = reactive({ count: 0 });
// 通过改变对象发生响应式变化
state.count++;
模板中使用
<template>
<span>{{ count }}</span>
<span> {{ state.count }}</span>
</template>
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";
export const firstName = ref("Aaa");
export const lastName = ref("bbbB");
export 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";
export const firstName = ref("Aaa");
export const lastName = ref("bbbB");
export 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>
指定数据源监视(watch)
watch 函数用来侦听特定的数据源,并在回调函数中执行副作用。默认情况是懒执行的,也就是说仅在侦听的源变更时才执行回调。
import { ref, watch } from "vue";
export 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; // 是否初始化执行, 及以当前值执行
收集依赖并监视(effect)
watchEffect会自动收集使用依赖并立即执行传入的一个函数,并响应式追踪其依赖,并在其依赖变更时重新运行该函数。
<script setup lang="ts">
import { ref, watchEffect } from "vue";
export const firstName = ref("Aaa");
export const lastName = ref("bbbB");
export const name = ref("");
// watchEffect会自动收集使用依赖
// 当firstName与lastName改变时执行监视函数
watchEffect(() => {
name.value = firstName.value + "|" + lastName.value;
});
</script>
停止监视
watchEffect 返回停止函数,可以调用该函数以显式停止观察程序:
import { ref, watchEffect } from "vue";
export 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) { },
})
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
computed 替换过滤器
Filters 已从 Vue 3.0 中删除,不再受支持。在 3.x 中,filters 已删除,不再受支持。相反,官方建议用方法调用或计算属性替换它们。
// filters.ts
import { computed } from "vue";
export function (value: Ref) {
return computed(() => "...")
}
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 源码分析
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'
export const count = ref(0)
export const inc = () => count.value++
</script>
<style lang="scss"></style>
引入组件格式
<template>
<!-- 使用组件标签(大小写区分) -->
<HelloWorld></HelloWorld>
<!-- 使用组件标签(中划线区分) -->
<hello-world />
</template>
<script lang="ts">
export { default as 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接收数据 -->
declare const props: {
addTask: Array<any>
}
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"
export const count = ref(0)
export 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){...})
路由组件编程
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) => {});
使用effect?进行监听
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) => { });
Vuex 集中式状态管理
Vuex 4.0 提供了 Vue 3 支持,其 API 与 3.x 基本相同。唯一的突破性变化是插件的安装方式。
简单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";
// 表单通过验证
export const onSubmit = (values: any) => {
console.log(values);
};
</script>