前端组件化开发方向

1.什么是组件化开发

  • 前端的组件化在概念上与后端的 package 很相似,只不过前端的组件涉及到更多的是展示和交互方面的逻辑。当然,前端组件与后端架构的微服务概念类似,可以理解成一个组件就是一个服务组件,只提供某个服务。
  • 前端组件化开发,就是将页面的某一部分独立出来,将这一部分的 数据层(M)、视图层(V)和 控制层(C)用黑盒的形式全部封装到一个组件内,暴露出一些开箱即用的函数和属性供外部组件调用。
  • 一个前端组件,包含了 HTML、CSS、JavaScript,包含了组件的模板、样式和交互等内容,基本上涵盖了组件的所有的内容,外部只要按照组件设定的属性、函数及事件处理等进行调用即可,完全不用考虑组件的内部实现逻辑,对外部来说,组件是一个完全的黑盒。
  • 组件可以多层封装,通过调用多个小组件,最后封装成一个大组件,供外部调用。比如:一个 Input 输入框 是一个组件,一个 Select下拉选择框 也是一个组件,可以用 form 在这两个组件上包装一层,就是一个 Form 的组件。

2.组件化开发的优点

  • 前端的组件化开发,可以很大程度上降低系统各个功能的耦合性,数据相互独立,并且提高了功能内部的聚合性。这对前端工程化及降低代码的维护来说,是有很大的好处的,内部结构密封,不与全局或其他组件产生影响,特别是针对逻辑复杂的功能能够进行拆分,更好排查问题。
  • 耦合性的降低,提高了系统的伸展性,降低了开发的复杂度,提升开发效率,降低开发成本。

3.怎么设计一个组件

1.标准性

  • 任何一个组件都应该遵守一套标准,可以使得不同区域的开发人员据此标准开发出一套标准统一的组件。

2.专一性

  • 设计组件要遵循一个原则:一个组件只专注做一件事,且把这件事做好。
  • 一个功能如果可以拆分成多个功能点,那就可以将每个功能点封装成一个组件,当然也不是组件的颗粒度越小越好,只要将一个组件内的功能和逻辑控制在一个可控的范围内即可。
  • 页面上有一个 Table 列表和一个分页控件,就可以将 Table 封装为一个组件,分页控件 封装成一个组件,最后再把 Table组件 和 分页组件 封装成一个组件。Table 组件还可以再拆分成多个 table-column 组件,及展示逻辑等。

3.可配置性

  • 一个组件,要明确它的输入和输出分别是什么。
  • 组件除了要展示默认的内容,还需要做一些动态的适配,比如:一个组件内有一段文本,一个图片和一个按钮。那么字体的颜色、图片的规则、按钮的位置、按钮点击事件的处理逻辑等,都是可以做成可配置的。
  • 要做可配置性,最基本的方式是通过属性向组件传递配置的值,而在组件初始化的声明周期内,通过读取属性的值做出对应的显示修改。还有一些方法,通过调用组件暴露出来的函数,向函数传递有效的值;修改全局 CSS样式;向组件传递特定事件,并在组件内监听该事件来执行函数等。
  • 在做可配置性时,为了让组件更加健壮,保证组件接收到的是有效的属性、函数接收到的是有效的参数,需要做一些校验。
一. 属性的值的校验
  1. 属性值的类型是否是有效的。如果某个属性要求传递一个数组,那么传递过来的值不是数组时,就要抛出异常,并给出对应的提示。
  2. 属性是否是必填的。有的属性的值,是组件内不可缺少的时,就要是必填的,在组件初始化时要做是否传递的检查,如果没有传递,则需要抛出异常,并给出相应的提示。如果属性不是必填的,可以设定一个默认值,当属性没有被设置时,就使用默认值。
  3. 代码示例
// title.jsx (Title组件)
  import React, { Component, PropTypes } from 'react';
 
  export default class Title extends Component {
    constructor(props) {
      super(props);
    }
 
    static propTypes = {
      // 属性检查
      title: PropTypes.string.isRequired
    }
 
    render() {
      const { title } = this.props;
 
      return (
        <p>{ title }</p>
      )
    }
  }
函数的参数的校验
  1. 函数的参数校验,只要按照传统的方法进行校验即可。在函数内部顶部判断参数的值和类型,如果不满足要求,则抛出异常,并给出相应的提示。
  2. 代码示例
// 判断一个函数的第一个必填,且为 String 格式
// ES6 语法
changeTitle(title) {
  if (typeof title !== 'string') {
    throw new Error('必须传入一个 title,才能修改 title。')
  }
  // 满足条件,可以进行修改
  this.title = title   // vue 语法
  this.setState({      // react 语法,修改state的值
    title
  })
} 

4.公共组件

  • 目前项目中针对所有的项目风格有统一要求,所以公司做一个统一的组件发布npm非常重要,项目中最常用的组件有:Button、CheckBox、DatePicker、Input、Radio、Select、Upload、Table、Modal、Spin、运营侧框架层、商户侧框架层、table组件、form组件、form新增数据组件,目前运营侧框架层、商户侧框架层、table组件、form组件、form新增数据组件这几个组件,针对项目统一性比较关键也能更高的提升开发效率。
  1. 目前在使用的有运营侧框架层、商户侧框架层、table组件
  2. 目前我这边使用的表单搜索组件SearchForm,通过传入的configs自动查询数据,回调到父组件

5.公共函数

  • 目前项目中很多函数是可以共用出来的
  1. 正则验证
export default {
 //验证是否为空
    'IsNull': function (data) {
        return (data.length > 0);
    },
    //验证密码
    'Password': function (pwd) {
        if (/^[A-Za-z0-9]{6,16}$/g.test(pwd)) {
            return true;
        } else {
            return false;
        }
    },
    //验证两次密码是否相同
    'IsSamePwd': function (pwd1, pwd2) {
        if (pwd1 == pwd2) {
            return true;
        } else {
            return false;
        }
    },
    //验证手机号
    'Phone': function (data) {
        if (/^0?1[3|4|5|8|7][0-9]\d{8}$/.test(data)) {
            return true;
        } else {
            return false;
        }
    },
    //验证验证码
    'Captcha': function (data) {
        if (/^\d{4,6}$/g.test(data)) {
            return true;
        } else {
            return false;
        }
    },
    //判断是否为数字
    'IsNumber': function (data) {
        if (/^[0-9]*$/.test(data)) {
            return true;
        } else {
            return false;
        }
    },
    //判断小数或整数
    'IsNumberOrDecimal': function (data) {
        if (/^[0-9]*(\.[0-9]{1,2})?$/.test(data)) {
            return true;
        } else {
            return false;
        }
    },
    //验证中文
    'IsChinese': function (data) {
        if (/^[\u4e00-\u9fa5]{1,16}$/.test(data)) {
            return true;
        }
        else {
            return false;
        }
    },
    //验证银行卡
    'Credit': function (data) {
        if (/^(\d{16}|\d{19})$/.test(data)) {
            return true;
        }
        else {
            return false;
        }
    },
    //验证车牌号
    'VehicleNum': function (data) {
        if (/^[\u4E00-\u9FA5][\da-zA-Z]{6}$/.test(data)) {
            return true;
        }
        else {
            return false;
        }
    },
    //验证车辆识别代码
    'VehicleIDNum': function (data) {
        if (/^\d{17}$/.test(data)) {
            return true;
        }
        else {
            return false;
        }
    },
    //验证邮箱
    'Email': function (str) {
        if (/^(\w-*\.*)+@(\w-?)+(\.\w{2,})+$/.test(str)) {
            return true;
        } else {
            return false;
        }
    },
    //身份验证
    IdentityCodeValid: function (code) {
        var city = { 11: "北京", 12: "天津", 13: "河北", 14: "山西", 15: "内蒙古", 21: "辽宁", 22: "吉林", 23: "黑龙江 ", 31: "上海", 32: "江苏", 33: "浙江", 34: "安徽", 35: "福建", 36: "江西", 37: "山东", 41: "河南", 42: "湖北 ", 43: "湖南", 44: "广东", 45: "广西", 46: "海南", 50: "重庆", 51: "四川", 52: "贵州", 53: "云南", 54: "西藏 ", 61: "陕西", 62: "甘肃", 63: "青海", 64: "宁夏", 65: "新疆", 71: "台湾", 81: "香港", 82: "澳门", 91: "国外 " };
        var tip = "";
        var pass = true;

        if (!code || !/^\d{6}(18|19|20)?\d{2}(0[1-9]|1[12])(0[1-9]|[12]\d|3[01])\d{3}(\d|X)$/i.test(code)) {
            tip = "身份证号格式错误";
            pass = false;
        }

        else if (!city[code.substr(0, 2)]) {
            tip = "地址编码错误";
            pass = false;
        }
        else {
            //18位身份证需要验证最后一位校验位
            if (code.length == 18) {
                code = code.split('');
                //∑(ai×Wi)(mod 11)
                //加权因子
                var factor = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
                //校验位
                var parity = [1, 0, 'X', 9, 8, 7, 6, 5, 4, 3, 2];
                var sum = 0;
                var ai = 0;
                var wi = 0;
                for (var i = 0; i < 17; i++) {
                    ai = code[i];
                    wi = factor[i];
                    sum += ai * wi;
                }
                var last = parity[sum % 11];
                if (parity[sum % 11] != code[17]) {
                    tip = "校验位错误";
                    pass = false;
                }
            }
        }
        return pass;
    }
}
  1. 数字加减乘除解决浮点失精度的问题
export default {
  // 加
  accAdd: function (arg1, arg2) {
    var r1, r2, m;
    try { r1 = arg1.toString().split(".")[1].length } catch (e) { r1 = 0 }
    try { r2 = arg2.toString().split(".")[1].length } catch (e) { r2 = 0 }
    m = Math.pow(10, Math.max(r1, r2))
    return (Math.round(arg1 * m) + Math.round(arg2 * m)) / m
  }
  ,
  // 除
  accDiv: function (arg1, arg2) {
    var t1 = 0, t2 = 0, r1, r2;
    var t1 = 0, t2 = 0, r1, r2;
    try {
      t1 = arg1.toString().split(".")[1].length;
    }
    catch (e) {
    }
    try {
      t2 = arg2.toString().split(".")[1].length;
    }
    catch (e) {
    }
    try {
      r1 = Number(arg1.toString().replace(".", ""));
      r2 = Number(arg2.toString().replace(".", ""));
    }
    catch (e) {
    }
    return (r1 / r2) * pow(10, t2 - t1);
  }
  ,
  // 减
  Subtr: function (arg1, arg2) {
    var r1, r2, m, n;
    try { r1 = arg1.toString().split(".")[1].length } catch (e) { r1 = 0 }
    try { r2 = arg2.toString().split(".")[1].length } catch (e) { r2 = 0 }
    m = Math.pow(10, Math.max(r1, r2));
    n = (r1 >= r2) ? r1 : r2;
    return ((arg1 * m - arg2 * m) / m).toFixed(n);
  }
  ,
  // 乘
  accMul: function (arg1, arg2) {
    var m = 0, s1 = arg1.toString(), s2 = arg2.toString();

    try { m += s1.split(".")[1].length } catch (e) { }

    try { m += s2.split(".")[1].length } catch (e) { }

    return Number(s1.replace(".", "")) * Number(s2.replace(".", "")) / Math.pow(10, m)
  },
}

3.一些有用的函数

export default{
  // 多余数字显示...
  stringCutOut(str, num) {
    if (str.length > num) return str.substring(0, num) + '...';
    return str;
  },
  // 千分位
  toThousands(num) {
    num = num.toString().replace(/$|\,/g, '');
    if (isNaN(num))
      num = "0";
    let sign = (num == (num = Math.abs(num)));
    num = Math.floor(num * 100 + 0.50000000001);
    let cents = num % 100;
    num = Math.floor(num / 100).toString();
    if (cents < 10)
      cents = "0" + cents;
    for (var i = 0; i < Math.floor((num.length - (1 + i)) / 3); i++)
      num = num.substring(0, num.length - (4 * i + 3)) + ',' +
        num.substring(num.length - (4 * i + 3));
    return (((sign) ? '' : '-') + num + '.' + cents);
  },
  // 数组移动
  swapItems: function (arr, index1, index2) {
    arr[index1] = arr.splice(index2, 1, arr[index1])[0];
    return arr;
  },
  // 获取浏览器参数
   getParam: function (paramName) {
    // 获取参数
    var url = window.location.search;
    // 正则筛选地址栏
    var reg = new RegExp("(^|&)" + paramName + "=([^&]*)(&|$)");
    // 匹配目标参数
    var result = url.substr(1).match(reg);
    //返回参数值
    return result ? decodeURIComponent(result[2]) : null;
  },
  // 文本框不允许输入超过两位小数点的小数
  clearNoNum: function (obj) {
    obj.value = obj.value.replace(/[^\d.]/g, "");  //清除“数字”和“.”以外的字符
    obj.value = obj.value.replace(/\.{2,}/g, "."); //只保留第一个. 清除多余的
    obj.value = obj.value.replace(".", "$#$").replace(/\./g, "").replace("$#$", ".");
    obj.value = obj.value.replace(/^(\-)*(\d+)\.(\d\d).*$/, '$1$2.$3');//只能输入两个小数
    if (obj.value.indexOf(".") < 0 && obj.value != "") {//以上已经过滤,此处控制的是如果没有小数点,首位不能为类似于 01、02的金额
      obj.value = parseFloat(obj.value);
    }
  },
  // 保留小数位
  format_number(srcNumber, n) {//n是要保留的位数
    var dstNumber = parseFloat(srcNumber);
    if (isNaN(dstNumber)) {
      return srcNumber;
    }
    if (dstNumber >= 0) {
      dstNumber = parseInt(dstNumber * Math.pow(10, n) + 0.5) / Math.pow(10, n);//关键点
    } else {
      var tmpDstNumber = -dstNumber; dstNumber = -parseInt(tmpDstNumber * Math.pow(10, n) + 0.5) / Math.pow(10, n);
    }
    var dstStrNumber = dstNumber.toString();
    var dotIndex = dstStrNumber.indexOf('.');
    if (dotIndex < 0) {
      dotIndex = dstStrNumber.length; dstStrNumber += '.';
    }

    while (dstStrNumber.length <= dotIndex + n) {
      dstStrNumber += '0';
    }
    return dstStrNumber;
  }
}
  1. 解决ant design分页选中不能保存之前页选中数据的函数
import findIndex from 'lodash/findIndex';
import remove from 'lodash/remove';

export default (value, selectedRowKeys, selectedRows, param) => {
  return {
    selectedRowKeys,
    onChange(selectedRowKeys) {
      value.setState({ selectedRowKeys });
    },
    onSelect: (record, selected) => {
      if (selected) {
        // 如果数组里面不存在则做添加
        if (findIndex(selectedRows, record[param]) < 0) {
          selectedRows.push(record);
        }
      } else {
        remove(selectedRows, (n) => {
          return n[param] === record[param];
        });
      }
      value.setState({
        selectedRows
      });
    },
    onSelectAll: (selected, selectedRow, changeRows) => {
      if (selected) {
        for (let index = 0; index < changeRows.length; index += 1) {
          if (findIndex(selectedRows, changeRows[index][param]) < 0) {
            selectedRows.push(changeRows[index]);
          }
        }
      } else {
        for (let index = 0; index < changeRows.length; index += 1) {
          remove(selectedRows, (n) => (
            n[param] === changeRows[index][param]
          ));
        }
      }
      value.setState({
        selectedRows
      });
    }
  };
};

posted @ 2019-04-16 22:30  福小松  阅读(3281)  评论(0编辑  收藏  举报