vue3项目--让我们更快开发vue(二)

5,870 阅读17分钟

前言

首先非常感谢大家对于第一篇文章的支持,在本文的最后会对一些使用的关键再做详细说明,在这里先将带来我们第二篇文章vue3项目实践(源码在最后面哦),对于没有看过第一篇的大佬我们还是放上文章整体介绍

本文为一个连续的文章,分为以下几个阶段,每个阶段都会出一篇文章(本篇为第二节):
本篇内容文字性很多,需要一定时间来进行理解和消化,主要是对于设计模式和代码结构这种逻辑性的内容进行分析(虽然我的不一定对,但是我觉得在特定项目中会很有用)

Vue3快速上手

可能大家都已经看过无数关于vue3方面的文章了,在这个地方我需要说的就有点不一样了,我就说说我在使用时候的一些注意点和问题

工欲善其事必先利其器,如果对于vue3还不是很了解的大佬可以主要了解两个东西:

  • 什么是composition-api,有什么魔力让vue进行了转变
  • Vue3的api有哪些不一样,点我打开api

在了解完成后,接下来就介绍一下我对于一些新写法的认识:

setup

简单粗暴的一个函数把所有东西都包含了,让我们开始了我们的composition-api,那么我们需要注意的地方有哪些呢?

  • 首先作为一个函数,vue获取数据或方法都需要写到return里面。
  • 写在函数内部的东西都相当于以前的create生命周期中
  • 在函数内部我们有自己的常量等,那么响应的数据就需要进行reactive或者ref化(在vue3中使用都要进行手动引入)
  • 在函数内部无法像vue2使用this(为undefind),那么我们使用额外的参数就不能直接this.XXX
  • 生命周期使用方式的改变具体查看api(可以写多个按顺序执行符合eventloop规则)

watch和watchEffect

在vue3中监听就给了我们两个选择了,那么他们之间有啥不一样呢?

watch:

  • 所watch对象必须是:A watch source can only be a getter/effect function, a ref, a reactive object, or an array of these types(官方报错提示),所以我们想直接watch某个数据那是不得行的。
  • 监听前后数据的变化
  • 初始化执行与否可以进行设定

watchEffect:

  • 自动收集内部响应数据变化执行
  • 初始化就会执行一次
  • 收集依赖后进行effect触发,注意逻辑递归,内部值变化则会继续触发

这样看起来watchEffect很好用的样子,不用管触发参数。其实在使用的时候特别想能够排除触发参数,或者没注意怎么一次性触发了这么多次,再或者直接逻辑混乱蒙蔽。

所以如果是比较复杂的函数我推荐大家还是尽量使用watch来进行手动触发,防止一些鬼畜的问题。(但是watchEffect确实很方便)

ref和reactvie

两个都是计算属性,一个为对象一个为单值,那么使用的时候要避免哪些问题呢?

ref:

  • 被ref后的值会处理成对象形式,所以在setup中访问需要以xxxx.value的形式,但是在html代码模板中不需要!!!
  • 如果被ref的对象后绑定值reactive中,那么使用reative对象访问就不需要添加.value来进行访问
  • 温馨提示:如果脑抽的把ref值给直接等于赋值了后,如果在create中直接赋值的页面还是会正常显示!但是后期操作会失去响应,找bug巨难找怎么就失去响应了。
  • ref包含了refs的方法,具体实现是在定义一个同名的ref对象在dom和return中,vue会通过渲染后分配给同名值的对象索引

reactive:

  • 内部所有参数都会被添加至响应,但是可以对某个参数进行消除响应
  • 和ref一样的,千万别直接把reative后的对象给等于了,那么会丢失对象响应
  • 可能就有小伙伴想既然等于失去响应那么我直接再给reative不就行了吗?(你可真是‘小机灵鬼’)那可是不行的,应为你和页面模板绑定的是之前的对象啊,你给后面的换了对象但是页面的没换,所以显示还是之前的内容。

computed

在vue2中作为一个数据返回的方法,在vue3中返回的是一个ref对象了。我们也看到api上这样说:传入一个 getter 函数,返回一个默认不可手动修改的 ref 对象。就是我们在获取的时候会触发getter方法的一个ref对象,当然我们也可以这样去写这个计算属性

		const HOT_HTML = computed({
            get: () => store.state.themes.HOT_HTML,
            set: () => {
                store.commit('CHANGE_HOT_HTML');
            }
        });

可以看到我们可以自由的去操作它的方法,转念一想这不就是响应式原理么,重写get和set方法,岂不美滋滋。

props、emit

可能就会有小伙伴有疑问了,没有this了那么我的一些常用方法和传参参数怎么获取呢? 其实很简单就是改变了一下获取方式,我们在setup函数中执行的时候给我们传递了一些参数让我们可以使用,其中就有这些,常用的方式就如下面写的

setup(props, { emit }) {
...
}

实操一波代码

友情提示,一大波代码来袭

<script>
import { computed, reactive, ref } from 'vue';
import { useStore } from 'vuex';
import router from '@/router/index';
import MessageBox from '../../../components/popUps/MessageBox';
import Show from '../../../api/Show';
import SaoSelect from '../../../components/select/SaoSelect';

export default {
   name: 'Home',
   components: { SaoSelect, MessageBox },
   props: {
       scrollTop: {
           type: Number,
           default: null
       }
   },
   setup(props, { emit }) {
       const store = useStore();
       const messageDia = ref(null);

       /** *************************************************************************************************/
       /** ***************************************代码块控制***************************************************/
       /** *************************************************************************************************/
       const cartList = computed(() => store.state.cart.cartList);
       const showObj = reactive({
           showClose: false,
           list: []
       });

       // 获取基础数据
       function getAllComponentsWithHtml(classify) {
           Show.getAllComponentsWithHtml({
               classify: classify,
               pageSize: 6
           }).then((response) => {
               showObj.showClose = true;
               setTimeout(() => {
                   showObj.list = response.list;
                   showObj.showClose = false;
               }, 1200);
           });
       }

       getAllComponentsWithHtml();

       function showBigHtml(index) {
           const changeDom = document.getElementsByClassName('code-open-iframe')[index];
           if (showObj.list[index].showBig) {
               showObj.list[index].showBig = false;
               changeDom.style.position = '';
               changeDom.style.left = '';
               changeDom.style.top = '';
               changeDom.style.width = '';
               changeDom.style.height = '';
               changeDom.style.zIndex = '';
           } else {
               showObj.list[index].showBig = true;
               changeDom.style.position = 'fixed';
               changeDom.style.left = '0';
               changeDom.style.top = '0';
               changeDom.style.width = '100%';
               changeDom.style.height = '100%';
               changeDom.style.zIndex = '1';
           }
       }

       function changeRouter(id) {
           const openUrl = router.resolve({ path: `/Code/index`, query: { id: id }});
           window.open(openUrl.href, '_blank');
       }

       function addCart(item) {
           if (!item.cartMove) {
               item.cartMove = true;
               store.commit('changeCartList', item);
               setTimeout(() => {
                   item.cartMove = false;
               }, 900);
           } else {
               messageDia.value.showMessage('error', '请不要频繁操作哦');
           }
       }

       /** *************************************************************************************************/
       /** ***************************************搜索控制***************************************************/
       /** *************************************************************************************************/
       const selectObj = reactive({
           inputText: '',
           classifyList: [],
           chooseClassify: null
       });

       // 获取类别
       Show.getAllClassify().then((response) => {
           selectObj.classifyList = response.map((item) => ({
               value: item.id,
               name: item.name
           }));
           selectObj.classifyList.unshift({ value: null, name: '全部' });
       });

       /** *************************************************************************************************/
       /** ***************************************右侧功能***************************************************/
       /** *************************************************************************************************/

       function openCartDia() {
           store.commit('changeCartDiaShow');
       }

       function backToTop() {
           emit('backtop');
       }

       function gotoSearch(params) {
           emit('backtop');
           router.push({ path: '/Show/list', query: params });
       }

       return {
           selectObj,
           showObj,
           messageDia,
           cartList,
           showBigHtml,
           changeRouter,
           addCart,
           getAllComponentsWithHtml,
           openCartDia,
           backToTop,
           gotoSearch
       };
   }
};
</script>

这段接近两百行的代码就是我们看到的首页了,大家可以看看和平时我们写的vue有哪些不一样呢?

  • 一大波需要import的东西,如果不熟悉还是要查阅一下
  • 既然代码可以符合composition-api那么我们的注释是不是也可以这样按模块来区分
  • 感觉return的东西有点杂乱,并且webstorm不能通过在模板点击直接访问方法,而是访问到return的这个地方有点不爽
  • 获取ref的dom有那么一点点麻烦
  • teleport标签!可以直接把内部模板代码添加至指定的dom中,贼好用!(这里的代码没有)
这些大概就是我对于vue2跨度到vue3的一些平时使用的小总结了。当然我觉得这些具体使用都是口头说说罢了,实际上去使用的时候才能够真实感受到区别,在遇到问题的时候才会深刻的认识到这些差距。
那么接下来就进行介绍整个项目的内容了

整体项目架构

一个好的项目结构可以让我们项目中的各个功能模块分明,甚至是能够了解到一部分代码内容,我觉得这是一个需要进行深入讨论的问题,在这里我只是说说我对于这个项目的结构认识。

文件基础结构

对于文件结构其实就和大部分的vue项目采用的一样,主体为以下结构

-public
	-ace(编辑器)
   -styles(重置和基础reset央视)
   -iconfont
	-index.html
-src
	-api(请求接口)
   -assets(静态文件)
   -components(公共组件)
   -decorators(装饰器)
   -router(路由)
   -store(vuex)
   -types(.d.ts文件)
   -utils(公共配置和工具栏)
   -views(页面)
   -App.vue(入口vue)
   -main.ts(主入口文件)
-tests
...
  • decorators文件夹后面会对一些内容进行详细解释
  • 项目中所有配置采用ts书写,所有vue文件使用正常js(即项目基础用ts,具体业务采用js快速开发)
  • public中的内容为不需要被进行打包的内容,由于有些三方插件打包后会有问题,所以采取这种方式进行引入

样式设计结构

其实不知道大家体验过没,在编辑器中整体页面是可以支持换肤的,对于一般的换肤而言我觉得设计比较多的情况是进行css引入文件的切换带来的样式切换,但是我觉得这样做对于写的时候的效率和快速新增主题都不是特别友好,这个时候就可以利用我们函数式样式来进行一定的改造。

废话不如直接看一看,来看看截取一个样式函数的代码:

// 颜色函数
.mixin-font-color (@name) {
  each(@themeList, {
    @fc: ~'@{name}-@{value}';
    [data-theme='@{value}'] & {
      color: @@fc;
    }
  });
}

这样做的最大意义在于,我们使用样式的时候写的不是具体的颜色样式,而已一个代理的函数,然后通过样式名称的拼接来实现真正的样式,从而实现写一遍就可以造出无数的主题样式。整理流程如下图:

接口设计架构

接口设计关系到了装饰器,所以先行上一个整体流程图来讲解思路会比较方便

看起来是不是就是一条线,比我们平时的书写多了一步axios dec装饰器而已,那么这个装饰器干了什么呢?让我们来看看其中一个装饰器的内容:

static Post(url: string): PropertyDecorator {
       return (target: any, propertyKey: string | symbol): void => {
           target[propertyKey] = (params: any, config?: object) => Request.post(url, params, config);
       };
   }

这就是一个简单的重构原方法去进行请求而已,这样做我们的api文件可以写的非常非常简单,就像这样:

export default class Code {
   @AxiosDec.Post('/code/codeOnline/setHtml')
   static setHtml: any

   @AxiosDec.Post('/code/CodeControl/getTemplate')
   static getTemplate: any

   @AxiosDec.Post('/code/CodeControl/saveHtmlTemplate')
   static saveTemplate: any

   @AxiosDec.Post('/code/CodeControl/saveCodeTemplate')
   static saveCodeTemplate: any

   @AxiosDec.Post('/code/CodeControl/delectCodeById')
   static delectCodeById: any

   @AxiosDec.Post('/code/CodeControl/getAllComponents')
   static getAllComponents: any
}

当然我个人看起来是觉得很舒服的,其实整体没有多大用

组件设计架构

在这需要进行说明,这个架构只是针对于有值进行绑定的组件进行详解,就是我们作为ui开发的基本组件,因为项目本身是没有使用任何三方的内容,所以本身内部就有一套组件架构

一、校验结构

大家在做项目的时候是否有遇到过非表单只是一个简单的输入框需要做输入校验的情况,这种时候我们的很多框架由于采用的provide和inject注入到表单校验的方式,没有直接提供给我们接口进行校验那我们无法进行快速的对于单输入框进行校验。针对这个问题我简单设计了一下校验的整体流程逻辑(其实感觉很像angular的校验模式)。

整体逻辑为以下结构,由于整体实现内容过多就不放在架构这一节来进行梳理,放在后面专门进行介绍重要功能中。

二、Promise弹窗

我们在进行一些表单提交的时候经常会遇到需要打开一个弹窗然后获取信息后继续操作的情况,我们常用的方式就是利用组件传递的值控制弹窗显示,完成后触发emit然后继续方法。

但是大家想想整个流程其实和我们的Promise是一样的啊,进行操作完成后触发resolve继续接下来的代码,那么我们可不可以把弹窗当作一个大型的Promise来进行封装呢?并且内部内容自定义用solt传参来完成高度自由化。

整体流程为上图所示,具体实现方式其实就是利用Promise的特性将resolve和reject方法抛给外部方法执行。

  // 构建弹窗Promise对象
  function diaPromise() {
          DiaShow.value = true;
          return new Promise((resolve, reject) => {
              stepPromise.resolve = resolve;
              stepPromise.reject = reject;
          });
      }

主要的一些结构介绍可能到这就差不多了,很多没有介绍的地方其实就是正常的开发流程和架构了。

具体实现

在线编译功能

大家之前是否了解过一些在线编辑的vue是怎么实现的呢?我觉得主要实现方式是通过两个方面:

  • 通过使用vue的$mount来进行代码的在线编辑和挂载,这样是纯前端做法
  • 通过将代码传递给后端使用iframe来进行实现,展示内容块都是iframe网页

然而对于一个网站项目来说,特别是这种需要后期加功能的网站,操作经过后端无疑是有非常多的好处的:

  • 能够进行历史记录,记录历史代码
  • 实现记忆功能
  • 统计代码模块
  • 代码高度自定义处理
  • 独立的网页环境,无变量影响
  • 用户操作记录

那么本网站具体是怎么实现的呢?还是老方法上流程图: 在这里我们之说前端逻辑,后端留着下一章来进行讲解。其实我们就是做了接口请求后替换iframe地址参数而已,这样就可以很轻松的生成我们的在线页面了。网站的所有组件页面都是利用这种方式构成的,预览也是一个个iframe,所以所有组件都可以完美触发它的时间来进行预览。

npm发布

在写这一小节的时候我是比较犹豫的,因为主体基本上为后端,但是作为网站的一个最重要功能,还是需要讲解一下前端干了什么

在前端方面,发布的最终接口其实只有两个参数,一个是发布npm包的id另一个为发布的版本号(版本号格式为x.x.x大家不要乱发布哦)

致命问题:现阶段还未完成发布包状态查询和结果查询,后期一定会解决该问题。并且现阶段发布包都在本人的空间中,后端其实已经支持各位通过各自的npm账号来进行发布,后期会进行完善,下面上一张现在空间内包的发布情况图

中间的两个包为之前的文章,关于我是怎么在git提交时既偷懒又规范(基于nodejs)的公司内部的git自动化提交,mr请求,现在新增了发版日志自动化生成,如果有感兴趣的同学可以私聊我。

组件输入值校验

本小节逻辑较为复杂,推荐心情好的时候慢慢观看

这一小节其实就是紧接着上面组件结构-校验结构小节的内容

首先我们接着上面的内容介绍了整体的校验流程,那么我是怎么去实现整个流程的呢?倒着来一步步看我做了啥:

在组件中

const { inputObject } = new InputTools(props, emit, input); //对象绑定

是的,整个组件中的内容基本上就这一句话完成了。

在类型公共组件中

import { BindingObj } from '@/types/validation';
import ValidaDue, { RefDom, ValidaPorops } from '@/components/utils/ValidaDue';

export default class InputTools extends ValidaDue {
    inputObject: BindingObj

    /**
     * 构造函数
     * @param {ValidaPorops} props 传入传递对象
     * @param {Function} emit 回调函数
     * @param {Element} dom 传入监听dom
     */
    constructor(props: ValidaPorops, emit: Function, dom: RefDom) {

        super(props, emit, dom);

        this.inputObject = this.ValidaObject;
    }

}

也是简简单单的一个构造函数就完成了,采用了继承的思想,基本上的内容都是通过继承而来的,这样做的好处不用多说了吧,便于拓展同时也减少了代码量,是一个代码耦合和解耦比较好的办法。

那么再下一层呢?

在公共校验中

export default class ValidaDue {
    value: any
    ValidaObject: BindingObj

    @ValidateDec.validationFn
    private static validation: Function

    @ValidateDec.resetStatus
    private static resetStatus: Function

    @ValidateDec.registerTrigger
    private static registerTrigger: Function

    /**
     * 构造函数
     * @param {ValidaPorops} props 传入传递对象
     * @param {Function} emit 回调函数
     * @param {Element} dom 传入监听dom
     */
    constructor(props: ValidaPorops, emit: Function, dom: RefDom) {
        this.value = ref(props.modelValue);

        emit('update:modelValue', this.value);

        this.ValidaObject = reactive({
            value: this.value,
            rules: props.rules,
            check: true,
            errorMsg: ''
        });

        this.ValidaObject.validation = ValidaDue.validation(this.ValidaObject);

        this.ValidaObject.resetStatus = ValidaDue.resetStatus(this.ValidaObject);

        onMounted(() => {
            if (this.ValidaObject.validation) ValidaDue.registerTrigger(dom.value, this.ValidaObject);
        });

        const obj: any = inject('form', null);

        obj?.push(this.ValidaObject);
    }

}

代码量一下多了一点,也仅仅是一点,我们在这里面主要是构造了一个ValidaObject对象用来返回,这就是我们的返回对象了,里面包含这值的绑定校验的信息结果和事件等。同时在这我们也同样的使用了inject来进行了上下组件的传递,具体这个东西在哪用的熟悉element源码的同学可能会猜测在form中,其实就是一样的在form中进行了provide的接受。

校验装饰器

这就是本条链的终点了,由于代码量稍微多了一点我就分开一个个方法进行梳理讲解,首先上流程图: 主要方法很明显为主构建方法,通过构建数组后进行注册事件。那么我们倒着来分析每个功能

resetStatus(重置方法)
/**
     * 重置参数为默认状态
     * @param {*} target des改变源
     * @param {String} propertyKey key值
     * @returns {void}
     */
    static resetStatus(target: any, propertyKey: string | symbol): void {
        target[propertyKey] = (that: BindingObj): Function => {
            const setValue = that.value;
            
            function resetThat() {
                that.value = setValue;
                that.check = true;
                that.validation && that.validation.checkList.forEach((item: Validated) => {
                    item.check = true;
                });
            }
            
            return resetThat;
        };
    }

重置方法为一个外部暴露方法可以直接通过对象调用

由于我们使用的是通过装饰器的方式来构建的,所以写成了这种模式,具体执行为中间的resetThat,通过保存初始状态的值来进行值的返回并且重置状态为校验状态true的状态(包括下级各个规则)

registerTrigger(注册事件)
 /**
     * 注册监听事件
     * @param {*} target des改变源
     * @param {String} propertyKey key值
     * @returns {void}
     */
    static registerTrigger(target: any, propertyKey: string | symbol): void {
        target[propertyKey] = (dom: Element, that: BindingObj): void => {
            const getAllTrigger: string[] = [];

            that.validation.checkList.forEach((item: Validated) => {
                if (item.trigger && item.trigger.length > 0) {
                    item.trigger.forEach((name: string) => {
                        if (getAllTrigger.indexOf(name) === -1) getAllTrigger.push(name);
                    });
                }
            });

            that.rules?.trigger?.forEach((name: string) => {
                if (getAllTrigger.indexOf(name) === -1) getAllTrigger.push(name);
            });

            getAllTrigger.forEach((name: string) => {
                dom.addEventListener(name, () => {
                    that.validation(name);
                });
            });
        };
    }

在这里就需要开始补充设定方面的内容了,可以看到代码模块上面我进行了两个方面的处理

  • 第一个是对于checkList中的事件进行了挨个注册
  • 第二个是对于rules数组中含有trigger的对象进行了注册

这里就需要看一下整个rules对象的d.ts文件了

// 校验规则对象
export interface BindingObj {
    value: any;
    rules?: ValidaRule;
    check: boolean;
    errorMsg: string;
    validation?: any;
    resetStatus?: Function | any;
}

export interface ValidaRule {
    validate?: (string | ValidateS)[];
    message?: string;
    trigger?: string[];
}

// 输入
export interface ValidateS {
    validateName: string;
    message?: string;
    trigger?: string[];
}

export interface Validated {
    validateName: string;
    check: boolean;
    message?: string;
    trigger: string[];
    backMessage?: string;
}

看到这的都是真正的勇士,别说你们我的头都痛了,简单来说上面那一串规则就是这样一个对象

{
  validate: [
    {
      validateName: 'required', // 校验规则
      trigger: ['input'] // 触发事件
    },
    'HtmlTag' // 校验规则
  ],
  trigger: ['blur'] // 公共触发事件
}

所以这就可以明白为什么上面进行了两次注册事件了,为了满足公共触发和单个触发

喝口水冷静冷静,这什么玩意

runValidFn(执行校验方法)
/**
     * 校验执行
     * @param {Validated} validated 执行对象
     * @param {Object} that 传递对象
     * @returns {{check: boolean}} 返回处理对象
     */
    private static async runValidFn(validated: Validated, that: BindingObj): Promise<boolean> {
        let check;

        try {
            check = await (ValidateRule as any)[validated.validateName](that.value);
        } catch (e) {
            throw Error.ValidateRule;
        }

        try {
            validated.backMessage = validated.message ? validated.message : (DefaultMsg as any)[validated.validateName][check];
        } catch (e) {
            throw Error.DefaultMsg;
        }

        return check;
    }

直接上代码的原因是因为这个是一个private方法,不可外部调用的哦。先行说明后来看看这个方法干了啥:

  • emmm,第一步执行了正则校验
  • 第二部执行了校验后的提示文字赋值
  • 第三步返回校验结果

哇是不是最简单的一个方法,继续下去

dealValidates(执行数组处理)
/**
     * 执行数组处理
     * @param {Array} validates 校验规则
     * @param {Array} trigger 基础触发事件
     * @returns {Validated[]} 返回数组
     */
    private static dealValidates(validates: (string | ValidateS)[], trigger: string[]) {
        const FnList: Validated[] = [];

        validates.forEach((item: string | ValidateS) => {
            if (typeof item === 'string') {
                FnList.push({ validateName: item, check: true, trigger: trigger });
            } else {
                FnList.push({ validateName: item.validateName, check: true, trigger: item.trigger ? item.trigger : trigger, message: item.message });
            }
        });

        return FnList;
    }

同样作为一个private方法也是非常的简单,就是循环处理了校验的规则,生成了我们需要的对象数组罢了。

validationFn(主构建方法)

最后我们可以来看看我们的主构建方法了:

/**
     * 构建正则校验方法
     * @param {Object} target 执行class
     * @param {String} propertyKey 赋值的key
     * @returns {void}
     */
    static validationFn(target: any, propertyKey: string | symbol): void {
        target[propertyKey] = (that: any): Function | undefined => {
            if (!that.rules) return;

            const checkList = ValidateDec.dealValidates(that.rules.validate, that.rules.trigger);

            const backFn = async function(name: string, isCheckAll: boolean) {
                let check = true;
                let otherMessage = '';

                for (let i = 0; i < checkList.length; i++) {
                    const item: Validated = checkList[i];
                    if (item.trigger.indexOf(name) === -1 && item.check && !isCheckAll) continue;
                    item.check = await ValidateDec.runValidFn(item, that);
                    if (!item.check) check = false;
                    if (!item.check && item.backMessage) otherMessage = otherMessage ? `${otherMessage}${item.backMessage}` : item.backMessage;
                }

                that.errorMsg = `${that.rules.message ? that.rules.message : '输入有误'}${otherMessage ? ':' + otherMessage : ''}`;
                that.check = check;
            };

            backFn.checkList = checkList;

            return backFn;
        };
    }
  • 首先这是一个构造方法,所以有重写的基本模板
  • 第一步我们把rules构建成了我们需要的对象数组
  • 然后就是方法内部,我们对刚才生成的数组进行了循环
  • 对每一个需要执行的规则进行了执行后返回结构
  • 最终生成返回状态和返回信息

当我们一步步把每个方法梳理完成后,最终的方法其实也并不难,就是一个由浅入深的过程罢了。

form表单

是不是都以为结束了,刚才我们提到过的表单校验还没出现呢。这里的校验和element不太一样,只是针对于功能的集合,并没有样式的规定限制。

由于这种类似的在别的框架也很多了,我们就简单看一下setup的内容吧:

setup() {

        /** *************************************************************************************************/
        /** ***************************************注册provide***************************************************/
        /** *************************************************************************************************/
        const form = reactive([]);

        provide('form', form);

        /** *************************************************************************************************/
        /** ***************************************回调方法***************************************************/
        /** *************************************************************************************************/
        const TypeFormApi = reactive({
            // 重置表单
            resetForm() {
                nextTick(() => {
                    form.forEach((item) => {
                        item.resetStatus && item.resetStatus();
                    });
                });
            },
            // 提交表单前的校验
            async validate() {
                const backList = [];
                for (let i = 0; i < form.length; i++) {
                    form[i].validation && await form[i].validation('', true);
                    if (!form[i].check) backList.push(form[i]);
                }
                return backList;
            }
        });

        return {
            form,
            TypeFormApi
        };
    }

就是利用provide获取参数,然后进行每个参数的挨个校验,最终结果通过返回校验数组来实现,整理来说逻辑不算太复杂。

对于各位该项目意义

本项目从开始是4月份到现在9月份已经过了快半年的时间了,对于项目未来的发展和结果作为个人开发者来说是完全不可预见的。
但我觉得这其实也是近期我看到使用vue3开发的完整且较大的项目了(官方都推荐暂时不在正式项目使用),所以我觉得这个项目至少也可以作为大家后期使用vue3进行开发的一个模板来利用。内部其实也有很多组件了,并且新建组件基本上不需要写太多的逻辑只需要进行样式的修改即可。
其次,作为一个初学者也是一个很好的提升,去详细了解项目中各个内容的详细配置,以及进行思考为什么这个要这样配置我觉得是非常好的学习途径(本人就是通过自己配置然后看别人配置开始学习的vue)

当然了,如果有个人或者公司想要使用这个项目我是非常欢迎的,我也可以帮大家进行一定程度的修改,这个东西作为一个内部的交流平台还是有点意义的(平台化、社区化个人力量无法支撑),所以非常欢迎大家来进行联系我。

然后就是作为一个vue3踩坑项目来说内部的配置也进行了集合,大家也可以直接进行使用,把这个项目作为改版的自己项目也是可以的,现有的配置主要有:

  • api接口设计
  • axios封装
  • 几个常用的directives
  • 数值绑定的组件校验(校验规则)
  • 主题化的多样式快速生成以及切换

项目总结

除了上诉介绍的一个设计模式和规则其实在写的时候也有其他的问题和小坑,在这如果一一讲解的化就显得文章过于繁琐了,所以感兴趣的同学可以直接去下载源码来进行查看。

在这也对一些使用的问题进行解答,在我们发布npm包版本的时候格式应该为x.x.x的数字,所以不熟悉npm包的同学记得注意哦,大家也可以放心进行npm包的发布和操作,这边会对每天的数据进行自动化备份的。也可以进行尝试安装npm包来进行测试这个包是否真的可以使用,具体使用方法可以看看每个组件的index.vue文件怎么用的。

bug时刻

文章到这里基本上就算结束了,那么这个章节我们就来看看vue有哪些小bug和我的经历放松一下吧

无效的proxy

不知道是不是自己的原因,在我对vue3项目进行vue.config.js中设置proxy代理的时候没有效果!

'proxy': {
            '/': {
                target: 'http://36.111.183.168:9527',
                changeOrigin: true
            }
        }

就像这样完全没有效果,如果有小伙伴知道怎么回事欢迎留言哦

不智能的router

我们在对于vue2项目进行打包后并不需要去知道最终网站的目录结构是怎么样的,比如:http://36.111.183.168:9527/assemble/#/Show/index 这个地址中,我并不需要知道我这个页面是从assemble路径中进行访问的,但是在vue3中就不行了,正常不做处理的化会变成所以文件请求都变成 http://36.111.183.168:9527/*.css 之类的,那肯定不行啊访问不到该怎么办呢?

const router: Router = createRouter({
    history: createWebHashHistory(window.location.pathname),
    routes
});

自己去规定一下访问的路由,这也太不智能了吧,一个小语句也能实现这种功能的吧,

然后我就提了一个issue:github.com/vuejs/vue-r… 然后得到的回答是:

Please open the issue in English and with a boiled down reproduction(请以英文开本,并抄录简要)

这。。。好的我改大哥,新issue来:github.com/vuejs/vue-r… (百度翻译就是好用)

然后经过两轮重申问题后得到这样的结果,

next time provide a repro using the starter at new-issue.vuejs.org/?repo=vuejs…. I had a very hard time understanding the bug you were reporting (下次使用*上的入门版提供复制。我很难理解您报告的错误)

我。。。。知道这位大哥提出问题github.com/vuejs/vue-r… 后据说是得到了解决,但是我之前更新了vue-router-next包还是存在这个问题 : ) ,所以大家使用的时候可以注意一下这个东西。(我的百度翻译就这么不靠谱吗)

下章预告

下一章:Vue3.0一个不算小的项目实践(三)(文章完成后会将该标题换为链接)

主要内容:我是如何使用springMVC模式来写一个node后端的(不一定很好,但是本人觉得这也算一个思路去靠近面对对象编程)

预告代码:

@routerDec.BaseRequest('/template/templateControl')
export class TemplateControl {
	private static TemplateService: TemplateService = new TemplateService()

	/**
	 * 通过id获取模板数据
	 * @route POST /template/templateControl/getTemplateById
	 * @group 第三方包管理
	 * @param {string} ids.formData 搜索名称
	 * @returns {Promise} 200 - 返回查询结果
	 * @returns {Promise} 500 - 返回错误原因
	 */
	@routerDec.RequestMapping('/getTemplateById', MyType.post)
	async getTemplateById(
		@routerDec.RequestParams('String', 'ids') ids: string,
		@routerDec.Response() res: express.Response
	): Promise<void> {
	    const response = new BaseResponse<Template[]>();

	    try {
	        if (ids) {
	            response._datas = await TemplateControl.TemplateService.getTemplateByIds(ids);
	        }
	        response.changeType(BackType.success);
	    } catch (e) {
	        response._msg = BaseErrorMsg.sqlError;
	    }

	    res.json(response);
	}

	/**
	 * 查询模板名称
	 * @route POST /template/templateControl/getTemplateName
	 * @group 第三方包管理
	 * @param {string} ids.formData 搜索名称
	 * @returns {Promise} 200 - 返回查询结果
	 * @returns {Promise} 500 - 返回错误原因
	 */
	@routerDec.RequestMapping('/getTemplateName', MyType.post)
	async getTemplateName(
		@routerDec.Response() res: express.Response
	): Promise<void> {
	    const response = new BaseResponse<TemplateName[]>();

	    try {
	    	response._datas = await TemplateControl.TemplateService.getTemplateName();
	        response.changeType(BackType.success);
	    } catch (e) {
	        response._msg = BaseErrorMsg.sqlError;
	    }

	    res.json(response);
	}

}

emmm..浓浓的java控制器风格,味太冲了

源码

由于本人网撇,访问github难受,所以选择的是gitee码云,所以后面的操作包括WebHooks操作都是在码云上进行完成的。欢迎大家下载源码给一个小星星哦

如果希望运行的话可以切换到run分支,可以直接访问线上的接口环境

源码地址: gitee.com/beon/vue-as…

运行命令: npm run serve

结尾

这就是本文全部内容了,如果有任何问题或者想让我帮忙进行开发欢迎进行评论的私聊我,下面贴上本人微信二维码。