设计的魅力(一):导师问:你了解状态树的设计吗?我:什么是状态树?


前言

最近在做可视化框架研发,排期到了事件可视化部分。需求评审和设计评审基本完成,开发任务逐步清晰。

总体而言,给我的感觉还是比较有挑战的。话不多说,先来个图。

设计的魅力(一):导师问:你了解状态树的设计吗?我:什么是状态树?_reactjs

小的东西,设计的优势显现不出来,但是一旦场景变得复杂,就不得不考虑代码的可维护性。那么,如何写出可维护性高的代码呢?

其背后一定是优秀的设计支撑。和导师碰设计的时候,导师问:你了解状态树的设计吗?我:什么是状态树?导师:…

吾日三省吾身:好菜,好菜,好菜。看导师一脸复杂的神情,我就知道又又又触及到我的知识盲区了。

下面我们从www角度聊一下状态树,这个看似高大上却很常见的东西。

状态树的www

what–什么是状态树?


以redux为例,状态树可以理解为redux中的store,也就是数据存储的地方。对store的设计与规划,其实就是对状态树的设计。


听完导师的解释,我觉得我似乎也不是很菜,当场强行扳回一局:啊,你要直接说store我就明白了,状态树这名词不知道。

不知道大家是否也有这样的感觉,实际使用过或了解某个技术点,但与之对应的技术名词不清楚。

状态树设计的背后其实还有一套思想的支撑,叫做​​normalizr​​(数据标准化)。

不一定完全依赖这个,但可做实际参考,结合业务场景完成最终的设计。

why–为什么需要状态树的设计?


知道了什么是状态树的设计,我们再从算法,理解和维护这三个角度谈谈为什么需要单独花时间对状态树进行设计。


​算法角度​

对于redux而言,store中的数据绝大多数都是需要reducer去进行变更处理的,或增加,或删除,或修改。

试想一下,不假思索的将store设计成数组对象,对象数组等层层嵌套的复杂state,reducer处理增删改的时间复杂度将直接飙升。

如果数据量很大的时候,层层嵌套循环会让整体运行效率变得很慢

​理解角度​

你也许会想:我之前项目store也没特意规划,用起来也没啥毛病啊。至于理解,也能看懂,问题不大。

这种想法大多是因为项目本身不够复杂,数据量不大,所以即便是平方阶时间复杂度也没什么,理解也不成问题。

双循环用起来顺手,就是天王老子来了,也是双循环好用

再者也许就是自始至终都是你一个人维护,何苦为难自己,又不是不能用!

最后可能就是比较扎心的了,你完全不清楚还有这层设计,也不知道什么是好的设计, 如果闻所未闻,又何谈刻意使用呢?

就像东哥一样:我脸盲,不知道什么是好看不好看。

最直观的理解反映在代码上就是mapStateToProps

如果你不假思索的乱设计state,复杂场景下就是在给自己挖坑,遍历遍历遍历好多才能从store中拿到预期数据

​维护角度​


团队开发中,不论是你个人维护还是和同事一起维护,都逃不开维护这个话题。
维护可不仅仅是改改bug,这里的维护指的是广义的维护,包括但不限于增加某项功能,修改某项功能,删除某项功能…


设计的好坏直接决定算法的好坏,和是否容易理解,反映到代码层次就是是否易读易写

可维护性其实也就是易读易写的直接表现。

how–如何设计一个优秀的状态树?


在考虑如何设计一个优秀的状态树的时候,有两个小建议:不苛求绝对的完美,不过度执着一步登天。


不苛求绝对的完美:有的时候理解和读写是相悖的,追求一个平衡就好。

不过度执着一步登天:如果经验不是特别丰富,很难一下子设计出满意的状态树,可以先写一个基础的,逐步分析改进

状态树的设计–初版


拒绝空想,我做这部分设计的依赖是导师画的用例图。为了加强理解,我转成了文章顶部的xmind形式


经过一番思考后,我完成了第一版的状态树设计,结构如下:

设计的魅力(一):导师问:你了解状态树的设计吗?我:什么是状态树?_数组_02


提示:业务场景需要,不必过度纠结上述各个字段名含义,与xmind配图结合看即可,只需关注事件类型和事件动作

补充:选中某个组件后,选择事件类型,每个事件类型包含一个有序的动作列表,执行不同动作

分析

从理解上:这版设计基本是符合xmind配图中所展现的组件,事件类型,事件动作三者关系的

缺点如下:

事件动作列表项缺少标识id,无法修改和删除

事件和动作嵌套,耦合度高,层级深,不利于读写

示例:以修改一个点击事件的数据请求动作为例,reducer需要做的事如下:(假设此时动作id已经加上)

1. 从store中拿到events数组
2. 对events数组进行遍历,找到item键为onClick的对象
3. 从onClick对象获取actionList数组
4. 对actionList数组进行遍历,找到符合id的项,修改

时间复杂度

这个修改过程涉及事件列表和动作列表两个嵌套循环,视为m*n时间复杂度。

也许你觉事件列表数量是有限的,可视为常量,此时时间复杂度取决于动作列表数量,复杂度为O(n)

可即便如此,这个效率还是被拖慢了m倍,如果能完全去掉m,还能优化到O(1),岂不是美哉?

ok,现在优化目标很明确了,干掉m,优化到O(1)

状态树的设计–优化


干掉m最直接的方式就是将嵌套循环拆出来,再通过id进行关联,优化到O(1)就得考虑换别的数据结构了
无论如何,读写层面,数组是做不到O(1)的,但是哈希表可以。优化后结构如下


设计的魅力(一):导师问:你了解状态树的设计吗?我:什么是状态树?_程序设计_03


分析

从理解上:这版设计依然是符合xmind配图中所展现的组件,事件类型,事件动作三者关系的,并且更为清晰

优点同样明显:事件动作分离,且动作列表直接从数组存储换为哈希存储,查找时间复杂度降为常数级

取舍

在如何设计状态树的部分,我提到了理解和读写有时是相悖的,优化版中将初版的targetIds和params封装为对象actionOptions。

虽然理解上更直观,但读写层级也多了一层。

无需过多纠结这个点,有个平衡就好,js对象读写属性也是很快的

示例:以修改一个点击事件的数据请求动作为例,reducer需要做的事如下:

1. 获取事件id(做为可视化展现界面的隐藏字段,在触发更新时是可获取的)
2. 从store中获取actions对象
3. 从actions对象中找到符合id的项,修改

总结

设计先行,以写出可维护性高的代码为目标

设计好坏的评判维度是算法,理解,可维护性,具体表现为是否易读易写

每日三省己身:菜否?菜否?菜否?


文末彩蛋:导师说过哪些让你一下子就自闭的话?


  1. 我觉得开发不是什么难事
  2. 哎呀,这个很简单
  3. 这个难道不很简单吗?
  4. 我其实数据结构和算法不太好

情如风雪无常,却是一动既殇。感谢你这么好看还来阅读我的文章,我是冷月心,下期再见。