Vue3门户项目 | 开发过程记录

项目构建基础环境:
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等简单的类型,该属性一旦发生更改,都会被检测到。


import {ref} from "vue"
setup(){
   const count =ref(0)
   count.value++    //必须要加.value
   return{
       count        //一定要return 出去
   }
}

reactive 声明响应式数据对象


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,补全代码

你可能感兴趣的:(Vue3门户项目 | 开发过程记录)