-
Notifications
You must be signed in to change notification settings - Fork 0
Description
本文主要记录最近工作中对Hook、数据模型和plop工具的探索
React Hook编写范式
在React16 hook刚出来的时候,其实就有尝试过在项目中使用hook的经历,但是当时给我的感觉是,没有class便捷,而且代码组织很混乱,容易变量满天飞,再加上一个组件写起来经常会超过上千行代码(class component的思维)。写出来的代码也容易有一些变量监听混乱的问题,所以后面在小的组件才会用fc,而在页面级别的组件还是传统的class component。
其实我们在写组件的时候,只需要区分好实现的细节和逻辑的节点就好,class之所以看上去会比较有条理,是因为天然的把这些逻辑都写在一个成员函数里。类下面的每个成员函数代表一个处理的细节,编辑器中直接把代码折叠后,就会发现class写的比较有条理,想找什么细节直接点开去找。
FC或者hook也是一样的,举最近做的一个 新建/编辑签署页为例,提取简单的业务逻辑节点。
- 确定页面是新建还是编辑
- 编辑页需要根据paperNo去请求数据填充,新建页则做一些初始操作
- 提交、保存、确认、取消等操作
- 其他行为,比如自动保存草稿与恢复编辑草稿等
所以我们的组件大概可以是这样
function SignoffForm() {
const [initState] = useInitDataWithState()
const [handleSubmit] = useSubmit()
const [hasDraft, {
removeDraft,
fillDraft
}] = useAutoSave()
return (
<form> ... </form>
)
}
虽然不一定这些hook是可以被复用的,但是这样抽离出去,整个组件的逻辑就清晰很多,别人来接手看代码的时候一看就知道这个组件大概的功能点在哪。想要改某个功能点的时候再去看实现的细节,比如说初始化数据的时候有bug,那直接去看 useInitDataWithState 这个hook就可以,不需要在一大堆意大利面里找代码。
而里面的一些逻辑抽离成hook之后还可以和其他的组件复用,比如 送签表单需要草稿功能,付款表单也需要草稿功能,那 useAutoSave 就可以直接在两边都用了,这在 class Component 里面是无法想象的。
再者比如最近在做一个公益的小程序,用的框架是Taro,如果在pc或app端用Hook实现了一些逻辑的话,那么在小程序上也是可以直接拿来就用的,比如ActionSheet有展示和隐藏的逻辑,那是不是可以直接用 useToggle 来管理这个状态切换呢?
这和数据模型有点共通之处,现在在项目中,会把业务模型抽离出来独立于框架,这样后面换个vue或angular框架,内部的业务逻辑也可以直接共用,同理还有headless component。其实最近在工作的时候都有很留意headless的思想,因为项目即将进行大范围的重构,这些脱离UI的逻辑实现后续复用起来会更方便。
回到Hook上面来,现在一般页面的组织形式如下:
signoffForm
├── components 当前页面的组件
│ ├── field.jsx
│ ├── formItem.jsx
│ ├── index.js
│ ├── oppositeCorporations.jsx
│ ├── oursCorporations.jsx
│ └── selectFormItem.jsx
├── helper.js 与UI、State无关的函数,一般是数据转换
├── hooks.js 页面相关的逻辑,实现细节抽离成Hook
├── index.js
├── model.js Redux model
└── signoffForm.less 样式表
shared
├── models
│ └── signoffModel 送签模型,负责处理送签各个字段的逻辑,比如必填,展示,字段关联等
多端统一数据模型
在我所在的项目组里由于是投资领域的2B业务,平时主的工作是数据处理,业务逻辑,各种字段的关联关系,表单处理等,比较少接触动效领域。
工作中会发现很多字段的处理逻辑其实是一致的,比如一个字段format是四舍五入还是小数点后六位直接舍去,单位是需不需要用mn,bn,tn等,这些处理方式在列表页、详情页、表单页其实处理的逻辑都是一致的。
再或者比如一个表单字段,可能需要 name、default、label、type、validator、optionKey、optionValue、placeholder、hidden、drop等等十几配置信息去描述他,这些以往我们可能都是直接写在jsx里面,比如:
<form-field name="category" default="" label="文件类别" validator="required(xxx)" optionKey="type" optionText="name" .... />
pc这些写,app也这么写,后面改的时候又得两处改,更关键的是,如果多端因为历史债务原因使用不同的技术栈,那么端与端之间的复用更是无从说起。
所以最近在做项目的某个业务时,转换一下思路,将字段的逻辑抽离成一个model,脱离技术栈的实现。有点像DDD,在写业务之前先思考这个模块到底涉及了哪些逻辑,哪些字段,这些字段与字段之间的关系是什么,然后单纯用js来描述这些属性。
最后再写一个桥接,将这些model和view映射起来,比如react,那可能就是变动的时候触发setState更新视图。
这么写的好处是什么?
将这些复杂的业务逻辑抽离出view层,不仅在angular和react可以复用同一套业务逻辑,后面重构的时候,也可以最大程度的复用这些代码,只要是能跑js的都能运行。
业务变动的时候也不会改一个端之后忘了修改另一个端,如果只是样式变动则只需要修改某个端的ui呈现。
其实这块就是对应上面 React Hook编写范式
中的 shared > models > signoffModel
class Demo1 extends BasicMeta {
static name = '示例文案'
static default = ''
static label = i18next.t('signoff:demo')
static type = Number
static formValidator = required(Demo1.label)
static optionKey = 'type'
static optionText = 'name'
static valueType = 'key'
static fieldOptionsKey = 'demo1'
static placeholder = generatePlaceholder(Demo1.label)
static hidden() {
return !isCNY(this.team)
}
static drop(data, key) {
return this.$views[key].hidden
}
}
利用 plop 规范化代码模板
最近做项目的构建工具时,顺手加了一个 plop 功能, plop 官方的描述是: Consistency Made Simple。
简单来说我们可以利用它做一些项目中的模板生成,比如 components
pages
modules
的生成。这里有个很简单的示例 plop-demo。
git clone [email protected]:jsonz1993/plop-demo.git
cd plop-demo
npm i
npm run start
// 新开一个shell
npm run new
如果我们选了modules 并输入modules 名,可以发现工具自动帮我们把modules的模板拷贝的对应的位置,并且修改了路由文件。这只是一个简单的实例,在项目中我们的模板会更加具体,路由器及其他相关配置也会更多,这块可以继续看官方文档。https://github.com/plopjs/plop