# Key-Value的复用方式
# 🎬 业务场景
在前端工程的许多地方存在 key-value 转换:以下是一些常见例子
<el-option label='typeA' value='1'>
<el-option label='typeB' value='2'>
<div>{{ data.isActive === 0 ? '否' : '是' }}</div>
<div v-if="status === 0">未生效</div>
<div v-else-if="status === 1">生效中</div>
<div v-else-if="status === 2">已过期</div>
<div v-else>--</div>
以上的代码都是 key-value 转换的例子,但是这些代码的可复用性,可拓展性都不佳。每次增加或删除一个键值对,都需要在各个地方修改。
统一管理枚举能减少冗余代码,提升开发效率,减少出错概率。是前端工程化中必要的步骤
# 📂 解决方案
对于不用场景下的 key-value 值使用,存在一些不同的方案。
# 1. 后端转换
部分展示类的内容使用此种解决方案,后端返回数据中同时包含 key 和 value,例如
{
status: 1,
statusStr: '已完成',
roleType: 0,
roleTypeStr: '管理员',
isActive: 0,
isActiveStr: '未激活'
}
优点
- 后端处理映射,保证了所有的key-value和后端数据库一致
- 在需要变动时,只需要修改后端配置文件即可,无需前端重新发布
- 对于前端来说很方便,随取随用
缺点
- 因为数据中只有当前key-value的值,而没有全部的,只能用于展示数据
- 需要返回双倍的字段,数量一多会难以维护
- 返回数据会向用户暴露key-value的映射关系,可能造成一些安全问题
- 如果在项目中存在需要使用所有的key-value,那此处的转换其实是多余的。如果使用的key-value是前端单独维护的,那么甚至有前后端不统一的风险(比如后端改了前端没改)
总结
后端转换在一些仅展示性的场景是可行的,但是限制非常多。
# 2. 接口提供
对于同时存取,或者带状态的key-value通常会提供映射接口
const res = await getUserTypeOptions()
if (res.code === 200) {
this.userTypeOptions = res.data.map(e => { // [{ code: 1, name: '管理员' }, ...]
return {
label: e.name,
value: e.code
}
})
}
优点
- 能够带参数存取,对一些权限类,实时数据类的key-value存取,不可避免使用接口
- 保证了所有的key-value和后端数据库一致
- 在需要变动时,只需要修改后端配置文件即可,无需前端重新发布
- 前端可以同时在展示和修改中使用
缺点
- 每次需要调用接口,增加网络消耗。若接口错误会直接阻塞业务
- 前端需要额外处理请求和转换这些 key-value
- 在存在大量的下拉列表筛选的情况下,可能会有非常多的接口请求(可以通过封装统一的映射查询接口解决)
- 可能存在不同组件相同接口的重复调用(可以通过缓存解决)
- 不同的接口可能由不同的后端开发,提供的key-value名称可能不一致(
codestatusnamevaluekey),需要单独处理(可通过后端规范解决)
总结
接口提供 key-value 是通用的方法,存在的一些问题也有对应的解决方案。但是目前大部分解决方案没有具体的规范和通用的实现。导致key-value接口依然存在大量可优化的问题。
# 3. 前端维护
将 key-value 完全存储在前端,对于一些不常变动的数据,前端维护是一种开销最小的方案
// 存在组件中
data() {
return {
statusOptions: [
{ label: '启用', value: 1 },
{ label: '禁用', value: 0 },
]
}
}
// 存在全局变量中
Vue.prototype.$enums.status = {
0: '禁用',
1: '启用',
}
<el-option
v-for="(key, index) in Object.keys($enums.status)"
:key='index'
:label='$enums.status[key]'
:value='key'>
</el-option>
- 优点
- 无需接口,降低代码复杂度,不依赖网络请求
- 全局使用,依赖单一,静态查找方便(需要统一存储)
- 代码提示友好(需要配置插件)
- 易于封装,使用JSON格式存储,使用统一的转换函数即可(需要封装)
- 使用范围广,可以同时在展示和修改中使用
- 缺点
- 可变性差,只适合长期不变的数据
- 无法保证key-value和后端数据库一致(可能存在前后端数据不一致的隐患)
- 需要临时变更key-value时,需要前端发版
- 对于某些带状态过滤的key-value,过滤逻辑同样需要前端维护
# 🚀 最佳实践
# 使用后端转换:
对于仅展示,或是提供有详情查询接口的一些常见 Table 数据,可以使用后端转换方式,但不是必须的。
# 使用接口查询:
大部分动态和即时性较强的key-value应该主要使用接口查询。
- 利用浏览器缓存机制,即http 304 状态码缓存不变的接口数据
- 后端应规范所有的key-value接口入参出参格式,方便统一处理
- 前端应该存在统一的loading封装,在UI侧提示用户该key-value还在加载中(例如el-select中的remote-loading)
- 后端最好能提供一个合成接口,根据入参一次性查询所有需要的key-value值,避免重复请求
- 💡 但是若使用了304缓存机制,提供该接口反而会不利于调用缓存中的数据,容易存在重复请求浪费资源
# 使用前端存储
若可预见变动较少的key-value,前端维护能够减少开发成本,提升效率
- 所有的 key-value 应该统一使用
JSON格式存储在指定的位置,并提供全局转换方法,和全局变量访问 - 所有的前端维护的 key-value 应该支持代码提示,增加开发效率
# 🔑 关键技术
# http 304缓存方案介绍
该方案不需要前端额外维护存储数据代码,符合业界标准,易于维护,并且速度很快。
Cache-Control / Expires
- http/1.0 使用 Expires 字段,http/1.1+ 使用 Cache-Control 字段(Expires 仍然有效)
常见Cache-Control的值(所有值见MDN (opens new window))
常见Cache-Control值 含义 no-cache 不用本地缓存,正常的向服务端请求,服务端怎么处理我们不用管 no-store 简单粗暴,直接从服务端拉取缓存 private 只能允许最终用户做缓存,最终用户即电脑、手机等等 public 允许中间路由或中间代理做缓存 max-age 设置缓存的最大过期时间 must-revalidate 一旦资源过期(比如已经超过max-age),在成功向原始服务器验证之前,缓存不能用该资源响应后续请求。 … … 强缓存和协商缓存
- 强缓存不发送请求,浏览器端直接调用缓存资源(from cache)
- 协商缓存不查询数据,服务端比对Hash值或token后,若数据相同返回304,不同查询新数据返回200
- 缓存失效规则
- 跳转链接,前进后退是,强缓存和协商缓存都有效
- F5 刷新时,强缓存失效,协商缓存有效
- ctrl+F5 强制刷新时,强缓存和协商缓存都失效