全栈之路 —— Vue自定义表格组件

We should separate Structure, Presentation, and Behavior. -- The Golden Rule

2019年上半年,间间断断写了一些页面,也为我的全栈打上了前端这块拼图。

这篇文章,我会先介绍一下我对前端的理解,然后用vue框架写一个自定义的表格的demo。

A Quick Glimpse

在公司里,我做了一些后台管理服务,也开发了一些数据分析工具。后台管理服务对页面的要求不高,python可以用jinja,java(kotlin)可以用ftl,来渲染动态页面,开发快速,简单实用。数据分析工具的页面就会比较复杂,PM也比较看重页面的美观和交互,我用了当下流行的vue框架,页面交互处理起来确实更方便。

我自己在写前端页面的时候,有一个豁然开朗的时间点,关于黄金法则“结构和表现相分离”。了解法则之前,我专注于实现页面价值,在代码的结构上思考的不多,然后页面进行增量和迭代时都痛苦不堪;了解之后,写代码就有了一定的理论指导:

  • html和css的分离。让盒子模型一下子非常清晰了,拿到PM的原型图,先分几个大块,结构就基本确定了;
  • js和css的分离。让页面事件变得非常清晰,js基本只负责click,input和select等几个用户主动交互的事件;
  • css对表现的绝对控制。让苛刻的PM也喜笑颜开,掌握一些基本style (display, position等),就能完全满足PM关于位置、颜色、大小等等的任性要求;
  • 还不满足?HTML5中的media tag,加上pixi.js库,多媒体和动画也不在话下。

理论核心 + 不断实践,在应用or业务层就感觉非常棒了。

A Brief Instance

在做数据分析工具的时候,最常写的就是画图和表格。这里做一个table demo,分享一下所思所学。

在开始写代码之前,我们先想一下表格的常见属性。

  • 不固定的列数。有五列的表,也有九列的表,表头的名字也经常换,表头有时会需要一些注释;
  • 筛选的列。有些列需要能够筛选;
  • 排序的列。有些列需要能够排序;
  • 个性化的单元格。有的单元格可能需要特殊处理。

所以,我首先把表格分成了header和body,每一个单元格都是一个对象,具有单元格的一些属性。

在渲染数据的过程中,为了响应筛选和排序,采用行列索引的方式依次渲染单元格。为了筛选和排序互不影响,就简单地采用了全部排序的方式。

想清楚了这些问题之后,我们就可以开始写代码了。

说到开始写代码,前端demo代码有一点很有意思,因为即时重启的缘故,不用测试代码就有直观的反馈,让每一行代码都有一个功能点,很棒!


一个DEMO

那,就开始吧。

  1. 直接使用vue create创建一个新的项目。
vue create custom-table
# 安装一些必要的依赖,bootstrap,jquery,fontawesome之类的
yarn add bootstrap jquery
# 为了让vue更好的使用全局的jquery,webpack提供的plugin,这里可以简单的创建一个vue.config.js
touch vue.config.js
  1. 在App.vue中写出表格的结构。

// 表的数据
props: {
    header: {
      type: Array,
      require: true,
      default: () => {
        return [
          { value: "col1", info: "这是第一列" },
          { value: "col2", filter: true },
          { value: "col3", sort: true }
        ];
      }
    },
    body: {
      type: Array,
      require: true,
      default: () => {
        return [
          [{ value: "col1", color: "red" }, { value: "col2", type: "percent"}, { value: "col3", type: "float" }],
          [{ value: "col1" }, { value: "col2" }, { value: "col3" }]
        ];
      }
    }
}

这里可以感受到vue语法糖的优雅,for和if很好的嵌入,通过数据来改变DOM结构。
也能感受我的设计意图,header中的属性可以决定表头的特殊功能(筛选、排序、注释);body中的属性可以去改变单元格的表现(css、format)。

  1. 有了清晰的结构,就开始组件补全计划吧。
    用FilterElement来说,table父组件中会有若干个filter组件,每一个filter组件输入rowIndex和对应colIndex的value([{rowIndex: rowIndex, value: value}]或者简单写成[[rowIndex, value]]),输出经过筛选之后的rowIndex;
    同时,为了让多列同时筛选,我们取不同组件输出值的并集。
    想清楚这两个细节之后,代码就顺理成章了。
    在table父组件中,我们有:


data() {
    return {
        // key是colIndex, value是fitleredRowIndexes, 用来最后取并集
        filterBuffer: {}
    }
},
methods: {
    // 获取表格一列的值,和sortElement的方法一样
    getAllColumnData: function(colIndex) {
        let tmpArray = [];
        for (let rowIndex = 0; rowIndex < this.body.length; rowIndex++) {
            tmpArray.push([rowIndex, this.body[rowIndex][colIndex].value]);
        }
        return tmpArray;
    },
    // 用filterBuffer来保存某一列的排序索引
    filterRows: function(colIndex, filteredRowIndexes) {
        this.$set(this.filterBuffer, colIndex, filteredRowIndexes);
    }
}

在FilterElement子组件中,我们有:

computed: {
    // 获得列的数据
    allDataArray: function() {
        let tmpArray = [];
        for (let i = 0; i < this.columnData.length; i++) {
            tmpArray.push(this.columnData[i][1]);
        }
        return tmpArray;
    },
    // 去重数据
    allDataSet: function() {
        return Array.from(new Set(this.allDataArray));
    },
    // 筛选后的行索引
    filteredRowIndexes: function() {
        let tmpArray = [];
        for (let i = 0; i < this.columnData.length; i++) {
            if (this.checkValue.includes(this.allDataArray[i])) {
                tmpArray.push(this.columnData[i][0]);
            }
        }
        return tmpArray;
    }
},
watch: {
    // 监听用户的筛选事件
    checkValue: function() {
        this.$emit("filterRows", this.filteredRowIndexes);
    }
}

在写筛选组件时,有很多值得思考的地方:

  • 关于去重元素,如果是primitive data,我们可以直接使用Set;那如果是对象,就需要hash去重,还可能要考虑“与”和“或”的关系;
  • 无论js、java、还是c,在做对象遍历的时候都没有python那种“自在”的感觉。不过js在性能上好像有这样的关系,for > for-of > forEach > filter > map > for-in
  • 对象的深、浅拷贝,确实比python需要要花更多的心思;
  • 全选 / 全部选 / checkbox的不确定态;
  1. 完成所有组件之后,demo就完成了。全部代码在我的GIT REPO里,欢迎大家查看。

A Short Summary

前端三板斧,HTML、CSS 和 JS,随着实践也掌握的越来越多。JS的对象和原型,CSS的loader和parser,Vue的生命周期和状态管理,都略知一二。学习会让人开心,但也会让人迷惘,因为在这个焦虑的社会,价值还是太重要了。共勉 ~

你可能感兴趣的:(全栈之路 —— Vue自定义表格组件)