We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
在上篇查缺补漏 React 状态管理发出之后,留下了一个问题,那就是 hox 真的很香吗 ? 香不香我不知道,下边是我使用 hox 之后的一些感想...
hox
不得不说,实在是太难顶了,最后改了一下源码,实现了一个低配版的 model tree ...
以下内容是个人感受,仅代表个人观点,如有错误,还望指出 🤝 ~
网上搜了一下 Hox 文章,相对较少,并且大部分是介绍型的文章(就是告诉你这是什么库,怎么用),经过在项目中,落地实践了一个模块之后,也算是有所小收获,下边是我的一些感受...
Hox
想跟大家探讨如何“划分颗粒度”的问题,怎么理解呢?比如我们现在有个 reducer,里边有近 40 个字段,这应该很常见吧,对于一个复杂模块,存在一个 reducer 拥有三四十个字段,是个再正常不过的事情了~
问题在于我们如何定义“细”这个纬度,这个问题跟 hox 是没关系的,它只是能将你的 hooks 变成持久化,全局共享的数据,我们讨论的是 : 该写一个 model hooks 还是写 n 个 model hooks ?
这个问题在我重构实践的时候,也一直在困扰我,最后还是决定,分三种情况,如下 :
下边展开说说我为什么这么设置 :
其实我不太赞同这样的颗粒度划分,为什么呢? 划分的太细了,你想啊,如果一个小模块,有 10 个字段,那你就对应有 10 个文件,really ? 那为什么我还要说这种情况呢?因为如果你这个字段比较独立,与其他字段毫无关联,同时比较复杂,可能你会在你的 model 里边做一些副作用操作,那么写成这样,可能相对比较好 ?
哪有好与不好,最终看你自己如何定义,按照你自己喜欢的写就好了
但是这种情况,就会导致你代码量有点大,举个例子,你这个组件 index.js 需要这 10 个数据,那么代码就会是这样的
import useClassModel from '@src/model/user/useClassModel' import useSubjectModel from '@src/model/user/useSubjectModel' import useChapterModel from '@src/model/user/useChapterModel' // 此处还需import 7个文件 ... function User() { const { classId, changeClassId } = useClassModel() const { subjectName, changeSubjectName } = useSubjectModel() const { chapterName, changeChapterName } = useChapterModel() // 此处还有代码... }
然后你修改时候,逻辑代码可能就是这样
function changeSelectClass (class) { changeClassId(class.id); // 修改班级id changeClassName(class.clsname); // 修改班级名 changeSubjectName(class[0].subjects[0].subjectName) // 修改班级,默认切换此班级下的第一个学科 changeSubjectCode(class[0].subjects[0].subjectCode) changeChapterName(class[0].subjects[0].subjectCode.chapter[0].chapterName) // 默认选中此学科下的第一个章节 changeChapterCode(class[0].subjects[0].subjectCode.chapter[0].chapterCode) }
想表达的就是,这种方式,你的代码量会上来,不过问题不大,个人认为这种代码阅读性还可以
这种情况我建议是“强关联”的时候这么写,什么是强关联? 比如 classId 和 className,subjectCode 和 subjectName 这种,就是一个改变,另一个肯定也会改变,这种情况可以写在一个 useModel 中。如下
function useSelectSubject() { const [subjectCode, changeSubjectCode] = useState(undefined) const [subjectName, changeSubjectName] = useState(undefined) const setSubjectCode = (subjectCode: string) => changeSubjectCode(subjectCode) const setSubjectName = (subjectName: string) => changeSubjectName(subjectName) return { subjectCode, subjectName, setSubjectCode, setSubjectName, } }
当然你也可以用一个对象包这两个字段,一切随你喜欢,我只是想表达,既然是有关系的,一个变另一个也变的,可以放在一块
会不会存在一种情况,就是你可能有好几个字段,这好几个字段可以按照上边 “强关联” 分割成 n 个,但你又不想有 n 个 .js 文件,并且可能这 n 个文件是这个模块公用的。
.js
总不能新建一个文件夹,然后将 n 个文件都写成 useN1Model.js、useN2Model.js、useN3Model.js 吧,不会吧不会吧 ?
我个人建议是 : 该文件控制在 150 行代码以内,大概 5 个 model 即可,超出 5 个,则拆分,当然,这只是我个人的建议而已啦
// 比如我这个就叫做 useBaseModel.js import { createModel } from 'hox' function useSelectClass() {} function useSelectSubject() {} function useSelectChapter() {} export default { useSelectClass: createModel(useSelectClass), useSelectSubject: createModel(useSelectSubject), useSelectChapter: createModel(useSelectChapter), }
在使用的时候只需要 import 一次就好了,而且这个在阅读性上,也还行,毕竟这三个 useModel 都是具有关联性的
import
useModel
import useBaseModel from '@src/model/user/useBaseModel' function User() { const { className } = useBaseModel.useSelectClass() const { subjectName } = useBaseModel.useSelectSubject() const { chapterName } = useBaseModel.useSelectChapter() }
官方对 hox 特性的说明 : 随地可用,所以 model 层的文件目录规范也要统一,虽然之前使用 redux 的时候,业务 reducer 也是放在业务文件夹下,但是因为 combineReducer 的存在,我们是可以在 store 的入口文件 index.js 看到所有 import 进来的 reducer
combineReducer
但是 hox,你不使用 createModel 时,它就是一个业务 hooks,随时建,随时用,会导致后期维护非常麻烦,特别是无 dev-tools 下,你根本不知道哪些 hooks 是变成了持久化、全局共享的 model hooks
会不会有一种情况,那就是 A 在开发的时候,一开始它写的就是一个业务 hooks,后边它需要把这个数据变成全局共享,于是它直接在当前目录下,直接用 createModel 包一下,完事,后边的开发人员发现有人这样写了,照猫画虎,渐渐的就成为了一种"规范"... 这就是典型的“破窗效应”啊
createModel
破窗效应 : 一幢有少许破窗的建筑,如果那些窗不被修理好,可能将会有破坏者破坏更多的窗户。一面墙,如果出现一些涂鸦没有被清洗掉,很快的,墙上就布满了乱七八糟、不堪入目的东西
我建议,原来我们写 store 的文件夹,改成 model,同时 model 下的文件夹架构和业务一致。
网上查询 hox 相关文章,几乎很少,github 目前 star 519,issues 9 ,算小众的库,当然这也不是一个大问题,因为你不使用它的 createModel 将数据持久化,那么本质上就是自定义 hooks
star 519
issues 9
如果有问题,大部分都还是自己逻辑问题,可能自定义写的 hooks 有点毛病,只需要自己 log 排查问题即可。
这才是痛点 !!!你想想,我们使用了 createModel 包裹之后,如何知道这个数据是否真的被持久化、全局共享呢 ?
常规操作就是,在组件中 import 这个数据源,然后 console.log 打印看看,但是如果是使用redux,那么我们可以在 redux-devtools 插件下,直接看到这颗 state tree 的。这就很方便了。
比如我想看看 user 下是否存在我想要的字段,那么我只需要在插件中看一下 state tree
还有一种场景,比如我们在 A 组件中,发送请求,然后往 model 中修改了一些数据,这些数据在 B 组件才用到,A 组件用不到,在没报错的情况下,我们无法知道数据是否真的被修改
实在是很难过了,我总不能真的去 import 进来,然后去 console.log 吧?于是跟鹏哥一起琢磨了一下,直接修改源码,然后实现一个简单的 model tree 吧 ...
这里有个问题,那就是我们给 createModel 传递的是一个 hooks,是你在 useSelectClass 中,return { classId, changeClassId } 这个对象,所以你是不知道当前这个 hook 的名称,为此我们给这个 hooks 注入一个命名空间
function useSelectClass() {} useSelectClass.namespace = 'useSelectClass' export default { useSelectClass: createModel(useSelectClass), }
然后修改createModel源码,原来只需要给 Executor 传递 2 个字段,现在给它支持 namespace
Executor
// createModel.js render( <Executor onUpdate={(val) => { container.data = val container.notify() }} hook={() => hook(hookArg)} namespace={hook.namespace} // 新增此字段支持 /> )
function Executor(props) { // 原逻辑代码,巴拉巴拉xxxx // 下边这段代码是新增的 if (!window.hox) { window.hox = {} } let keys = Object.keys(data) let maps = {} keys.forEach((key) => { if (typeof data[key] !== 'function') { maps[key] = data[key] } }) window.hox = { ...window.hox, [props.namespace]: { ...maps }, // 以namespace为key,过滤function后的数据为value } return null }
此时去打印 window.hox ,是有数据的,又离成功近了一步,但是问题来了,我们修改 model 值之后,我们要实时响应,该这么办?记得之前看 Vue 双向绑定原理,哦豁,有了,Object.defineProperty 用起来
Object.defineProperty
在我们写的 dev-tools 组件中,监听一下 window.hox ,将最新的 model 数据,存入 state,然后搭配 Ant Design Tree 组件,最终渲染
componentDidMount() { window.b = {}; const _this = this; Object.defineProperty(window, 'hox', { set: function(value) { window.b = value; _this.setState({ model: value }); }, get: function() { return window.b; } }); }
原本我们用的不是 window.b = {} , 而是 let a = {},然后在 set 中,a = value,但是发现会存在问题,什么问题? 小伙伴们可以想一下,或者实践一波
这是最费时的了,我们要将这种数据格式,转成 Tree 想要的数据格式,写了好多遍都没写对,后边还是鹏哥给了提示,感谢鹏哥
// 我们的数据 window.hox = { useUserModel: { name: '彭道宽', school: [ { s_name: 'xxx大学', time: '2015-2019', }, { s_name: 'xxx高中', time: '2012-2015', }, ], currentCompany: { c_name: 'CVTE', c_job: '前端工程师', }, }, }
// Ant Design Tree 数据 antdTree = [ { title: 'useUserModel', key: '0', children: [ { title: 'name', key: '0-0', children: [], }, { title: 'school', key: '0-1', children: [ { title: 's_name', key: '0-1-0', children: [], }, ], }, ], }, ]
上边的我就不一一写的,问题在于,如何遍历 ? 小伙伴们可以想一想 ? 不想的就直接到下一步吧
format = (model) => { let result = [] const deep = (children, value, idx) => { Object.keys(value).forEach((key, index) => { let temp = {} temp.key = `${idx}-${index}` // 这是我对title的处理,因为非function/object,直接就渲染值在后面 temp.title = this.renderTitle(value[key], key) temp.children = [] if (this.checkIsObjectOrArray(value[key])) { // 如果是对象或者数组,那就递归 deep(temp.children, value[key], temp.key) } children.push(temp) }) } Object.keys(model).forEach((key, index) => { let temp = {} temp.key = index temp.title = this.renderTitle(model[key], key) temp.children = [] if (this.checkIsObjectOrArray(model[key])) { deep(temp.children, model[key], index) } result.push(temp) }) return result }
效果如图 👇
这个还是有点小问题的,后期打算优化一波,问题不大,大家知道一下思路就好~
如果我代码这么写,你们觉得有什么问题吗?
function useSelectClass() { const [classId, setClassId] = useState('') const changeClassId = (classId) => setClassId(classId) useEffect(() => { // 一顿操作,然后把classId改了 const data = handle() setTimeout(() => { setClassId(data) }, 10000) }) return { classId, changeClassId } } export default createModel(useSelectClass)
你们觉得我这么写,会不会有问题呢 ?有,如果我作为使用者,我取这个 classId 值,那么在这个 10s 内,我取的是一个 undefined,然后 10s 后,突然 classId 变了;还有一种可能,你依赖 classId,当 classId 变了之后你发送请求,10s 之前你发现没问题,10s 之后,哦豁,怎么突然多发了一遍请求 ?
所以我认为要约束,规定 model 的 hooks 不要写副作用,它就存粹的 getValue、setValue 就好了
或许有人会说,那你也可以在 redux 这么做啊,不是吧,reducer 可是一个纯函数啊,小老弟,不知道纯函数的去科普一下哈
还有一个问题,不应该说是缺点,就是想讨论 : 相对于 redux 的集中式管理 store 与 hox 的分散式且无 dev-tools 情况下的管理
如果我是一个新人,我就只会 react,什么 redux、什么 dva、mobx 我都不会,我就只想写一些小项目,那么我会选择 hox ~~
对于小项目,可能需要 store 存储的字段比较少,那么配上简陋版本的 dev-tools,那么hox 或许比较适合此场景,如果引入 redux 那套 action->saga->reducer 等,前期投入比较大;但是对于大项目,还是用 redux 吧,毕竟 redux 算比较成熟,生态圈也很丰富
个人还是觉得 hox 这个设计思想还是挺不错的,但是我很担心一点,那就是 : 这会不会是一个 KPI 的产物,到后期没人维护,也没人更新。
好了,对于 hox,今天就聊到这,大家散了散了。最后建议,感兴趣的可以去看看源码,确实还可以,涨知识了...
文章发出之后,得到umijs团队中的一名开发成员评论:表示不用怕没人维护吗,问题不大...
The text was updated successfully, but these errors were encountered:
No branches or pull requests
在上篇查缺补漏 React 状态管理发出之后,留下了一个问题,那就是 hox 真的很香吗 ? 香不香我不知道,下边是我使用
hox
之后的一些感想...不得不说,实在是太难顶了,最后改了一下源码,实现了一个低配版的 model tree ...
以下内容是个人感受,仅代表个人观点,如有错误,还望指出 🤝 ~
正文开始
网上搜了一下
Hox
文章,相对较少,并且大部分是介绍型的文章(就是告诉你这是什么库,怎么用),经过在项目中,落地实践了一个模块之后,也算是有所小收获,下边是我的一些感受...如何划分颗粒度
想跟大家探讨如何“划分颗粒度”的问题,怎么理解呢?比如我们现在有个 reducer,里边有近 40 个字段,这应该很常见吧,对于一个复杂模块,存在一个 reducer 拥有三四十个字段,是个再正常不过的事情了~
问题在于我们如何定义“细”这个纬度,这个问题跟 hox 是没关系的,它只是能将你的 hooks 变成持久化,全局共享的数据,我们讨论的是 : 该写一个 model hooks 还是写 n 个 model hooks ?
这个问题在我重构实践的时候,也一直在困扰我,最后还是决定,分三种情况,如下 :
下边展开说说我为什么这么设置 :
其实我不太赞同这样的颗粒度划分,为什么呢? 划分的太细了,你想啊,如果一个小模块,有 10 个字段,那你就对应有 10 个文件,really ? 那为什么我还要说这种情况呢?因为如果你这个字段比较独立,与其他字段毫无关联,同时比较复杂,可能你会在你的 model 里边做一些副作用操作,那么写成这样,可能相对比较好 ?
但是这种情况,就会导致你代码量有点大,举个例子,你这个组件 index.js 需要这 10 个数据,那么代码就会是这样的
然后你修改时候,逻辑代码可能就是这样
想表达的就是,这种方式,你的代码量会上来,不过问题不大,个人认为这种代码阅读性还可以
这种情况我建议是“强关联”的时候这么写,什么是强关联? 比如 classId 和 className,subjectCode 和 subjectName 这种,就是一个改变,另一个肯定也会改变,这种情况可以写在一个 useModel 中。如下
当然你也可以用一个对象包这两个字段,一切随你喜欢,我只是想表达,既然是有关系的,一个变另一个也变的,可以放在一块
会不会存在一种情况,就是你可能有好几个字段,这好几个字段可以按照上边 “强关联” 分割成 n 个,但你又不想有 n 个
.js
文件,并且可能这 n 个文件是这个模块公用的。我个人建议是 : 该文件控制在 150 行代码以内,大概 5 个 model 即可,超出 5 个,则拆分,当然,这只是我个人的建议而已啦
在使用的时候只需要
import
一次就好了,而且这个在阅读性上,也还行,毕竟这三个useModel
都是具有关联性的文件目录划分
官方对 hox 特性的说明 : 随地可用,所以 model 层的文件目录规范也要统一,虽然之前使用 redux 的时候,业务 reducer 也是放在业务文件夹下,但是因为
combineReducer
的存在,我们是可以在 store 的入口文件 index.js 看到所有 import 进来的 reducer但是 hox,你不使用 createModel 时,它就是一个业务 hooks,随时建,随时用,会导致后期维护非常麻烦,特别是无 dev-tools 下,你根本不知道哪些 hooks 是变成了持久化、全局共享的 model hooks
我建议,原来我们写 store 的文件夹,改成 model,同时 model 下的文件夹架构和业务一致。
生态圈小
网上查询 hox 相关文章,几乎很少,github 目前
star 519
,issues 9
,算小众的库,当然这也不是一个大问题,因为你不使用它的 createModel 将数据持久化,那么本质上就是自定义 hooks如果有问题,大部分都还是自己逻辑问题,可能自定义写的 hooks 有点毛病,只需要自己 log 排查问题即可。
无 dev tools 支持
这才是痛点 !!!你想想,我们使用了 createModel 包裹之后,如何知道这个数据是否真的被持久化、全局共享呢 ?
常规操作就是,在组件中 import 这个数据源,然后 console.log 打印看看,但是如果是使用redux,那么我们可以在 redux-devtools 插件下,直接看到这颗 state tree 的。这就很方便了。
比如我想看看 user 下是否存在我想要的字段,那么我只需要在插件中看一下 state tree
还有一种场景,比如我们在 A 组件中,发送请求,然后往 model 中修改了一些数据,这些数据在 B 组件才用到,A 组件用不到,在没报错的情况下,我们无法知道数据是否真的被修改
解决方案
实在是很难过了,我总不能真的去 import 进来,然后去 console.log 吧?于是跟鹏哥一起琢磨了一下,直接修改源码,然后实现一个简单的 model tree 吧 ...
这里有个问题,那就是我们给 createModel 传递的是一个 hooks,是你在 useSelectClass 中,return { classId, changeClassId } 这个对象,所以你是不知道当前这个 hook 的名称,为此我们给这个 hooks 注入一个命名空间
然后修改
createModel
源码,原来只需要给Executor
传递 2 个字段,现在给它支持 namespace此时去打印 window.hox ,是有数据的,又离成功近了一步,但是问题来了,我们修改 model 值之后,我们要实时响应,该这么办?记得之前看 Vue 双向绑定原理,哦豁,有了,
Object.defineProperty
用起来在我们写的 dev-tools 组件中,监听一下 window.hox ,将最新的 model 数据,存入 state,然后搭配 Ant Design Tree 组件,最终渲染
这是最费时的了,我们要将这种数据格式,转成 Tree 想要的数据格式,写了好多遍都没写对,后边还是鹏哥给了提示,感谢鹏哥
上边的我就不一一写的,问题在于,如何遍历 ? 小伙伴们可以想一想 ? 不想的就直接到下一步吧
效果如图 👇
提问环节
如果我代码这么写,你们觉得有什么问题吗?
你们觉得我这么写,会不会有问题呢 ?有,如果我作为使用者,我取这个 classId 值,那么在这个 10s 内,我取的是一个 undefined,然后 10s 后,突然 classId 变了;还有一种可能,你依赖 classId,当 classId 变了之后你发送请求,10s 之前你发现没问题,10s 之后,哦豁,怎么突然多发了一遍请求 ?
所以我认为要约束,规定 model 的 hooks 不要写副作用,它就存粹的 getValue、setValue 就好了
或许有人会说,那你也可以在 redux 这么做啊,不是吧,reducer 可是一个纯函数啊,小老弟,不知道纯函数的去科普一下哈
还有一个问题,不应该说是缺点,就是想讨论 : 相对于 redux 的集中式管理 store 与 hox 的分散式且无 dev-tools 情况下的管理
最后
如果我是一个新人,我就只会 react,什么 redux、什么 dva、mobx 我都不会,我就只想写一些小项目,那么我会选择 hox ~~
对于小项目,可能需要 store 存储的字段比较少,那么配上简陋版本的 dev-tools,那么hox 或许比较适合此场景,如果引入 redux 那套 action->saga->reducer 等,前期投入比较大;但是对于大项目,还是用 redux 吧,毕竟 redux 算比较成熟,生态圈也很丰富
个人还是觉得 hox 这个设计思想还是挺不错的,但是我很担心一点,那就是 : 这会不会是一个 KPI 的产物,到后期没人维护,也没人更新。
好了,对于 hox,今天就聊到这,大家散了散了。最后建议,感兴趣的可以去看看源码,确实还可以,涨知识了...
The text was updated successfully, but these errors were encountered: