VUE-最佳实践-前端

214 阅读5分钟

3 最佳实践

3.1 模板代码 vs 渲染函数

Vue提供了两类生成HTML代码的方式,绝大多数情况下我们建议使用模板代码template;在需要时可使用渲染函数,根据场景的需要选择hJSX,但不建议大规模在项目中使用JSX。这是因为:

  1. 虽然渲染函数更贴近编译器,但模板代码更贴近HTML语法,有更好的可读性和可维护性。
  2. 渲染函数需要开发人员对VNode有更深入的理解,这增加了学习成本。
  3. 复杂组件在使用h函数时会异常痛苦,而使用JSX形式的代码又会增加额外的技能要求。

当然,有些场景是非常适合使用渲染函数的:

  • 需要使用复杂逻辑构造高阶组件(HOC)时。
  • template代码的复杂逻辑需要通过JavaScript代码逻辑进行简化时。
  • 需要通过JavaScript配置项来动态构造组件,实现类似工厂方法设计模式时。

3.2 Options API vs Composition API(Vue > 2.7.0 | Vue 3)

关于这两者的对比,官网已经有详细的介绍,简而言之就是:

  • 组合式API能更好的提炼可复用代码,并让逻辑关注点更为聚焦。
  • 组合式API更加灵活。

如果你已经在使用Vue 3开发前端项目,可以把鼠标悬浮到某个Options API上,然后你会看到Vue.js的开发者已经将它命名为LegacyOptions

此外,Vue.js官方在发布v2.7.0版本时,明确宣布了会对Vue 2提供最后 18 个月的技术支持和维护。这也意味着在 2023 年年底,v2.x 版本将会正式退出历史舞台。之后如果基于项目安全性考虑,可以购买基于SLA证书的由Herodevs提供支持的版本

所以,针对使用 Vue.js 框架的项目,我们建议:

  • 在启动新项目时,优先考虑使用Vue 3,且尽可能多的使用Composition API进行代码复用;除非对版本有特殊要求,否则不再轻易使用Vue 2
  • 对于老项目,若有长期维护或迁移的必要,也尽可能在手动升级到v2.7.x版本后,再配合迁移指南升级流程进行升级。
  • 对于部分新需求较少、维护工作较小的老项目,为节约成本考虑,可以保持原有版本。

也请大家注意,组合式API和选项式API并不是非此即彼的,不必强行要求项目完全摒弃选项式API。


3.3 减少混入Mixin的使用

关于这点无论是Vue.js的开发者还是使用者,都应该深有感触。具体可以参阅官网说明,不再赘述。


3.4 工具类依赖建议

3.4.1 使用·Big.js进行数值运算

JavaScript的数字存储方式符合IEEE754的规范,这导致了直接进行十进制运算时会在进制转换时出现精度丢失问题。

如果你的项目需要进行数值类型数据的运算,强烈建议使用Big.js提供的API,避免精度问题。

/** 代码对比 */
import Big from 'big.js'

// 直接计算
const foo = 0.3 - 0.1                    // 0.19999999999999998
const bar = (new Big(0.3)).minus(0.11)   // 0.2

3.4.2 使用Lodash简化你的代码

Lodash个一致性、模块化、高性能的JavaScript实用工具库,能够在简化我们日常开发工作的同时,有效的避免常见异常的产生。

除非你原本的代码健壮性存在问题,否则这个工具库能够大幅度提升代码的简洁程度,这里举几个简单的例子。

注意:Lodash提供的四则运算方法并不能解决精度丢失问题!

/** 代码对比 */
import _ from 'lodash'

// 简化判断
const hasEmptyInfos = infos === undefined || infos === null || infos.length === 0  // 不使用Lodash
const hasEmptyInfos = _.isEmpty(infos)

// 优化操作
const label = info ? info.name || '默认值' : '默认值'  // 不使用Lodash
const label = _.get(info, 'name', '默认值')           // 使用Lodash

// 不使用Lodash
function getAliveCats (cats) {
  const aliveCats = []
  if (!cats || cats.length === 0) return aliveCats
  return cats.filter(cat => cat && cat.alive)
}
// 使用Lodash
function getAliveCats (cats) {
  return _.filter(cat => _.get(cat, 'alive', false))
}

// 优化对象比对
const foo = { a: 1 }
const bar = { a: 1 }

foo === bar                     // => false
_.isEqual(foo, bar)             // => true

// 函数(方法)操作
function foo (a, b, c) {
  return [a, b, c]
}

const curried = _.curry(foo)    // 克里化
curried(1)(2)(3) => [1, 2, 3]

const throttled = _.throttle(foo, 500, { leading: false, tailing: false })  // 节流
throttled(1, 2, 3)    // 500ms左右只执行一次

3.4.3 使用dart-sass替代node-sass

在基于Vue.js开发的前端项目中使用sass/scss语法是非常常见的,但用于预编译过程的node-sass却因为网络和机器因素,令人十分头疼。

即使是在配置了镜像的情况下,依然可能因为gyp版本的原因导致无法使用。Vue.js官方也意识到了这个问题,在近几年的工程初始化中也会建议使用dart-sass替换该依赖,对于老项目的替换步骤如下:

  • 移除 node-sass 依赖,添加 dart-sass 依赖,以 yarn 为例 yarn remove node-sass && yarn add -D dart-sass
  • 如果正在使用 stylelint,调整 stylelint 配置,增加 'selector-pseudo-element-no-unknown': null
  • 如果项目中使用 /deep/ 做样式定制化,全部调整为 ::v-deep

3.4.4 使用Day.jsMoment.js处理时间日期

直接使用字符串或者Date类型处理时间日期是可以的,但出现需要进行一些比对逻辑、调整日期等工作时则会显得非常繁琐。

使用Day.jsMoment.js处理时间日期能够几大幅度的提升代码可读性,在进行日期比较和运算时也能提高代码执行效率。

注意:你需要根据实际项目的情况,任选其一而不是混用它们。

/** 代码对比 */
import dayjs from 'dayjs'
import moment from 'moment'

// 使用day.js
const startDate = dayjs().startOf('day')            // 取今日的凌晨0点作为开始时间。
const yesterdayDate = dayjs().subtract('day', 1)    // 取昨天的当前时刻。
const endDate = dayjs().endOf('day')                // 取今晚的11点59分59秒作为结束时间。

if (endDate.isBeforeOrSame(startDate)) {
  throw new Error('endDate cannot before startDate')
}

// 使用moment.js
const startDate = moment().startOf('day')            // 取今日的凌晨0点作为开始时间。
const yesterdayDate = moment().subtract('day', 1)    // 取昨天的当前时刻。
const endDate = moment().endOf('day')                // 取今晚的11点59分59秒作为结束时间。

if (endDate.isBeforeOrSame(startDate)) {
  throw new Error('endDate cannot before startDate')
}

Day.js 核心库体积非常小(~2kb),而 Moment.js 默认库的体积则较大(~70kb),导致这个差异的主要原因有:

  • Moment.js 默认提供的 API 会更加丰富(但实际上未必用得到);Day.js 的核心 API 较少(但通常足够用),一些功能需要引入配套模块。
  • Moment.js 默认加载了国际化语言模块;Day.js 支持国际化,但需要手动加载。

二者的选择建议:

  • 通常情况下,更推荐选择 Day.js,并按需加载所需模块
  • 项目使用的第三方依赖已经引用了 Moment.js,且无法替换该引用(例如 antd@1.x),则选择使用 Moment.js
  • 项目完全没有编译效率或资源大小的顾虑(例如纯粹的桌面端应用),则可以考虑使用 Moment.js

在一些使用 Moment.js 的项目中,如果在意最终打包资源的大小,并且你确实不需要多少国家的日期语言包,有个很简单的办法降该依赖打包后的体积(以使用 webpack为例):

/** 在webpack配置的plugins配置项中 */
plugins: [
  /* eslint-disable no-useless-escape */
  new ContextReplacementPlugin(/moment[/\]locale$/, /en|zh/)     // 仅使用中英文语言包,极大幅度的降低打包后Moment.js依赖的大小(大概最后剩下~30kb)
]

3.4.5 接口报文中的日期时间使用时间戳格式

日期、时间格式在前后端通讯时如果不使用统一的格式标准,很容易出现时区问题(尤其在带有时间的情况下)。

其原因时因为客户端(浏览器)一般默认使用东八区时间GMT+8,而服务器常用的是格林威治时间(世界时GMT+0)。

然而如果只是简单的定义前后端的日期时间格式,则可能带来日期比较或转换的问题(后端代码会更麻烦,因为数据可能需要入库)。

所以,强烈推荐在接口报文中前后端均使用时间戳进行日期时间数据的传输。

// bad 不推荐
import axios from 'axios'

// 查询某个接口时前端选择日期,后端使用该日期时间过滤数据。
axios.get('/foo', {
  params: {
     ...
     someDateTime:  '2022-07-01 00:00:00'  // 后端在获取并转换该日期时,很可能转换成2022630日下午4点
  }
})

axios.post('/bar', { someDateTime: '2022-07-01 00:00:00' })

// good 推荐
import axios from 'axios'
import dayjs from 'dayjs'

axios.get('/foo', {
  params: {
     someDateTime:  dayjs('2022-07-01 00:00:00').valueOf()
  }
})

axios.post('/bar', { someDateTime: dayjs('2022-07-01 00:00:00').valueOf() })

// 后端数据也可以简单转换,例如 updatedDate = 1656604800000
const someDateTime = dayjs(res.updatedDate)