Vue3 的新特性学习与实践,以及配合 vite、typescript 使用
# Vue 3 的安装
# 使用 vue-cli 创建项目
检查 vue 版本
vue -V
创建项目
vue create projectName
选择 vue3 default
如果你在 Windows 上通过 minTTY 使用 Git Bash,交互提示符并不工作。你必须通过
winpty vue.cmd create hello-world
启动这个命令。不过,如果你仍想使用vue create hello-world
,则可以通过在~/.bashrc
文件中添加以下行来为命令添加别名。alias vue='winpty vue.cmd'
你需要重新启动 Git Bash 终端会话以使更新后的 bashrc 文件生效。
# 使用 vite 创建项目
- 快速的冷启动
- 即时的模块热更新
- 真正的按需编译
安装 vite
npm install -g create-vite-app
利用 Vite 安装 Vue3.0 项目
create-vite-app projectName
# 安装依赖
- dependencies
axios
vue
vue-router
vuex
- devDependencies
vite
sass
sass-loader
eslint
eslint-config-prettier
typescript
stylelint
stylelint-config-standard
stylelint-order
@typescript-eslint/eslint-plugin
@typescript-eslint/parser
scripts
脚本命令中加入 lint 命令
"format": "prettier --write \"src/**/*.ts\" \"src/**/*.vue\"", "lint": "eslint --fix --ext .ts,.vue src"
添加配置文件
tsconfig.json
.eslintrc.js
prettier.config.js
stylelint.config.js
# vue3 新特性
# 创建 App
vue2.x
import Vue from "vue"; import App from "./App.vue"; new Vue({ render: (h) => h(App), }).$mount("#app");
vue3.0
import { createApp } from "vue"; import App from "./App.vue"; createApp(App).mount(App, "#app");
# 生命周期
废弃beforeCreate
created
,在 setup 函数中完成
(beforeCreate) => setup;
(created) => setup;
(beforeMount) => onBeforeMount;
(mounted) => onMounted;
(beforeUpdate) => onBeforeUpdate;
(updated) => onUpdated;
(beforeDestory) => onBeforeUnmount;
(destoryed) => onUnmounted;
(activated) => onActivated;
(deactivated) => onDeactivated;
(errorCaptured) => onErrorCaptured;
# 全局 API
vue2.x
import Vue from "vue"; import App from "./App.vue"; Vue.prototype.custom = () => {}; new Vue({ render: (h) => h(App), }).$mount("#app");
调用
this.custom;
vue3.0
import { createApp } from "vue"; import App from "./App.vue"; const app = createApp(App); app.config.globalProperties.custom = () => {}; app.mount(App, "#app");
调用
import { getCurrentInstance } from "vue"; export default { setup() { const { ctx } = getCurrentInstance(); const value = ctx.custom; return { value }; }, };
# Tree-shaking
vue3 不会把所有的 api 都全局引入,需要自己按需引入。
# Suspense
加载异步组件,在异步组件加载完成成并完全渲染之前 suspense 会先显示 #fallback 插槽的内容 。
<Suspense>
<template>
<Suspended-component />
</template>
<template #fallback> Loading... </template>
</Suspense>
# 异步组件
在 Vue3 中,提供了defineAsyncComponent()
方法创建异步组件,同时可以返回一个 Promise 对象来自己控制加载完成时机
import { defineAsyncComponent } from "vue";
// simple usage
const AsyncFoo = defineAsyncComponent(() => import("./Foo.vue"));
const asyncComponent = defineAsyncComponent(
() =>
new Promise((resolve, reject) => {
/* ... */
})
);
# Fragments(组件根元素数量)
不再限制 template 中根元素的个数(旧的版本之前是只能有一个根元素)
# 指令动态参数
指令名,事件名,插槽名,都可以使用变量来定义了。
<!-- v-bind with dynamic key -->
<div v-bind:[key]="value"></div>
<!-- v-bind shorthand with dynamic key -->
<div :[key]="value"></div>
<!-- v-on with dynamic event -->
<div v-on:[event]="handler"></div>
<!-- v-on shorthand with dynamic event -->
<div @[event]="handler"></div>
<!-- v-slot with dynamic name -->
<foo>
<template v-slot:[name]> Hello </template>
</foo>
<!-- v-slot shorthand with dynamic name -->
<!-- pending #3 -->
<foo>
<template #[name]> Default slot </template>
</foo>
# 插槽 slot 语法
用到自定义 render 方法和插槽时,this.$scopedSlots
替代成this.$slots
<foo>
<template v-slot:one="one">
<bar v-slot="bar">
<div>
{{ one }} {{ bar }}
</div>
</bar>
</template>
<template v-slot:two="two">
<bar v-slot="bar">
<div>
{{ two }} {{ bar }}
</div>
</bar>
</template>
</foo>
简写
<TestComponent>
<template #one="{ name }">Hello {{ name }}</template>
</TestComponent>
# transition-class
v-enter
重命名成 v-enter-from
,v-leave
重命名成 v-enter-from
.v-enter-from,
.v-leave-to {
opacity: 0;
}
.v-leave-from,
.v-enter-to {
opacity: 1;
}
# v-model
实现
<input type="text"
:value="price"
@input="price=$event.target.value">
1.原来的方式保留
<input v-model="foo">
2.可绑定多个 v-model
<InviteeForm v-model:name="inviteeName" v-model:email="inviteeEmail" />
其实上面这种方式就相当于之前的 .sync 。
3.额外处理
<Comp
v-model:foo.trim="text"
v-model:bar.number="number" />
# .sync
将之前的 v-model 和.sync 整合到一起了,并淘汰了.sync 写法
<!-- vue 2.x -->
<MyComponent v-bind:title.sync="title" />
<!-- vue 3.x -->
<MyComponent v-model:title="title" />
# 指令的钩子函数
vue2.x
const MyDirective = { bind(el, binding, vnode, prevVnode) {}, inserted() {}, update() {}, componentUpdated() {}, unbind() {}, };
vue3.0
const MyDirective = { beforeMount(el, binding, vnode, prevVnode) {}, mounted() {}, //对应inserted beforeUpdate() {}, // new updated() {}, //对应update beforeUnmount() {}, // new unmounted() {}, // 对应unbind };
# 自定义标签和 is
- 自定义标签
const app = Vue.createApp({});
app.config.isCustomElement = (tag) => tag === "plastic-button";
- 动态组件 is
由于is
的特性,这种写法在 Vue2.x 最终会被渲染成<plastic-button>
组件,但是在 Vue3 中,只会把is
当作一个普通的 props 属性,如果想实现 Vue2.x 一样的效果,可以使用v-is
<!---vue2.x---->
<button is="plastic-button">Click Me!</button>
<!---vue3.0---->
<button v-is="plastic-button">Click Me!</button>
# router
router-link 添加 scoped-slot API 和 custom 属性,并移除了 tag 属性和 event 属性。
添加 scoped-slot 有什么用呢?以前只能通过 active-class 来改变元素样式的,现在有了 scoped-slot 之后,我们就更加灵活了,可以根据 scoped-slot 回传的状态自定义,不管是样式还是类。
<router-link to="/" custom v-slot="{ href, navigate, isActive }">
<li :class="{ 'active': isActive }">
<a :href="href" @click="navigate">
<Icon>home</Icon><span class="xs-hidden">Home</span>
</a>
</li>
</router-link>
获取当前路由信息
import router from "../router"; export default { setup() { const currentRoute = router.currentRoute.value; console.log(currentRoute); }, };
创建路由
import { createRouter, createWebHashHistory } from "vue-router"; const router = createRouter({ history: createWebHashHistory(), routes, });
# 动态路由
vue-router4
- router.addRoute(route: RouteRecord) 动态添加路由
- router.removeRoute(name: string | symbol),动态删除路由
- router.hasRoute(name: string | symbol): boolean ,判断路由是否存在
- router.getRoutes(): RouteRecord[] 获取路由列表
# 属性值修正
在新版本中基本保持了原样,也就是我们给元素添加什么属性值,vue 处理完后还是什么属性值。
# Composition API
# emit 用法
export default {
setup(props, context) {
context.emit("created");
},
};
context 结构
attrs: (...)
emit: (...)
slots: (...)
props V.S. attrs
- props 要先声明才能取值,attrs 不用先声明
- props 不包含事件,attrs 包含
- props 支持 String 以外的类型,attrs 只有 String 类型
- props 没有声明的属性,会在 attrs 里,若在 props 内声明了该属性,那么 attrs 里就不会出现该属性
ES6 写法
export default {
props: {
msg: String,
},
// 需要在 emits 中声明 不然会报错 下面会有解释
emits: {
sayhi: (payload) => {
// validate payload by returning a boolean
return payload;
},
},
setup(props, { emit }) {
emit("created");
},
};
在 setup 中使用 emit , 需要在 emits 中声明它,否则会报以下错误。
Extraneous non-emits event listeners (此处是函数名) were passed to component but could not be automatically inherited because component renders fragment or text root nodes. If the listener is intended to be a component custom event listener only, declare it using the "emits" option.
# provide/inject
provide()
和inject()
可以实现嵌套组件之间的数据传递.这两个函数只能在setup()
函数中使用。父组件中使用 provide()
函数向下传递数据;子组件中使用 inject()
获取上层传递过来的数据。
父组件:
import { provide } from "vue";
setup(){
const color=ref('pink')
provide('globalColor',color)
}
子组件:
import {inject} from '@vue/composition-api';
setup(){
const color= inject('globalColor')
return {color}
}
# ref、reactive、toRefs
import { ref, reactive, toRefs } from "vue";
- ref 只可以监听简单数据(如数字、字符串、布尔之类的简单数据),reactive 可以监听所有数据
- ref 修改数据需要使用这样count.value=xxx的形式,而 reactive 只需要state.reactiveField=值这样来使用
- 在 reactive 在 return 时候需要 toRefs 来转换成响应式对象
- toRefs 函数我是这么理解的 他能将 reactive 创建的响应式对象,转化成为普通的对象,并且这个对象上的每个节点,都是 ref()类型的响应式数据。
# watch、watchEffect、computed
watch 监听的目标只能是 getter/effect 函数、ref、reactive 对象或数组。
watchEffect 和 watch 类似,可以监听属性的变化
import { watch, watchEffect } from "vue";
// 简单监听
watch(count, () => {
console.log(count.value);
});
// 下面直接监听data.state会报错。watch 监听的目标只能是getter/effect函数、ref、reactive对象或数组。
// watch(data.state, (newValue, oldValue) => {
// console.log('newValue', newValue)
// console.log('oldValue', oldValue)
// })
// 监听 新的值和旧值
watch(
() => state.reactiveField,
(newValue, oldValue) => {
console.log("newValue", newValue);
console.log("oldValue", oldValue);
}
);
// watch 监听多个属性,返回的也是多个值的数组
watch([() => state.reactiveField, () => state.a], (newValue, oldValue) => {
console.log("old", oldValue);
console.log("new", newValue);
});
// watchEffect 不需要指定监听的属性
watchEffect(() => {
console.log("watchEffect ===>", state.reactiveField);
});
watch VS watchEffect
watchEffect
不需要指定监听的属性,它会自动的收集依赖,只要我们回调中引用到了 响应式的属性, 那么当这些属性变更的时候,这个回调都会执行,而watch
只能监听指定的一个或多个属性而做出变更。watch
可以获取到新值与旧值,而watchEffect
是拿不到的oldValue
watchEffect
在组件初始化的时候就会默认执行一次,而 watch 不需要。
computed
计算属性和 vue2.x 中一样
# vue3.0 移除
# 移除 filters
<template>
<p>{{ accountBalance | currencyUSD }}</p>
</template>
<script>
export default {
filters: {
currencyUSD(value) {
return '$' + value
}
}
}
</script>
替换为
<template>
<p>{{ accountInUSD }}</p>
</template>
<script>
export default {
props: {
accountBalance: {
type: Number,
required: true
}
},
computed: {
accountInUSD() {
return '$' + this.accountBalance
}
}
}
</script>