[关闭]
@jtong 2016-08-15T10:09:34.000000Z 字数 3553 阅读 2913

如何基于React,用图形表达思路

Graphic-Language Establish-Focus 胜任力


注: 本文不适合初学者阅读,适合有带新人需求的有经验的程序员

我们的胜任力模型中,有一个amplifier叫做建立关注点(Establish Focus)。尽管它更多的是指一个团队的领导者如何让组员把自己的工作和大目标align在一起,在工作时始终关注着业务和技术目标,但是使用的技巧与沟通时保持对方不会lost是同一种技能。

强求Jr.的从业者具备建立关注点的能力是很显然的强人所难,反过来要求带他们的Sr.的人具备帮助Jr.建立关注点的能力同样是强人所难。那么最好的解决方案是,我们发明一种语言来沟通,这个语言的要求有下列三点:

  1. 容易使用
  2. 无二义性
  3. 可以通过直观的方式来建立双方的关注点,并且能让双方无歧义的Get到这个关注点。

基于以上3点,我给出的语言是一个图形化的语言,其中大量吸取了UML的语法和产生式编程的Feature Model图等图形并予以简化。它跟代码是一一映射的。对应上述三点要求,正好带来三点好处:

  1. 因为它是图,所以它容易使用。
  2. 因为它是跟代码一一映射,所以它没有二义性。
  3. 因为它是图,所以可以用指指点点的方式来建立双方的关注点。且保证无歧义。

当然它不是完美的,但是在我们的项目中,它是work的。我们实现了一个Sr. Developer带7名毕业生进行开发并保质保量的完成工作的例子。这里面涉及很多具体的技法,我们这里不深入探讨,只探讨在React框架下,怎么做到这一点。

思维模型

我们认为不管使用jQuery,Angular,React,都应该是用下面的模型去抽象的思考我们的程序
front-end-mind-set.001.png-125.4kB
在三种框架里,上图所有的词汇皆有对应的概念和实现方式。这里不做展开,只关注在React上。且使用的是React+Redux作为核心技术栈。
那么在这个技术栈上:

我们主要做三件事

  1. 画出一个页面上的组件关系图
  2. 画出背后的所有state定义的描述
  3. 写出state如何map到props上,写出事件影响了哪些state。

我找了一个足够复杂的页面来进行下一步讲解。由于时间关系,我列出的做法没有经过足够的深思熟虑,如果你发现了一些问题,也是你来思考我前面说的这门语言的第三个特点是不是很容易做到的一个很好的契机。所以,强调一下,这是一个沟通工具,不是一个设计工具,不能保证你做出好的设计,但是能帮助把你的设计的重点表达清楚。

Screen Shot 2016-07-03 at 11.45.53 PM.png-1662.4kB

当然这个页面比较复杂,为了讲解的方便,我们会有些省略

Screen Shot 2016-07-04 at 12.37.20 AM.png-1689.4kB

我们看到,如果我们点击“未命名表单”,它会显示为编辑模式,点击别处又恢复展示模式

Screen Shot 2016-07-04 at 12.37.36 AM.png-1643.2kB

当我们点击一个已经存在的表单项,我们右侧的Tab会跳到“编辑字段”,并可以进行编辑。

组件关系图

在这个页面上,我们做一些分析,第一步试图列出所有的组件关系图。上面的第一张界面,我们带上“X光眼镜”看一下,会看到下面这个样子。

components.png-3476.4kB

我还可以再分割下去,只是在这个图上画框就画不清楚了。如果再加上另外两张图里表达出的隐藏组件,就更画不清楚了。所以我们换一种方式表达,如下两图所示:

IMG_0042.PNG-349.4kB

IMG_0041.PNG-456.7kB

有了这些图,我们就可以指着界面去对应了。哪里错了用手一指即可。

有两点值得注意的:
一个是FormItemBox和FormItem之间1-0..n的关系,这个意思是说可以一个都没有,也可以有n个。
另一个是子组件的线上的一道弧线,这个意思是说下面的组件在同一时间只会出现一个,但是他们都存在。

当然这里写的不是很好的地方还是有的,比如FormItem其实还可以再写子组件,或者直接把复杂性放在FormItemBox这一层处理也可以,那就不需要把FormItem拆成多个组件。想一想如果有这样的想法,需要传递,有图会不会就容易很多。

State定义

我们的定义方式是仿照JSON,书写state的属性和属性相关的类型来表达state。如下图:

  1. var formDefinitionView = {
  2. titleBar: {
  3. isEdit: Boolean,
  4. title: String,
  5. description: String
  6. },
  7. items: [{
  8. type: String,
  9. isEdit: Boolean,
  10. definitions:{
  11. label: String,
  12. order: Number,
  13. defaultValue: String,
  14. tips: String
  15. validation: {
  16. mustHave: Boolean,
  17. notRepeatable: Boolean,
  18. range: {
  19. haveRange: Boolean,
  20. ranges: []
  21. }
  22. }
  23. layout: {
  24. widthScope: {
  25. value: String
  26. }
  27. }
  28. }
  29. }],
  30. submitBar: {
  31. isEdit: Boolean,
  32. definitions: {
  33. /**省略**/
  34. }
  35. }
  36. activeEditSubBoxTab : Number
  37. }
  38. var availableItems = {
  39. commonItems: {
  40. name: String
  41. tips: String
  42. value: [{
  43. type: String,
  44. iconURL: String,
  45. name: String
  46. }]
  47. },
  48. contactInfoItems: {
  49. name: String,
  50. tips: String,
  51. value: [{
  52. type: String,
  53. iconURL: String,
  54. name: String
  55. }]
  56. },
  57. productOrderItems: {
  58. name: String,
  59. tips: String,
  60. value: [{
  61. type: String,
  62. iconURL: String,
  63. name: String
  64. }]
  65. }
  66. }
  67. //下略

当我们把组件和组件对应的state都描述出来以后,剩下的工作就是把组件和State进行map,用户行为触发事件从而改变State和与服务端通信。

map State to Props和事件影响了哪些State

我们把编程模型抽象为输入,处理和输出,然后再进行表达。让描述者关注输入和输出,会更好的帮助他们界定问题的边界,从而避免思维的混乱。如下图所示:

像机器一样思考.004.png-50.6kB

具体的做法,参见拙作: 《像机器一样思考》
当然上文中所讲的场景有点简单,以后有机会我再描写更多的例子以方便读者理解。

我在这里就讲一个例子:
点击图上的唯一的标单项:一个label为“未命名”的文本框。从预览模式:
Screen Shot 2016-07-03 at 11.45.53 PM.png-1662.4kB
变成编辑模式
Screen Shot 2016-07-04 at 12.37.36 AM.png-1643.2kB
仅就这一步,可以画出如下的框图(注意,我们只画我们写的代码,不要去画框架和库的代码):
如何基于React,用图形表达思路 .001.png-76kB
针对图上的我们如果分析输入和输出的内容,就会得到下面的结果:

  1. #1 formDefiniteReducer
  2. 输入:
  3. [参数]
  4. action: {
  5. type: String,
  6. itemIndex: Number
  7. }
  8. formDefinitionView: [参见State定义]
  9. [hard code]
  10. tabNumber: Number
  11. 输出:
  12. [返回值]
  13. formDefinitionView: [参见State定义]
  14. #2 FormItemBox#mapStateToPros
  15. 输入:
  16. [参数]
  17. state.formDefinitionView: [参见State定义]
  18. 输出:
  19. [返回值]
  20. result: {
  21. items:[{
  22. type: String,
  23. isEditable: Boolean
  24. }]
  25. }
  26. //下略

我们这种手法要求,所有的输出都只可以靠输入的数据计算出来,也就是所有的输入要穷尽,所有的输出要穷尽。

为什么只写类型,主要是为了表达的速度。我们把看到类型就想到可能的测试用例这一步交给Sr.的人去猜想或者Sr.的人跟着Jr.的人通过口头沟通来理解。大部分严重的问题靠类型本身已经足够暴露出来。

严重的问题举点例子就是,漏写了一个hard code的输入,返回值的属性不足以完成需要的功能,本来可以做参数的用了hardcode。

当你发现了上述的问题的时候,你可以非常轻松的通过指指点点的方式来传递你的关注点,而听的人也会非常容易理解你在说哪里。对双方都容易建立关注点。

所以再强调一遍,这是一个沟通工具。如果你对你项目上的新人不够有信心,你可以进一步让他们写写example。就是输入和输出的具体数据,而不是简单的数据类型。这就跟写测试差不多了,会比较花时间,值不值要靠你自己决定。

当然对于很多简单的界面来说,这一步对解决我们文章开头的问题,不是那么重要。当我们把基本组件和state列清楚了,中间的转换靠口头沟通并没有那么难,出现的错误也容易找到。

以上就是所有手法,有问题欢迎讨论。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注