中台前端开发一些注意点

webpack打包

模块提取策略

提取出第三方依赖模块(通常是node_modules)、业务依赖公共模块(通常是项目的utils文件)、业务模块(通常是一个页面的js模块),webpack运行时模块。

打包策略1:
所有模块打包成一个文件。
简单粗暴。

打包策略2:
设定一个文件大小阈值,比如200KB。
大于200KB,提取出业务模块和其依赖模块两个文件。
小于200KB,业务模块和其依赖模块打包成一个文件。

打包策略3:
设定一个模块依赖阈值,比如依赖次数3。
一个模块被三个或以上模块依赖,就独立打包成一个文件。
否则,合并到被依赖模块一同打包。

打包策略4:
多个异步模块依赖的公共模块,
打包到首屏加载的业务依赖公共模块;
或者打包到独立的异步依赖公共模块,跟懒加载的异步模块并行加载。

模块加载策略

加载顺序如下:

  1. webpack运行时模块
  2. 业务模块
  3. 业务依赖公共模块
  4. 第三方依赖模块

同步加载场景:
当项目体积并不大,并且是内网环境,可以一次性加载所有模块,并做好缓存处理。

异步加载场景:
当项目体积较大,或者是外网环境,通常进行按需异步多次加载模块。

模块缓存策略

模块缓存依赖浏览器缓存。
浏览器缓存有强缓存和协商缓存。
强缓存依赖Cache-Control字段,协商缓存依赖ETag。
缓存的命中,依靠请求的地址一致。
针对这个机制,webpack的模块缓存策略在开发和生产环境不同。

  • 开发阶段的chunkhash 是 build-specific ,即每次编译都不同。
  • 生产环境的chunkhash 是 chunk-specific,是根据每个 chunk 的内容计算出的 hash。

webpack数据mock

使用webpack-api-mocker模块。
该模块包含了数据mock,代理转发功能。
参考 https://github.com/jaywcjlove/webpack-api-mocker

请求和响应的预处理

使用axios作为项目的接口的请求响应预处理,来设置请求响应的统一拦截。
常用配置如下:

import axios from 'axios'
axios.defaults.baseURL = ''

//依据http状态,统一封装响应处理
axios.interceptors.response.use((res)=>{
    // 状态码200阶段
    if (res.status !== 200) {
        return Promise.reject(res)
    }
}, (error) => {
    // 超时或者指定状态码阶段
    let status = error.response ? error.response.status : error.message
    switch (status) {
        case 400:
        case 401:
        case 500:
        default:
    }
    return Promise.reject(error)
})

// 请求的封装
function _request (method) {
  return function (url, pms) {
    let param2th = {}
    if (method === 'get') {
      param2th.params = pms
    } else if (method === 'delete') {
      param2th.data = pms
    } else {
      param2th = pms
    }

    return new Promise((resolve, reject) => {
      axios[method](url, param2th).then(response => {
        if (response.data.status === 0) {
          resolve(response)
        } else {
          Message.error(response.data.errMsg || '系统繁忙')
          reject(response)
        }
      }).catch((error) => {
        reject(error)
      })
    })
  }
}

export const _post = _request('post')
export const _put = _request('put')
export const _get = _request('get')
export const _delete = _request('delete')
import {
  _get,
  _post,
  _delete,
  _put
} from './config'

const obj = {
  get: _get,
  post: _post,
  delete: _delete,
  put: _put
}

const factory = function (method) {
  return function (url) {
    return function (pms) {
      return obj[method](url, pms)
    }
  }
}

const postfn = factory('post')
const getfn = factory('get')
const deletefn = factory('delete')
const putfn = factory('put')

const projectList = getfn('/project/list.json')
const projectEdit = postfn('/project/edit.json')

路由规划

下面是几个注意点:

  • 无token
    哪些页面需要token,哪些页面不需要。
    无token访问需要token的页面,路由跳转到何处。
    有token访问某些页面,需要自动跳转否。
    token的权限细粒度控制。

  • token过期

  • 路由无匹配

  • 子路由嵌入

  • 动态路由加载

接口结构和UI组件结构适配

后台接口结构和ElementUI所需数据结构往往不一致。
如果不用GraphQL,通常需要前端自己写适配库。

import cloneDeep from "lodash/clonedeep";

/**
树的递归查找方法
* @constructor
* @param {object} data - 树结构数据
* @param {string} key - 查找的key
* @param {string} name - key对应的value所需要相等的值
* @returns {object} - 已找到的对象
*/
const findLeafObj = function (data, key, name) {
  let obj = null;
  let finded = false;
  let getArray = function (data, key, name) {
    if (finded) {
      return;
    }
    for (let i in data) {
      let item = data[i];
      if (item[key] == name) {
        obj = item;
        finded = true;
        break;
      } else {
        if (item.children) {
          getArray(item.children, key, name);
        }
      }
    }
  };
  getArray(data, key, name);
  return obj;
};

/**
Select 选择器的数据结构转换方法
* @constructor
* @param {object} data - 后台数据
* @param {object} mapping - 对应的转换方式
* @returns {object} - 适合Select 选择器的数据结构
*/
const transformSelectOption = function (data, mapping) {
  return data.map(function (item) {
    return {
      value: mapping.value(item),
      label: mapping.label(item)
    };
  });
};

const filterNode = function (data, id, key, method) {
  return data[method](function (item) {
    return item[key] === id;
  });
};

const transformCascaderData = function (data, id, level, _innerLevel) {
  let lev = 99999;
  if(level !== undefined){
      if(level < 1){
        throw "level不能小于1";
      }
      lev = level;
  }
  // 根据id找到节点对象
  let cur = filterNode(data, id, "id", "find");
  if (!cur) {
    cur = {};
  }
  cur['_innerLevel'] = _innerLevel;

  // 根据id找到子节点对象们
  let children = filterNode(data, id, "parentId", "filter");

  // 根据子节点对项们递归找子节点对象们
  if ((lev > 0) && children.length) {
    for (let child of children) {
      let n = transformCascaderData(data, child.id, lev -1, _innerLevel+1);
      if (cur.children) {
        cur.children.push(n);
      } else {
        cur.children = [n];
      }
    }
  }
  return cur;
};

/**
 树的层序遍历
 * @constructor
 * @param {array} tree - 后台数据
 * 例如:[{
        "id": 1,
        "name": "集团",
        "parentId": 0,
        "parentName": ""
    }]
 * @param {number} level - 层序遍历的深度
 * @param {object} keyObj - 节点自身标识和父节点标识
 * @returns {array} - 指定深度的节点列表
 */
const layerTraverse = function(tree, level=1, keyObj={self:'id',  parent: 'parentId'}){
  let thatLevelArr = [];
  if(level < 1){
    return thatLevelArr;
  }

  const innerTraverse = function(tree, id, innerLev){
    let cur = filterNode(tree, id, keyObj['self'], "find");
    let children = filterNode(tree, id, keyObj['parent'], "filter");

    if(innerLev == level){
      thatLevelArr.push(cur);
    }
    if(children.length > 0){
      for(let child of children){
        innerTraverse(tree, child[keyObj['self']], innerLev + 1);
      }
      
    }
  }
  innerTraverse(tree, 0 , 0);
  return thatLevelArr;
};

const setCascaderOption = function (data) {
  for (let item of data) {
    item['label'] = item['name'];
    item['value'] = item['id'];
    let children = item.children;
    if (children) {
      setCascaderOption(children);
    }
  }
  return data;
};

const setCascaderOptionUser = function (data, UserKeyValue) {
  let dataKey = 'name';
  let dataValue =  'id';
  let showKey = 'label';
  let showValue =  'value';
  if(UserKeyValue){
    dataKey = UserKeyValue['dataKey'];
    dataValue = UserKeyValue['dataValue'];
    showKey = UserKeyValue['showKey'];
    showValue = UserKeyValue['showValue'];
  }

  for (let item of data) {
    item[showKey] = item[dataKey];
    item[showValue] = item[dataValue];
    let children = item.children;
    if (children) {
      setCascaderOption(children, UserKeyValue);
    }
  }
  return data;
};

/**
Cascader 级联选择器的数据结构转换方法
* @constructor
* @param {object} data - 后台数据
* @param {string} id - 转换的节点入口
* @param {object} UserKeyValue - 用户指定接口数据,和UI所需数据的对应关系
{
  dataKey: '', // 接口的key
  dataValue: '', // 接口value
  showKey: '', // UI所需的key
  showValue: '', // UI所需的value
}
* @param {number} level - 需要遍历的深度,默认99999层,最小1层。
* @returns {object} - 适合Cascader 级联选择器的数据结构
*/
const getCascaderOption = function (data, id, UserKeyValue, level) {
  const storageDirectory = cloneDeep(data);
  const cascaderData = transformCascaderData(storageDirectory, id, level, 0);
  const cascaderOptions = setCascaderOptionUser(cascaderData.children, UserKeyValue);

  return cascaderOptions;
};

export {
  filterNode,
  findLeafObj,
  transformSelectOption,
  getCascaderOption,
  layerTraverse,
}
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容