项目构建基础环境:
node 12.14.0
npm版本 6.13.4
ts版本 4.2.2
vue/cli版本 4.5.1
get方法请求此api,可以随机获得一些dog图片,用于本地开发调试:
https://dog.ceo/dog-api/
Vue3项目的一些区别
初始化生成的项目相比Vue2,整体几乎没有什么变化,只是在src文件夹的下面多了一个shims-vue.d.ts文件,这是一个类文件(也叫做定义文件),因为.vue结尾的文件在ts中不认可,所以要有定义文件,这一段删除,会发现 import 的所有 vue 类型的文件都会报错。
main.js文件内容的区别
vue2.x使用import Vue from 'vue',然后使用new Vue()创建实例。vue3.x则是import {createApp} from 'vue',通过createApp()来创建实例了,这就导致了很多的插件或UI组件库不能使用,比如ElementUI。
router.js创建路由
vue3.x需要引入createRouter创建地址路由。createWebHashHistory对应之前的hash,createWebHistory对应之前的history。
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import Home from '../views/Home.vue'
const routes: Array = [
{
path: '/',
name: 'Home',
component: Home
}
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
export default router
vuex状态管理
vue3.x中状态管理的创建方式变为了createStore 。代码结构更精简合理。
import { createStore } from 'vuex'
export default createStore({
state: {
},
mutations: {
},
actions: {
},
modules: {
}
})
Composition API
这个是vue3.x变化最大的地方,vue2.x数据存放在data,方法在methods,通过this去调用。但是在vue3.x这些都没有了,所有的代码全部都在 setup 里面实现,你页面需要哪些方法,就要在当前页引入即可。
同时Composition API可以实现更好的代码组合和复用,这方面Vue2的解决方案是Mixin,但是Mixin有着明显的缺点:
- 命名冲突
- 不清楚暴露出来的变量的作用
- 重用到其他component经常会遇到问题
而Composition API将相关的fetch组合在一起,不要分散在多个地方,从而可以非常容易的重复,比mixin有更高的灵活度
生命周期的变化
主要是废弃了beforeCreate和created生命周期
另外有vue3中就两个生命周期的名字改变了
beforeDestory => beforeUnmount
destoryed => unmounted
改变原因:更加语义化,一个组件的过程更应该是mount和unmount的过程。
beforeCreate 和created 两个周期和setup几乎同时,所以把其代码写在setup里就可以了
renderTracked和renderTriggered 是调试用的生命周期,其参数event记录了数据变化的过程
import {onBeforeMount,onMounted,onBeforeMount,onBeforeUpdate,onUpdated,onUnmounted} from 'vue'
export default {
name:'',
setup(props,context){
console.log('我最先被触发')
onBeforeMount(()=>{}),
onMounted(()=>{}),
onBeforeUpdate(()=>{}),
onUpdated(()=>{ }),
onBeforeMount(()=>{ }),
onMounted(()=>{}),
}
}
一些Composition API的简单用法:
setup
setup有两个参数 props 和 context
- props:接受父组件传的值
- context:vue3.x里面没有this,提供了一个context上下文属性,可以通过这个属性去获取进行 一些 vue2.x 用this实现的操作
如:context.attrs,context.slots,context.parent,context.emit,context.refs
ref 声明基础数据类型
创建一个响应式的基础类型,只能监听number、String、boolean等简单的类型,该属性一旦发生更改,都会被检测到。
{{count}} // 1
import {ref} from "vue"
setup(){
const count =ref(0)
count.value++ //必须要加.value
return{
count //一定要return 出去
}
}
reactive 声明响应式数据对象
{{count.name}} // 857
import {reactive} from "vue"
setup(){
const count =reactive({
name:'369'
})
count.name='857'
return{
count
}
}
注意:reactive定义一个响应式的对象data,但是要注意的是,如果用data.count
取了里面的数据,则data.count则变成了非响应式的,所以需要const refData = toRefs(data)
将data转换一下,那么data的属性也变成响应式的
router 路由
vue3.x的router和route属性也有了很大的变化,在vue2.x中使用this.route获取当前页面路由信息,而在Vue3中,需要分别引入useRouter和useRoute
// 路由跳转
import { useRouter} from "vue-router";
setup(){
const router=useRouter()
router.push('/path')
}
// 获取当前页面路由信息
import { useRoute} from "vue-router";
setup(){
const route=useRoute()
console.log(route) //这里的route相当于vue2.x中的this.$route
}
接下来npm run serve运行项目,项目有一个报错
Syntax Error: Error: Failed to load config "plugin:vue/vue3-essential" to extend from
查了了一下没有找到解决办法,暂时先把.eslintrc.js文件里的plugin:vue/vue3-essential注释掉,再次启动,项目正常运行了。
第一个组件:Dropdown
在vHeader组件中,定义一个点击展现下拉框的组件Dropdown,为了后期的维护方便,在Dropdown中再增加一个DropdownItem组件,DropdownItem支持参数disabled来禁止点击。
需求难点:
下拉框默认隐藏,当点击元素自身的时候,下拉框展现出来,当点击元素以外的地方的时候,下拉框再次隐藏。
首先需要在Dropdown组件中拿到元素自身
- 1.定义一个HTML元素:const dropdownRef = ref
(null) - 在setup函数中return出去dropdownRef 后将其通过ref绑定到Dropdown组件的根元素上:
- 自定义一个hook函数useClickOutside,在调用后获取其点击的是否为当前自身元素,返回值true代表点击是元素之外,通过watch监听实现下拉框隐藏
const isClickOutside = useClickOutside(dropdownRef) watch(isClickOutside, () => { if (isOpen.value && isClickOutside.value) { isOpen.value = false } })
useClickOutside使用了Composition API,因为给页面元素document绑定事件是一个很危险的操作,一定要记得及时销毁,否则会大大增加内存损耗。所以通过onMounted和onUnmounted生命周期实现事件的绑定和解除,代码如下:
import { ref, onMounted, onUnmounted, Ref } from 'vue' const useClickOutside = (elementRef: Ref
) => { const isClickOutside = ref(false) const handler = (e: MouseEvent) => { if (elementRef.value) { if (elementRef.value.contains(e.target as HTMLElement)) { isClickOutside.value = false } else { isClickOutside.value = true } } } onMounted(() => { document.addEventListener('click', handler) }) onUnmounted(() => { document.removeEventListener('click', handler) }) return isClickOutside } export default useClickOutside 自定义表单组件
需求:
1.各种输入框需要通过某个事件触发验证规则,比如blur
2.验证规则支持自定义多种Rule,比如不为空,长度,特殊格式等
3.验证规则未通过的时候出现具体的警告
4.有一个提交按钮,提交后验证整个Form,验证全部规则,不通过的问题显示出现让用户进行调整首先在自定义一个最基础的ValidateInput.vue组件
表单组建最重要的一点是可以自定义验证规则
在组件内先定义一个接口interface RuleProp { type: 'required' | 'email' | 'custom'; message: string; validator?: () => boolean; }
这个接口就规定了验证规则的格式,但是验证规则可能会有多个,所以需要通过type自定义一个数组接口
export type RulesProp = RuleProp[]
这样就能在props对接收到的rules进行限制
props: { // 将一个构造函数断言成一个类型,需要使用PropType rules: Array as PropType
} 在父组件传入验证规则的时候通过引入RulesProp,从而实现验证规则的代码格式自动Ts校验:
const emailRules: RulesProp = []
实现表单组件对v-model的支持
首先看下vue2中原生组件input
已经实现的形式:v-model的实现原理
Vue自定义组件实现的形式
组件v-model在Vue2中的实现原理
但是表单元素不止有普通的input,像type="checkbox"这种复选框形式的,值的获取并不是:value,同时值的改变也不是input事件,比如下面这个
Vue2的解决方案:
通过model去overwrite传递过来的prop属性,然后触发event里的事件但是Vue2的实现方式非常的难以理解,所以在Vue3中,对此进行了优化
组件内部直接在props定义modelValue属性props: { modelValue: String, },
然后通过update:modelValue将改变后的值通过参数传递出去,实现v-model数据的双向绑定
const inputRef = reactive({ val: computed({ get: () => props.modelValue || '', set: val => { context.emit('update:modelValue', val) } }), error: false, message: '' })
提交表单事件
在ValidateForm.vue中添加两个插槽slot,默认插槽用来插入表单元素内容,另外一个具名插槽submit通过插入表单底部按钮元素
点击底部的div可以触发submitForm事件,这个事件会通过every事件来获取其中的Boolean状态,并将这个验证状态传递给父组件
const submitForm = () => { const result = funcArr.map(func => func()).every(result => result) context.emit('form-submit', result) }
在父组件的引用标签
中添加多个普通表单元素,以及一个具名的template 兄弟组件之间的通信
子组件之间调用方法,传递数据,但是在vue3中,off都已经被废弃,只好求助mitt实现事件监听。
npm install mitt --save
在ValidateForm.vue中通过on接收并执行回调,并在onUnmounted生命周期函数中清理掉监听器const callback = (func?: ValidateFunc) => { if (func) { funcArr.push(func) } } // 添加监听器 emitter.on('form-item-created', callback) onUnmounted(() => { // 清理监听器 emitter.off('form-item-created', callback) funcArr = [] })
在ValidateInput.vue中触发监听器,并传递一个规则验证的回调函数
emitter.emit('form-item-created', validateInput)
父子组件传递的样式传递
子组件想接受父组件传递过来的一些默认的class,这个class我们要给指定默认的元素上,而不是子组件的根元素上
Vue3的组件开发,v-bind="$attrs"
可以继承父组件自动传递过来的一部分属性,而不必再去写props接收
首先不要让子组件去继承默认的attribute
添加属性:inheritAttrs: flase
给相应的想要添加class的元素添加 v-bind= "$attrs"
然后在父组件使用子组件的标签上通过class传递一些样式
这样demo-class1和demo-class2都自动带入到子组件指定的元素上了自定义顶部消息提示弹窗
首先在components文件夹中自定义一个Message.vue文件用来写样式代码,利用Vue3提供的
标签,可以将元素自动渲染到id为message的标签中,但是页面中需要有相对应的标签,可以写在index.html中,但是这里我们在hooks文件夹中再新建一个useDOMCreate.ts文件,专门用来生成和销毁 元素。
接下来新建一个createMessage.ts文件用来写核心逻辑代码,在页面中使用的时候也是引入这个文件,引入Message.vue文件import Message from './Message.vue'
通过createApp新建一个vue实例,这里的message和type在Message.vue的props就可以接收
const messageInstance = createApp(Message, { message, type })
设置一个定时器,用来销毁实例
setTimeout(() => { messageInstance.unmount() // messageInstance.unmount(mountNode) document.body.removeChild(mountNode) }, timeout)
这样销毁的时候也就会触发useDOMCreate.ts里的生命周期监听事件
onUnmounted(() => { document.body.removeChild(node) })
vetur插件点击“扩展设置”,然后点击“在settings.json”中编辑,添加上
"vetur.experimental.templateInterpolationService": true
可以让vscode更好的去支持ts,补全代码