热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

基于Vue3封装一个Pagination公共组件

基于Vue3封装一个Pagination公共组件-起因最近有幸参与了我们部门基于vue3的公共组件库的开发,记录一下vue3实际使用的过程的一些经验,以及开发公共组件注意的一些问
起因

最近有幸参与了我们部门基于 vue3 的公共组件库的开发,记录一下 vue3 实际使用的过程的一些经验,以及开发公共组件注意的一些问题。

要实现的功能

属性

事件

实现后的效果

理论开发流程

我们采用的是测试驱动开发(TDD)开发的流程为

  1. 写对应功能点的文档
  2. 写对应功能点的单元测试
  3. 跑测试案例,确保案例失败
  4. 写代码实现
  5. 跑测试案例,确保案例成功
实际开发过程

开发之前需要掌握的知识点

  1. Vue3 的新特性
  2. 用 jest 测试,@vue/test-utils: 2.0.0-rc.6 测试 Vue 组件
  3. jsx 语法

组织结构

项目组织结构

组织结构参考 vitepress 文档

写对应功能点的文档

主要根据设计师给出的 UI 效果图,再结合其他优秀的 UI 库中的功能点,最后讨论得到我们需要实现的效果,最后写文档。

测试用例编写

测试覆盖率的4个指标

行覆盖率(line coverage):每个可执行代码行是否都执行了?
函数覆盖率(function coverage):每个函数是否都调用了?
分支覆盖率(branch coverage):每个流程控制的各个分支是否都执行了?
语句覆盖率(statement coverage):每个语句是否都执行了?

如何编写测试用例

  1. 测试驱动开发要求测试用例编写优先于单元功能的实现,这就需要先去思考组件该如何取拆分,拆分以后每个部分需要哪些功能。
  2. 对这些想象中的功能进行测试。但是在实际开发过程中发现功能没有开发出来之前编写测试用例很难去做到一个比较高的一个测试覆盖率,只好在功能开发完成以后对测试进行一个补充。

    pagination 组件的结构

    如下是我在编写功能之前给给的组织结构,功能实现在 jumper、pager、pagination、simpler、sizes、total 等 jsx 文件

  • tests
    • pagination.js
  • style
    • index.js
    • index.less
    • mixin.less
  • const.js
  • index.js
  • jumper.jsx
  • pager.jsx
  • pagination.jsx
  • simpler.jxs
  • sizes.jsx
  • total.jsx
    
    #### 对 jumper 功能编写测试用例举例
    其他部分的测试也类似
  1. ShowQuickJumper 树形为 true 的时候 jumper 相关功能才会展示,而判断 jumper 是否展示可以通过渲染后的组件是否拥有的的特殊的 jumper 相关 class 来实现,@vue/test-utils 里面的 classes api 很好的实现这样一个效果。
  2. 当 jumper 输入的值不为数字、数字超出边界、数字为 NaN 等问题的一个测试。
  3. 功能测试,当输入完成,失去焦点的时候是否可以跳转到相应的页面。

达到的测试覆盖率

功能完成之前

测试都不能通过

功能完成之后

测试覆盖率不足 70%,可惜忘了截图

测试用例补充之后

如下图,最终测试覆盖率是100%

关于测试的总结

追求 100% 的测试覆盖率是否有必要呢?

非常有必要,因为在追求 100% 的测试覆盖率的时候我回去审查每一行代码,看实际过程中是否每一行代码都可以覆盖到,而在这个过程中发现了一些冗余代码,比如说我已经在 jumper.jsx 里面已经对回传 pagination.jsx 中的数据进行了一个处理,确保里面不会产生非数字,且不会超出边界,而又在 pagination.jsx 中处理了一遍, 这样导致 pagination 里面的处理变得多余,而永远不会执行。因为每一行,每一个分支都可以执行到,这样也可以有效的减少潜在的 bug。

功能实现过程遇到的问题

样式规范

  1. 需要兼容切换风格、后期可能会调整字体颜色、按钮大小、按钮之间的距离等,开发的时候所有颜色、常规的一些距离等等都需要再公共文件里面取。这样每一个小的样式都需要思考是否必须,是否需要在库里取,开发过程可能会比直接写颜色等要慢一些。
  2. 所以可能带有的 class 都要有 is 属性,is-disabled、is-double-jumper 啥的
  3. 尽量不用元素选择器,一个是效率低,一个是变更时候容易照成必须要的影响

    js 规范

  4. jsx 里面使用 bind 绑定事件时候,如果里面的第一个参数没有意义,直接设为 null 即可,handleXxx.bind(null, aaa)
  5. jsx 语句尽量格式化,简短一点
    
    // setup 里面
    // 不好的写法
    return (
     
    )

    3. 功能的功能尽量封装成 hooks, 比如实现 v-model
    ## vue3 新特性的实际使用
    ### setup 参数
    setup 函数接受两个参数,一个是 props, 一个是 context
    #### props 参数
    不能用 es6 解构,解构出来的数据会失去响应式,一般会使用 toRef 或者 toRefs 去处理
    #### context 参数
    该参数包含 attrs、slot、emit,如果里面需要使用到某个 emit,则要在 emits 配置中声明

    import { definedComponent } from 'vue';
    export default definedComponent({
    emits: ['update:currentPage'],
    setup(props, { emit, slot, attrs }) {
    emit('update:currentPage');
    ...
    }
    })

    ### v-model 使用
    pageSize 和 currentPage 两个属性都需要实现 v-model。
    #### vue2 实现双向绑定的方式
    
    [vue 2 实现自定义组件一到多个v-model双向数据绑定的方法](https://blog.csdn.net/Dobility/article/details/110147985)
    
    #### vue3 实现双向绑定的方式
    
    ##### 实现单个数据的绑定
    假如只需要实现 currentPage 双向绑定, vue3 默认绑定的是 modelValue 这个属性

    // 父组件使用时候

    // 相当于

    // Pagination 组件
    import { defineComponent } from vue;
    export default defineComponent({
    props: {
    currentPage: {
    type: Number,
    default: 1
    }
    }
    emits: ['update:currentPage'],
    setup(props, { emit }) {
    当 Pagaintion 组件改变的时候,去触发 update:currentPage 就行
    emit('update:currentPage', newCurrentPage)
    }
    })

    
    ##### 实现多个数据的绑定
    
    pageSize 和 currentPage 两个属性都需要实现 v-model

    // 父组件使用时候

    // Pagination 组件
    import { defineComponent } from vue;
    export default defineComponent({
    pageSize: {
    type: Number,
    default: 10,
    },
    currentPage: {
    type: Number,
    default: 1,
    },
    emits: ['update:currentPage', 'update:pageSize'],
    setup(props, { emit }) {
    当 Pagaintion 组件改变的时候,去触发相应的事件就行
    emit('update:currentPage', newCurrentPage)
    emit('update:pageSize', newPageSize)
    }
    })

    #### Vue3 和 Vue2 v-model 比较
    对于绑定单个属性来说感觉方便程度区别不大,而绑定多个值可以明显感觉 Vue3 的好处,不需要使用 sync 加 computed 里面去触发这种怪异的写法,直接用 v-model 统一,更加简便和易于维护。 
    
    ### reactive ref toRef toRefs 的实际使用
    #### 用 reactive 为对象添加响应式
    实际组件中未使用,这里举例说明

    import { reactive } from 'vue';

    // setup 中
    const data = reactive({
    count: 0
    })
    console.log(data.count); // 0

    
    #### 用 ref 给值类型数据添加响应式
    jumper 文件中用来给用户输入的数据添加响应式
    

    import {
    defineComponent, ref,
    } from 'vue';
    export default defineComponent({
    setup(props) {
    const current = ref('');
    return () => (

    );
    },
    });

    当然,其实用 ref 给对象添加响应式也是可以的,但是每次使用的时候都需要带上一个value, 比如,变成 data.value.count 这样使用, 可以 ref 返回的数据是一个带有 value 属性的对象,本质是数据的拷贝,已经和原始数据没有关系,改变 ref 返回的值,也不再影响原始数据
    

    import { ref } from 'vue';

    // setup 中
    const data = ref({
    count: 0
    })
    console.log(data.value.count); // 0

    
    ### toRef
    1. toRef 用于为源响应式对象上的属性新建一个 **ref**,从而保持对其源对象属性的响应式连接。接收两个参数:源响应式对象和属性名,返回一个 ref 数据,本质上是值的引用,改变了原始值也会改变
    2. 实际组件并未使用,下面是举例说明
    

    import { toRef } from 'vue';
    export default {
    props: {
    totalCount: {
    type: number,
    default: 0
    }
    },
    setup(props) {
    const myTotalCount = toRef(props, totalCount);
    console.log(myTotalCount.value);
    }
    }

    
    ### toRefs
    toRefs 用于将响应式对象转换为结果对象,其中结果对象的每个属性都是指向原始对象相应属性的 ref。常用于es6的解构赋值操作,因为在对一个响应式对象直接解构时解构后的数据将不再有响应式,而使用 toRefs 可以方便解决这一问题。本质上是值的引用,改变了原始值也会改变
    

    // setup 中
    const {
    small, pageSizeOption, totalCount, simple, showSizeChanger, showQuickJumper, showTotal,
    } = toRefs(props);

    // 这样就可以把里面所有的 props '解构'出来
    console.log(small.value)

    由于 props 是不能用 es6 解构的,所以必须用 toRefs 处理
    #### 四种给数据添加响应式的区别
    ##### 是否是原始值的引用
    reactive、toRef、toRefs 处理返回的对象是原始对象的引用,响应式对象改变,原始对象也会改变,ref 则是原始对象的拷贝,和原始对象已经没有关系。
    ##### 如何取值
    ref、toRef、toRefs 返回的响应式对象都需要加上 value, 而 reactive 是不需要的
    ##### 作用在什么数据上
    reactive 为普通对象;ref 值类型数据;toRef 响应式对象,目的为取出某个属性; toRefs 解构响应式对象;
    
    ### 用 vue3 hooks 代替 mixins
    如果想要复用是一个功能,vue2 可能会采用 mixins 的方法,mixins 有一个坏处,来源混乱,就是有多个 mixin 的时候,使用时不知道方法来源于哪一个 mixins。而 Vue3 hooks 可以很好解决这一个问题。我们把 v-model 写成一个 hook

    const useModel = (
    props,
    emit,
    cOnfig= {
    prop: 'modelValue',
    isEqual: false,
    },
    ) => {
    const usingProp = config?.prop ?? 'modelValue';
    const currentValue = ref(props[usingProp]);
    const updateCurrentValue = (value) => {
    if (
    value === currentValue.value
    || (config.isEqual && isEqual(value, currentValue.value))
    ) { return; }
    currentValue.value = value;
    };
    watch(currentValue, () => {
    emit(update:${usingProp}, currentValue.value);
    });
    watch(
    () => props[usingProp],
    (val) => {
    updateCurrentValue(val);
    },
    );
    return [currentValue, updateCurrentValue];
    };

    // 使用的时候
    import useModel from '.../xxx'

    // setup 中
    const [currentPage, updateCurrentPage] = useModel(props, emit, {
    prop: 'currentPage',
    });

    可以看到,我们可以清晰的看到 currentPage, updateCurrentPage 的来源。复用起来很简单快捷,pager、simpler 等页面的 v-model 都可以用上 这个 hook
    ### computed、watch 
    感觉和 Vue2 中用法类似,不同点在于 Vue3 中使用的时候需要引入。
    举例 watch 用法由于 currentPage 改变时候需要触发 change 事件,所以需要使用到 watch 功能

    import { watch } from 'vue';

    // setup 中
    const [currentPage, updateCurrentPage] = useModel(props, emit, {
    prop: 'currentPage',
    });
    watch(currentPage, () => {
    emit('change', currentPage.value);
    })

    
    # 参考文档
    1. [看懂「测试覆盖率报告」](https://github.com/JChehe/blog/issues/49)

推荐阅读
  • node.jsrequire和ES6导入导出的区别原 ... [详细]
  • 本文介绍了如何使用vue-awesome-swiper组件,包括在main.js中引入和使用swiper和swiperSlide组件,以及设置options和ref属性。同时还介绍了如何在模板中使用swiper和swiperSlide组件,并展示了如何通过循环渲染swipes数组中的数据,并使用picUrl属性显示图片。最后还介绍了如何添加分页器。 ... [详细]
  • uniapp开发H5解决跨域问题的两种代理方法
    本文介绍了uniapp开发H5解决跨域问题的两种代理方法,分别是在manifest.json文件和vue.config.js文件中设置代理。通过设置代理根域名和配置路径别名,可以实现H5页面的跨域访问。同时还介绍了如何开启内网穿透,让外网的人可以访问到本地调试的H5页面。 ... [详细]
  • VueCLI多页分目录打包的步骤记录
    本文介绍了使用VueCLI进行多页分目录打包的步骤,包括页面目录结构、安装依赖、获取Vue CLI需要的多页对象等内容。同时还提供了自定义不同模块页面标题的方法。 ... [详细]
  • 在package.json中有如下两个对象:husky:{hooks:{pre-commit:lint-staged}},lint-staged:{src** ... [详细]
  • 本文讨论了将HashRouter改为Router后,页面全部变为空白页且没有报错的问题。作者提到了在实际部署中需要在服务端进行配置以避免刷新404的问题,并分享了route/index.js中hash模式的配置。文章还提到了在vueJs项目中遇到过类似的问题。 ... [详细]
  • Python中的PyInputPlus模块原文:https ... [详细]
  • Vue基础一、什么是Vue1.1概念Vue(读音vjuː,类似于view)是一套用于构建用户界面的渐进式JavaScript框架,与其它大型框架不 ... [详细]
  • 本文介绍了brain的意思、读音、翻译、用法、发音、词组、同反义词等内容,以及脑新东方在线英语词典的相关信息。还包括了brain的词汇搭配、形容词和名词的用法,以及与brain相关的短语和词组。此外,还介绍了与brain相关的医学术语和智囊团等相关内容。 ... [详细]
  • Webpack5内置处理图片资源的配置方法
    本文介绍了在Webpack5中处理图片资源的配置方法。在Webpack4中,我们需要使用file-loader和url-loader来处理图片资源,但是在Webpack5中,这两个Loader的功能已经被内置到Webpack中,我们只需要简单配置即可实现图片资源的处理。本文还介绍了一些常用的配置方法,如匹配不同类型的图片文件、设置输出路径等。通过本文的学习,读者可以快速掌握Webpack5处理图片资源的方法。 ... [详细]
  • 如何使用Java获取服务器硬件信息和磁盘负载率
    本文介绍了使用Java编程语言获取服务器硬件信息和磁盘负载率的方法。首先在远程服务器上搭建一个支持服务端语言的HTTP服务,并获取服务器的磁盘信息,并将结果输出。然后在本地使用JS编写一个AJAX脚本,远程请求服务端的程序,得到结果并展示给用户。其中还介绍了如何提取硬盘序列号的方法。 ... [详细]
  • 本文介绍了在使用vue和webpack进行异步组件按需加载时可能出现的报错问题,并提供了解决方法。同时还解答了关于局部注册组件和v-if指令的相关问题。 ... [详细]
  • PHP反射API的功能和用途详解
    本文详细介绍了PHP反射API的功能和用途,包括动态获取信息和调用对象方法的功能,以及自动加载插件、生成文档、扩充PHP语言等用途。通过反射API,可以获取类的元数据,创建类的实例,调用方法,传递参数,动态调用类的静态方法等。PHP反射API是一种内建的OOP技术扩展,通过使用Reflection、ReflectionClass和ReflectionMethod等类,可以帮助我们分析其他类、接口、方法、属性和扩展。 ... [详细]
  • 前段时间做一个项目,需求是对每个视频添加预览图,这个问题最终选择方案是:用canvas.toDataYRL();来做转换获取视频的一个截图,添加到页面中,达到自动添加预览图的目的。 ... [详细]
  • 其实之前也有下载过完整的android源码,但是从来没有对这个做过一些总结,在加上最近需要经常去看,索性就在从新下载,编译一下,其实这些东西官网上面都有。http:sou ... [详细]
author-avatar
球之音_970
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有