mooc-extension
背景和动机
随着react新技术栈在中M的落地及逐步推进,前端应用架构给代码的规范化、质量、可维护性、可测试性、开发体验带来了一定的好处。但是在大家日常开发过程中,也碰到了效率低下的问题,主要归总为一下
- 应用架构中状态管理类库redux,虽然引入了reduxsauce减少了引入原生redux带来的文件太多的问题,但也因为这个,导致在action中actionName和reducer中的actionName因为reduxsauce的约定而写法不一致的情况出现
- 应用架构从代码的可维护性和扩展性考虑而出现的分层结构导致一个组件由多个文件构成,而各个文件职责不同,目前所分出来的文件有
- index.js:组件暴露给外部使用的唯一出口
- Comp.js:view层
- Comp.css:样式
- redux.js:逻辑层
- epic.js:副作用层
- api.js:接口层,主要用于ssr(这块为什么要独立出来和请求node服务时redux中间件中的副作用是异步的,目前没有很好的方案做到在node端让其可控的同步化,待后去寻求方案)
- 这么麻烦,为什么一定要拘泥于此应用架构
- 代码的规范,看别人的代码有章可循、对新人友好
- 代码可测试(虽然还没有,但不代表以后不会有)
- 易迁移,这个是只暴露出一个index口子的原因,并且需要引入组件外资源的时候尽量使用别名的方式引入。方面之后可能遇到的重构
- 一旦稳定下来便可以按照约定做批量话的生产出
可维护的代码
,提高效率
想法和理念
如何提高开发效率是前端恒久不变的话题,做这个插件的主要考虑点在于能够减少哪些代码的编写。在插件开发成本和组件通用性之间做权衡,尽量生成的代码使得开发的时候通过小的改动就能完成业务功能
原来的方式
- 每次写一个组件需要建立相关的文件夹,然后建立相关文件
- 在各个文件中写相关代码,这个过程中会有很多模版性的代码出现,并且有些地方是很容易出错的
- 写好组件,设置好路由、reducer和epic的引用
- 最后可以看到页面
运用vscode插件的方式
- 建立文件夹,在文件上点击右键后,点击
MOOC组件配置文件生成
按钮生成模版配置文件(json文件)
- 修改模版配置文件中的内容,大部分内容其中的模版会有很大的参考意义(这块需要前端在做迭代开发的过程之前做好设计,定好接口,这样很多代码都能够生成,提高效率)
- 在文件上点右键后,点击
MOOC组件生成
按钮生成所有相关的文件
- 设置好路由、reducer和epic的引用,修改相关缺失逻辑
- 最后可以看到页面
以后期待的方式
- 在其他地方生成模版配置文件
- 走vscode的接下来流程
主要的理念
整体的代码生成分为两个部分
- 第一个部分是view层的jsx代码片段的生成(涉及到的只有view层)
- 第二个部分是以action Name为核心的相关逻辑代码的生成(涉及到redux、epic、组件这些文件中的代码片段生成,api目前暂未开发)
jsx代码片段以及规范(这块主要目前针对于后台,以后会添加更多)
- 和策划以及视觉讨论过,从页面的级别抽象出各个页面模块(到时候补充图)
- 目前有的代码片段有breadcrumbs、panel、criteria、table
- 抽象出两部分
- 一部分会在mooc-extension工程中作为插件使用的原材料维护起来
- css单独抽离出来存放在mooc-bussiness-components工程中的
src/core/components/common/Templates/templates.css
中
- 这块代码需要和策划以及视觉沟通,通过多个迭代的打磨,生成模版页面,抽象到storybook中去
逻辑代码片段
- redux文件
- action name
- 名称:以名称+For组件名的形式命名
- 参数:以字符串数据的形式声明式的呈现
- 默认参数:提供配置项用于初始化的时候生成
- reducer的声明:提供配置项用于生成代码片段
- epic文件
- epic方法的代码片段
- 方法的整体结构
- 针对有请求的方法可进一步定制化url和body
- epic方法的导出片段
- 组件文件:extension.ts,主要是状态订阅的代码片段
- api文件:暂时未实现
模版配置文件
{
"project": "rc",// 目前没有用到
"entryName": "index",// 入口文件的文件名
"compName": "Test",// 组件和其css文件的名称
"reduxName": "redux",// redux文件名称
"epicName": "epic",// epic文件名称
"language": "js",// 用于扩展,以后获支持ts
"els": [// 组件view层中的模版用此中内容生成
{// 面包屑以后会统一和应用架构做融合
"name": "breadcrumb",// 面包屑块的固定名称
"stubs": {
"breadcrumbListStub": [// 面包屑的配置数组,没有链接href就不需要配置
{
"text": "一级菜单",
"href": "www.icourse163org"
},
{
"text": "二级菜单",
"href": "www.icourse163org"
},
{
"text": "三级菜单",
"href": "www.icourse163org"
}
]
}
},
{// 文案模块
"name": "panel",// 文案模块的固定名称
"stubs": {
"panelTitleStub": "页面标题",// 页面标题
"panelDescendStub": false,// panelDescendTextStub是否显示,为false的时候不显示,如果是链接则作为该链接的地址
"panelDescendTextStub": "返回上一层",// 和标题同一行右边的按钮
"panelDescriptionStub": "说明文字:页面说明是针对当前页面的全局介绍。",// 描述性文案
"panelLinkStub": false,// panelLinkTextStub是否显示,为false的时候不显示,如果是链接,则作为文字链接点击的地址
"panelLinkTextStub": "文字链接"// 文字链接的文案
}
},
{// 表格上方的查询模块
"name": "criteria",// 查询模版的固定名称
"stubs": {},
"comps": [// 配置在此处的组件将按照顺序生成代码
{
"type": "input",
"placeholder": "请输入姓名",
"defaultValue": "",
"stateValueName": "name",// const name = useSelector(state => state.teach.college.name);
"changeEventName": "nameChange",// onChange事件代码片段
"position": "left"// position在criteria中有两种,一种是靠左的一种是靠右的
},
{
"type": "select",
"placeholder": "请输入性别",
"defaultValue": "1",
"changeEventName": "gendarChange",
"listName": "gendarList",
"stateValueName": "gendar",
"position": "left"
},
{
"type": "search",
"placeholder": "请输入课程",
"defaultValue": "",
"stateValueName": "course",
"changeEventName": "courseChange",// onChange事件代码片段
"searchEventName": "search",// search事件代码片段
"position": "left"
},
{
"type": "button",
"buttonName": "新增",
"buttonType": "primary",
"clickEventName": "add",// onClick事件代码片段
"position": "right"
},
{
"type": "button",
"buttonName": "批量更新",
"clickEventName": "batch",
"position": "right"
},
{
"type": "button",
"buttonName": "导出数据",
"clickEventName": "exportData",
"position": "right"
}
]
},
{
"name": "table",
"stubs": {},
"datasourceAction": "getDatasource",
"columns": [// 表格字段配置
{
"key": "id",
"title": "id",
"hide": true // 列是否隐藏,不隐藏不用配
},
{
"key": "schoolId",
"title": "schoolId",
"hide": true
},
{
"key": "collegeName",
"title": "学院名称"
},
{
"key": "opts",
"title": "操作",
"type": "opts",// type为opts的时候表示操作列
"comps": [// 操作列中的组件列表,目前支持编辑和删除
{
"custom": "edit",// 表示生成编辑按钮
"text": "编辑",
"handler": "updateHander"// 编辑按钮的事件片段
},
{
"custom": "delete",// 表示生成删除按钮
"text": "删除",
"handler": [
"delHandler",// 删除按钮的事件片段
"cancelConfirm"// 删除按钮的取消删除事件片段
]
}
]
}
]
}
],
"gearing": {// 这个节点中配置代码逻辑相关的内容
"data": {
"stateBranch": "college.faculty",// redux中的state树的前缀
"initialState": {// 初始化的状态
"criteria": {},
"filter": {},
"query": {},
"datasource": []
}
},
"taps": [
"datasource",
"query",
],// 生成 const xxx = useSelector(state => state.teach.college.xxx);
"flows": [// 按照actionName为中心在redux、epic和组件文件中生成相关的逻辑代码,以后会扩展到api
{
"name": "getListTrigger",
"params": null,
"usedInComp": true,
"hasReducer": false,
"hasEpic": true,
"noRequest": true, // 在Epic中无请求
"next": "getList"
},
{
"name": "getList",// actionName,生成的名称会自动带上ForComp的后缀,为了保证actionName和epicName的唯一性
"params": null,// 所需要传的参数,以字符串数组的形式写
"usedInComp": true,// 是否会用在组件文件中,就是会否在view层订阅该action
"hasReducer": false,// 是否存在reducer,就是会否在redux文件中生成reducer相关的代码模版
"hasEpic": true,// 是否存在epic,就是或否通过这个名称生成epic的代码片段
"url": ".rpc",// 如果有epic代码模版生成的情况下,url会被使用在fetch函数中
"body": {
"schoolId": "state$.value.college.root.schoolId",
"schoolName": "state$.value.college.root.schoolName"
},
"next": "getListSuccess"// 指明下一个action的名称
},
{
"name": "getListSuccess",
"params": null,
"usedInComp": false,
"hasReducer": true,
"reducerStates": {
"query": "action.query",
"dataSource": "action.list"
},// 在reducer中返回状态中自动回填
"hasEpic": false,
"next": null
},
{
"name": "delData",
"params": null,
"usedInComp": true,
"hasReducer": false,
"hasEpic": true,
"url": ".rpc",
"body": {// 如果存在body,表面该请求会有参数,并生成参数代码片段
"id": "id"
},
"next": "delDataSuccess"
},
{
"name": "delDataSuccess",
"params": null,
"usedInComp": false,
"hasReducer": true,
"hasEpic": false,
"next": "delDataSuccess"
},
{
"name": "search",
"params": null,
"usedInComp": true,
"hasReducer": true,
"hasEpic": true,
"next": "getList"
}
]
}
}
插件原理
重要文件
- 编译文件:compiler.ts,用于将模版文件编译成可供插件代码文件使用的json文件(templates/codeSnippets.json)
- 模版文件:各种模版文件(不介绍了,自己有兴趣去看)
- 模版骨架文件
- comp.js
- redux.js
- epic.js
- entry.js
- 其他模版文件
- baseComp.tpl
- comp.tpl
- redux.tpl
- epic.tpl
- compBreadcrumb.tpl
- compPanel.tpl
- compCritreia.tpl
- compTable.tpl
- 插件代码文件:extension.ts,插件的主要功能
命令与发布
命令
- 安装vsce:npm i vsce -g
- yarn compiler:将代码模版文件编译成json文件
- yarn watch:用于监控代码改动,开发的时候需要开启
- vsce package:用于打包vsix文件
发布
一图胜千言