Open jiangjiu opened 6 years ago
在feed cms工作了大概半年的时间了,从最初的operate项目上手,到接手他人代码开发维护,再到contentcms平台多人协作,思考了一些cms相关的实践和经验,记录一下。
以下内容有在目前项目中经受住项目实战洗礼的最佳实践,也有工作中存在的问题与应对方案。
我提出的这些想法无关对错,也不会直接强行加在现在的业务中,毕竟实际业务场景需要衡量更多。
但重要的是,在工作和学习中持续思考~ 而且特别期待大家一起讨论技术问题~
同学们在工作中实践的其他优秀思想日后有机会再做总结。
cms系统很少会只有一个人开发,很难想象在多人协作的项目中没有代码规范会是怎么一种灾难的场景。
contentcms提供了一个栗子:
这个平台大概开发了三个月,同时有4人开发,src文件夹下的js文件和san文件一共有1300多个error,这还只是js部分。
后续同学进入开发时,拿到平台代码会很崩溃。
如何避免类似的问题继续出现呢? 如何在现有基础改良呢?
新坑以及激进派可以使用husky这个开源库配合代码规范来做预处理。
简单来讲,git commit/push前会跑一次代码检测服务扫描配置好的规则和目录,如果异常就禁止commit。
这点尤其感谢 @王兵同学 在VisualDL项目中的运用和指导,我对这种情况进行了简单的总结和实践,详见 使用husky解决git commit代码规范问题
上面的方案对已有项目并不友好,作为一个接手别人代码的同学来说,他没有那么多成本一次性改掉前面的问题。
这时候可以考虑使用lint-staged这个开源库,它借助git staged暂存区的概念,只针对暂存区里面的代码执行脚本。
也就是说,只针对新提交的代码做检测,以往的代码暂时不管,是改进项目的优秀解决方案。
详见使用lint-staged渐进式解决代码规范问题
上述两种都是在提交代码的时候做检查,此时开发基本已经完成。
更理想的情况是,如果开发实时运行过程中,构建工具编译检测到代码规范问题,就停止项目运行并报错,会更早发现错误,更容易避免问题代码污染,毕竟项目都已经跑不起来了。
详细设置及栗子可以参考https://github.com/vuejs/vue-cli 中的设置。
上面提到的都是构建工具编译时检测,但是,对于开发人员来说,没有比编辑器即时提示+一键修复更爽的开发体验了。
苦于san生态的不完善,相关的编辑器插件和eslint+prettier修复仍然在实践和探索中。 @周敏同学 对于这点很有想法,希望有精力的话可以一起完善这一点。
刚刚进入operate项目开发的时候,就看到 @令君学长 写的项目中有service服务,对于刚刚参加工作的我,自然是不明白为啥要在前端项目中分出这一层的。
(当时的service层只是生成一个axios实例,还有一些些前后端数据交互的格式转换)
随着在项目中的成长和经历,以及对sevice层的改写,慢慢体会到这一层的好处:
所有的请求信息都可以统一到一个或多个文件中,想要查看或搜索时十分清晰,避免了各个组件或页面内散落,难以追踪的问题。
如果有相同的请求,直接复用。
目前主流的请求库有axios、Fetch、Fly.js等,如果有一天业务中使用的某一请求库出了问题,可以在service内部直接替换改造,只要service接口不变,线上业务并无感知。
每次手动发起一个请求要写大量的重复性的内容,那么就封装一下重复性的东西,只暴露出内容不同的接口,写得少,看的顺心。
在之前的业务中请教了 @超凡学长 后写了一个命令式调用的Notification组件。
axios库针对同一实例请求的request和response,都可以采用统一的拦截器函数进行处理,比如请求返回异常时抛错。
拦截器配合命令式的toast/notification组件,从此在业务中只需要关注正确逻辑的处理了。
令君学长当时提到的是要在service层做好数据转换,view层拿到的即是完全和渲染逻辑匹配的数据结构,会让view层简单许多。
对大部分数据请求后直接渲染的数据来说,确实是。
但是组件间通信、url保存状态等等业务问题并不会走service,仍然会涉及数据转换的问题,所以这一点只能说是较为清晰的视图渲染。
有了这么多的好处,自然是大力推荐在cms系统中加入service数据层了。
我的mentor @孙微 提醒我改造service需要考虑向下兼容,因为我设计的service和之前的service文件接口有出入。
这着实有点让人难操作,service接口做兼容还好说,但是请求异常时要中断Promise并且不许在控制台抛出异常(因为以往的请求都是基于Promise.then处理业务逻辑,但没有自行catch)是比较蛋疼的,之前是通过Promise.reject来中断,但是这样一来,错误就回显示在控制台。
最后还是令君学长提供了下面的解决方案和相应的测试。
// service请求成功的拦截器函数 function successHandler(response) { const data = response.data; ... // 当请求成功,但状态码异常时,要提示并中断Promise if (statusError) { Notification.error(data.statusInfo); console.warn(data); return new Promise(() => {}); } ... }
我对这种pending状态的Promise是否有内存泄露提出了质疑,被令君学长放了个测试图瞬间打脸:
从测试场景来看,内存虽然升高但是一定时间后会被自动回收,并不存在内存泄露问题。
事后我调研了相关的原因,其实是因为new Promise的参数是一个空函数,v8引擎知道他不会有holding的资源,所以一定时间后就被gc自动释放了。
如果返回的Promise是递归pending的,或者函数内保存了一些全局变量等,这部分内存会持续无法释放。
具体的改造参见contentcms项目的src/common/HTTPService.js文件吧。
对于应用状态的管理一直很有争议,一些数据要放store还是放组件内部进行管理需要按业务场景来规划。
但是日常开发中,发现大家对San-Store的用法有些怪异,我把源码过了一遍,也和令君、erik都请教过心中的疑问,总结下项目遇到的问题吧。
详见 san-store默认值问题
详见san-store的connect问题
关于异步模型可以参见https://github.com/ecomfe/san-store/issues/6
这个issue里erik、灰大、大发等高T都有很详细的论述。
简单来说,同步的Action返回的一个updateBuider会立即更新数据状态,异步Action返回一个Promise/什么都不返回。
但是在实际场景中遇到的需求有可能是这样的:
this.actions.xxx(params) .then((){...})
按San-Store的设计,actions执行后是不可以接then方法去执行回调的,但是这样的业务场景很常见。
// page.js this.actions.fetchList({params,callback}) //store.js store.addAction('fetchList', function ({params,callback}, {getState, dispatch}) { ... return requestList(params).then(list => { callback() }); });
将回调函数放到action的参数中,异步行为放在actions中执行,完毕后执行callback。
这是一个最简单也最小侵入的方式。
//page.js requestList(params).then(res => { store.dispatch('xxx', res.data); ...blabla... })
另一种就是在页面或者组件中发起异步请求,回调里自行dispatch。
详见 通过url保存筛选条件的设计实践
详见 封装routeTo方法简化SPA开发
这一点主要是针对目前项目中单文件代码动辄一两千行来说的。 通过路由进行大型页面的切割,对视图组件进行必要的拆分和封装。 当一个组件的功能过于复杂时可以考虑使用HOC或者Mixin的方式进行解耦和代码复用。 这一点,在React和Vue生态上表现出异常明显的优势。
之前研究热加载写的一些东西。
san.nextTick与事件循环 webpack hot-module-replacement 原理&踩坑 san的热更新思路&实现 Vue-hot-reload-api 源码解析
简单总结,上述思考的热加载方案是针对san+router的,我对构建工具和模板做了整合,产出了一个san-hmr-template模板,并且实践在了脚手架工具、san-dux上。
好消息是之前给san-store提交的新特性createConnector的合并,使得目前这个并不完善的热加载方案有了整合san-store的能力。
这样一来,在业务中使用san+router+store全家桶的项目可以获得更爽快的开发体验了。
具体实践思路只要明白上述几篇,很容易就写出来了。
有兴趣的同学可以尝试。
San-CMS系统实践
在feed cms工作了大概半年的时间了,从最初的operate项目上手,到接手他人代码开发维护,再到contentcms平台多人协作,思考了一些cms相关的实践和经验,记录一下。
以下内容有在目前项目中经受住项目实战洗礼的最佳实践,也有工作中存在的问题与应对方案。
我提出的这些想法无关对错,也不会直接强行加在现在的业务中,毕竟实际业务场景需要衡量更多。
但重要的是,在工作和学习中持续思考~ 而且特别期待大家一起讨论技术问题~
同学们在工作中实践的其他优秀思想日后有机会再做总结。
代码规范
cms系统很少会只有一个人开发,很难想象在多人协作的项目中没有代码规范会是怎么一种灾难的场景。
contentcms提供了一个栗子:
这个平台大概开发了三个月,同时有4人开发,src文件夹下的js文件和san文件一共有1300多个error,这还只是js部分。
后续同学进入开发时,拿到平台代码会很崩溃。
如何避免类似的问题继续出现呢? 如何在现有基础改良呢?
激进派 使用husky
新坑以及激进派可以使用husky这个开源库配合代码规范来做预处理。
简单来讲,git commit/push前会跑一次代码检测服务扫描配置好的规则和目录,如果异常就禁止commit。
这点尤其感谢 @王兵同学 在VisualDL项目中的运用和指导,我对这种情况进行了简单的总结和实践,详见 使用husky解决git commit代码规范问题
改良派 lint-staged
上面的方案对已有项目并不友好,作为一个接手别人代码的同学来说,他没有那么多成本一次性改掉前面的问题。
这时候可以考虑使用lint-staged这个开源库,它借助git staged暂存区的概念,只针对暂存区里面的代码执行脚本。
也就是说,只针对新提交的代码做检测,以往的代码暂时不管,是改进项目的优秀解决方案。
详见使用lint-staged渐进式解决代码规范问题
新项目
上述两种都是在提交代码的时候做检查,此时开发基本已经完成。
更理想的情况是,如果开发实时运行过程中,构建工具编译检测到代码规范问题,就停止项目运行并报错,会更早发现错误,更容易避免问题代码污染,毕竟项目都已经跑不起来了。
详细设置及栗子可以参考https://github.com/vuejs/vue-cli 中的设置。
运行时的实时检查 + 一键修复
上面提到的都是构建工具编译时检测,但是,对于开发人员来说,没有比编辑器即时提示+一键修复更爽的开发体验了。
苦于san生态的不完善,相关的编辑器插件和eslint+prettier修复仍然在实践和探索中。 @周敏同学 对于这点很有想法,希望有精力的话可以一起完善这一点。
Service服务
原因
刚刚进入operate项目开发的时候,就看到 @令君学长 写的项目中有service服务,对于刚刚参加工作的我,自然是不明白为啥要在前端项目中分出这一层的。
(当时的service层只是生成一个axios实例,还有一些些前后端数据交互的格式转换)
随着在项目中的成长和经历,以及对sevice层的改写,慢慢体会到这一层的好处:
统一管理请求实例
所有的请求信息都可以统一到一个或多个文件中,想要查看或搜索时十分清晰,避免了各个组件或页面内散落,难以追踪的问题。
如果有相同的请求,直接复用。
隐藏HTTP请求实现
目前主流的请求库有axios、Fetch、Fly.js等,如果有一天业务中使用的某一请求库出了问题,可以在service内部直接替换改造,只要service接口不变,线上业务并无感知。
封装样板代码
每次手动发起一个请求要写大量的重复性的内容,那么就封装一下重复性的东西,只暴露出内容不同的接口,写得少,看的顺心。
自定义拦截器
在之前的业务中请教了 @超凡学长 后写了一个命令式调用的Notification组件。
axios库针对同一实例请求的request和response,都可以采用统一的拦截器函数进行处理,比如请求返回异常时抛错。
拦截器配合命令式的toast/notification组件,从此在业务中只需要关注正确逻辑的处理了。
较为清晰的视图渲染
令君学长当时提到的是要在service层做好数据转换,view层拿到的即是完全和渲染逻辑匹配的数据结构,会让view层简单许多。
对大部分数据请求后直接渲染的数据来说,确实是。
但是组件间通信、url保存状态等等业务问题并不会走service,仍然会涉及数据转换的问题,所以这一点只能说是较为清晰的视图渲染。
有了这么多的好处,自然是大力推荐在cms系统中加入service数据层了。
tricks/踩坑
我的mentor @孙微 提醒我改造service需要考虑向下兼容,因为我设计的service和之前的service文件接口有出入。
这着实有点让人难操作,service接口做兼容还好说,但是请求异常时要中断Promise并且不许在控制台抛出异常(因为以往的请求都是基于Promise.then处理业务逻辑,但没有自行catch)是比较蛋疼的,之前是通过Promise.reject来中断,但是这样一来,错误就回显示在控制台。
最后还是令君学长提供了下面的解决方案和相应的测试。
我对这种pending状态的Promise是否有内存泄露提出了质疑,被令君学长放了个测试图瞬间打脸:
从测试场景来看,内存虽然升高但是一定时间后会被自动回收,并不存在内存泄露问题。
事后我调研了相关的原因,其实是因为new Promise的参数是一个空函数,v8引擎知道他不会有holding的资源,所以一定时间后就被gc自动释放了。
如果返回的Promise是递归pending的,或者函数内保存了一些全局变量等,这部分内存会持续无法释放。
具体的改造参见contentcms项目的src/common/HTTPService.js文件吧。
San-Store
对于应用状态的管理一直很有争议,一些数据要放store还是放组件内部进行管理需要按业务场景来规划。
但是日常开发中,发现大家对San-Store的用法有些怪异,我把源码过了一遍,也和令君、erik都请教过心中的疑问,总结下项目遇到的问题吧。
San-Store默认值问题
详见 san-store默认值问题
San-Store的connect问题
详见san-store的connect问题
San-Store异步模型及实际场景处理
关于异步模型可以参见https://github.com/ecomfe/san-store/issues/6
这个issue里erik、灰大、大发等高T都有很详细的论述。
简单来说,同步的Action返回的一个updateBuider会立即更新数据状态,异步Action返回一个Promise/什么都不返回。
实际场景
但是在实际场景中遇到的需求有可能是这样的:
按San-Store的设计,actions执行后是不可以接then方法去执行回调的,但是这样的业务场景很常见。
解法1
将回调函数放到action的参数中,异步行为放在actions中执行,完毕后执行callback。
这是一个最简单也最小侵入的方式。
解法2
另一种就是在页面或者组件中发起异步请求,回调里自行dispatch。
San-Router
url保存筛选条件
详见 通过url保存筛选条件的设计实践
routeTo方法和部分源码解析
详见 封装routeTo方法简化SPA开发
组件划分
这一点主要是针对目前项目中单文件代码动辄一两千行来说的。 通过路由进行大型页面的切割,对视图组件进行必要的拆分和封装。 当一个组件的功能过于复杂时可以考虑使用HOC或者Mixin的方式进行解耦和代码复用。 这一点,在React和Vue生态上表现出异常明显的优势。
热加载
之前研究热加载写的一些东西。
san.nextTick与事件循环 webpack hot-module-replacement 原理&踩坑 san的热更新思路&实现 Vue-hot-reload-api 源码解析
简单总结,上述思考的热加载方案是针对san+router的,我对构建工具和模板做了整合,产出了一个san-hmr-template模板,并且实践在了脚手架工具、san-dux上。
加入san-store
好消息是之前给san-store提交的新特性createConnector的合并,使得目前这个并不完善的热加载方案有了整合san-store的能力。
这样一来,在业务中使用san+router+store全家桶的项目可以获得更爽快的开发体验了。
具体实践思路只要明白上述几篇,很容易就写出来了。
有兴趣的同学可以尝试。