我们需要怎样的 Service

14 世纪,英格兰的逻辑学家奥卡姆在他的《箴言书注》中说「不要浪费过多的东西,去做那些用较少的东西同样可以做好的事情」。后来这句话被简化为「奥卡姆剃刀原理」,即:如无必要,勿增实体。奥卡姆剃刀在各个领域都有他的运用,他不是一个公理,没有严谨的推导过程,但他却是一个在实践中被证明非常有效的解决问题的手段。
在编程世界里,有太多我们习以为常的东西,我相信存在即合理,同时我也相信存在都有前提,而前提会随着时间变化甚至消失。下面我想跟大家探讨下,我们前端项目中那些应该被剃刀剃掉的东西。
前端项目里的 service 层 在一个前端项目中,一般包含以下文件目录:

  • containers:页面
  • components:组件
  • utils:工具方法
  • routes:路由
  • services:数据服务
  • index.js 入口文件
我们的业务代码基本都在 containers components 里,utils 和 routes 也是必不可少的,但仔细思考我们就会发现,这里有个 services 文件夹,他被称为数据服务层,是我们跟后端打交道的。这一层真的需要吗?
我们来看看大家是怎么使用 service 的。
// services文件夹下的 accoutService.jsimport { post } from '@/utils/request'; // 获取账号列表 export const getAccountsList = params => post('/api/accounts.json', params); // 新增账号 export const insertAccount = params => post('/api/insertAccount.json', params); // 更新状态 export const updateAccount = params => post('/api/updateAccount.json', params); // 校验账号查询 export const checkAccount = params => post('/api/checkAccount.json', params); -------- 使用 ---------import { getAccountsList } from '@/services/accountService'const App = () => { const [name, setName] = useState(''); useEffect(() => { getAccountsList().then((res) => { setName(res.name); }); }, []) return {name}; };

从上面的代码我们可以看出,services 文件下基本是一些模板代码,偶尔有少见的一些数据转换。这些内容对于我们的业务代码来说,都是非业务相关的,写这些模板性的控制代码真的有必要吗?
service 里包含什么?
  • 数据转换逻辑 converHandler
  • 数据请求工具 request
  • 请求地址定义 url
  • 全局拦截器 interceptor
  • 附加功能 openApi
数据转换逻辑 converHandler:并不通用,有的一个请求在不同的页面需要走不同的转换逻辑,这些转换逻辑一般会写在调用位置的代码里,我也建议这么做,因为数据转换也是这块某个 container 的功能,而且为了方便测试,建议添加 handler.js 将转换逻辑抽离出来。
数据请求工具 request:主要是封装各种请求,这部分需要统一。非业务相关,可以提出来。
请求地址定义 url:这部分是强业务相关的,不应该放到 service 里,而是作为 service 的一个配置,由外部输入。
全局拦截器 interceptor:处理一些通用的业务状态码,比如编辑成功 10001,这部分也是强业务相关的,而且相对比较复杂,但是可以通过配置 schame 来描述,后面再讲。
附加功能 openAPI:如果你系统的接口想让别的系统复用,比如 MTEE 基础平台的接口需要复用给运营平台,那么前端需要提供领域物料,领域物料里会发请求,发请求要解决跨域、登陆、授权的问题,openAPI 应运而生。
综上可以看出,service 层只需要一些统一的逻辑处理和配置文件就能描述清楚,甚至我们可以把 Service 层简化为
$$service = request + config$$
我的 service 包 由此,我希望能设计这样一个 service 包,他需要包含下面的功能:
请求
支持常见的 get post jsonp 请求,以及对于这些请求的附加方法,比如 debounce、throttle、缓存、loading 等功能。也可以提供大家比较喜欢的 hooks API。
接口配置
一个接口包含域名 domain,地址路径 path,请求方法 method,参数 params,一些常见功能的开关,比如开启防抖 { debounce:true } 。参数的配置里,可以添加该参数的基本属性,比如是否必选 { require: true } ,这样包内可以对参数做必要的校验,这样可以保证非法数据传入后台。
环境切换
环境切换是一个非业务相关的功能,他不应该硬编码到代码里,带到线上。他应该只是一个配置,尽量与代码脱离,因此是用浏览器插件来切换,就是一个很好的方法。可以设计 service 包接收一个 domainMap,这个 domainMap 来自 window.GlobalConfig 下的某个变量,浏览器插件可以动态改变这个变量,就可以做到环境的切换了。
我们需要怎样的 Service

    推荐阅读