技术学习实践的“快“与“慢“

刚刚看完了Spring 4.3.4和Spring Boot 1.4.2的Reference guide,有点感念,想就技术学习的快与慢谈一些自己的感受。

坦白讲,我对新技术学习不算快,一方面是自己不够聪明,缺乏天赋,另一方面是自己多年养成的学习习惯使然。现在回想起来,当年学习C++的时候,第一本书是课程教材,用的清华大学钱能的那本《C++程序设计教程》,这本作为教材挺好的,现在虽然已经记不太清当年的学习过程,总归印象还是不错的,还是打下了比较坚实的基础,最起码C++期末考试考的还不错。后来大家一致推荐的《C++ Primer》,内容组织合理,讲解条理清楚,贴近规范和最佳实践,然而偏偏我当时不知道钻了什么牛角尖,一定要固执地坚持看C++爹的《C++程序设计语言》,当时看的是北京大学的裘宗燕老师翻译的, 回忆起来,有一些翻译的梗今天看来还是挺有意思的,记得当时大家都说裘老师一定是看花眼了,把”inline”看成“online”了,所以“内联“都翻译成”在线”,结果裘老师还在各个地方和大家争论——为什么翻译成“在线“,究竟是什么原因现在也没有定论,如今没人再寻根问底,都淹没在历史里了,目前我一直看的是第四版的英文版,说回书本身,这本书的特点就是信息密度非常大,很多非常重要的地方一打眼就错过去了,虽然我已经看的很慢了,但直到今天,有时因为工作需要查找某些知识点,翻到某些页还像第一次看到一样,我发现在xx语言发明者写的书里面都是这种风格,因为他们太熟悉了,所以知识点经常会出现很多的交叉索引,而大多数学习者习惯于线性思维,经常会陷入“这个是新的么?我学过了吗?”的混乱。后来用Scala的时候又再次感到了类似的体验——谈到语法,Scala不亏是不服C++的主儿——后话暂且不表,以后专门写Scala。

《C++程序设计语言》这本书的阅读体验从某种程度上很符合我的学习习惯,我一直没有买《C++ Primer》,而是买了它的习题集全部做了一遍,所以我跟别人讨论《C++ Primer》的时候,一般会提到“不要简单的把它当资料书,前辈们这么说是因为他们已经首先扎扎实实的读过了一遍,甚至每一道习题都做了一遍,如果要用它学习C++,请花大块的时间先认认真真的读一遍”。这种习惯一直贯穿着我学习技术的方方面面,很多人也称之为“笨”或者“难”的方法,然而很多技术书的“hard way”系列质量都很高。后来因为工作需要,转到Java领域,我读的第一本书也不是大家一直推崇的《Java编程思想》,而是高司令的《Java编程语言》,直到今天,关于Java,这本和《Effective Java》,《Java并发编程实践》仍然是我翻得最多的三本书,而且我发现自己还是能在书上不断得学到新的东西,比如这本书的讲解Serializable的部分,你知道你可以通过定义private static final ObjectStreamField[] serialPersistentFields 去制定序列化字段的名字和类型么?大家不要误会,这不是茴香豆有几种写法的八股,而是常看常新的一种体验。

后来工作中需要,接触了Spring框架,我记得很清楚,花了几周的时间一点一点把reference完完整整的看了一遍,这以后,再有新的版本升级,我每次都大概只用半天到一天的时间就能把新的reference全部过一遍了,这其中的“慢”与“快”很有体验。相反,有些同事从来没有认真完完整整的看过一遍,都是工作中用到哪点看哪点,到现在也没有对Spring形成一个相对完整的认识与理解,盲目推崇面向Google和StackOverflow编程。其结果就是经常出了一点非常specific的问题,就漫无目的的东找西搜,即使源码在手,也不知从何下手,其根源在于根本没有对Spring的整体设计与习惯用法有着清晰的理解。

再后来,因为工作中要设计一种DSL,加上通过JavaEye接触到Ruby,我也开始了Ruby的学习,惯性使然,我还是选择了一个相对“慢”和“难”的路,一直在用的两本书是Ruby之父写的《Ruby程序设计语言》和《Ruby元编程》,这两本的特点也是信息量非常密集,需要慢慢地看,时时的理解,每次我尝试着加快速度的结果就是一遍又一遍的回头去看,就其效果而言还不如慢下来。

虽然我自己习惯于这样的“慢”,然而在实际中也不总是使用“慢”方法,比如我平时开发一些web应用,虽然我对Spring MVC很熟,Thymeleaf模板也很好用,但总还是比不上用PHP Laravel或者Ruby on Rails来开发来得更方便,更迅速,而且即改即测。

“快”与“慢”总是相对的,也跟每个人的习惯有关,而习惯这个东西是比较难改的,但是要想成为一个好的程序员,我想我们总是还要“慢”下来,或早或晚。

基于Java的轻量级工作流引擎实现

基于Java的一个简单的工作流引擎实现,用Java 8自带的Javascript nashorn引擎作为工作流生命周期任务扩展定制。代码已经比较完备,nashorn为Java对象的互操作提供了很多便利,使得Java和Javascript几乎可以无缝的一起工作。

    1. 属性访问。如果Java对象实现了Map<String,Object>接口,当使用obj.foo = bar时,nashorn会调用obj.put(“foo”,bar),如果是一个普通的POJO,nashorn会用反射的方法去调用getFoo/setFoo,这个地方需要注意的是,如果缺乏相应的getXXX/setXXX方法,它不会报错抛异常,只是就像没调用一样,所以你会发现执行中出现奇奇怪怪的行为。
    2. 方法调用。一个注入Javascript的Java对象上的方法调用跟在Java代码里的方法调用没有区别,如果你想让这个Java对象表现的像一个函数,只要这个Java类继承AbstractJSObject或者直接实现JSObject接口,尤其是实现call(Object thiz, Object[] args)isFunction方法,那么这个类的实例就可以像一个函数一样在Javascript里调用了,更简单的方法是使用Java 8引入的functional接口Function<R,T>::表达式,我们只要对java对象xxx或者类的method方法做个正确的强制转型,(Function<R,T>)xxx::method,然后把转型结果注入引擎,就可像函数调用一样使用这个Java对象了。有了这样的支持,我们就很容易注入自己java业务对象,给客户扩展定制自己的业务,比如注入mailer,给客户用脚本发邮件,多数情况下已有的Java类稍微修改就可以在Javascript中使用了。

工作流本身的定义使用了Spring Data JPA存储在数据库中,Github 自取。

领域驱动设计实践中的几点思考

1.  要尽量建立充血的entity/aggregate模型,只有不适合放在entity/aggregate的行为才需要提升到Service层去实现,一般这是由DDD的一些设计准则导致的。(1) 要保持小的aggregate设计,保证事务执行的粒度 (2)要避免从aggregate直接navigate到其他相关aggregate的集合,即使Lazy Loading DDD也不提倡,而这样导致一些业务方法在没有repository/service注入的情况下没办法实现相关业务逻辑。有人认为这点多少是与传统的面向对象设计有冲突的,然而确实在实践中大多数情况下是应该遵守的,比如Category.getArticles(), 有人认为这是Category应该提供的行为接口,然而在DDD设计中是极力反对Category提供getArticles的访问方法的,反而用ArticleRepository.findByCategory来实现,这样的访问是可控并且有意义的,比如加入分页参数对于repository的query方法来说是有意义的,反之,如果在Category.getArticles加入分页信息是有点滑稽的,底层的分页概念侵入到了业务模型,如果不加,那么当某个category分类下面包含很多acticles时,Category.getArticles这么一个普通的可能是业务代码无意间的调用就会造成极大的系统内存抖动。

2.  Service层应该是无状态的,这里有个有趣的问题国内外的架构师一直在争论——Service层方法应该接受aggregate的实例,还是相关aggregate对应的ID?如果是前者,那么在application层应该负责保证生成正确的aggregate实例,这可能会带来一些不必要的查询,因为有的方法只需要ID即可,不需要实例。有些架构师会设计一个ServiceFacade的层,来处理DTO层传过来的包括ID等各种简单类型的String,Long值,而Service层只针对aggregate实例来做业务逻辑的计算。对我而言,我更喜欢这种设计,即service层针对实例来实现方法,虽然会带来一些潜在的不必要的查询,但是当entity/aggregate是充血建模的时候,绝大多数场景仅仅ID都是不够的,当然这也取决于你的service方法是否需要。总体来说在这一点上DDD的原则和传统的OOP设计是有部分冲突的。DDD也认识到没有类型信息的简单类型Long,String是非常容易误用和不友好的,所以一个常用的技巧就是DDD用一个值对象(Value Object)来包装ID,即使这个ID就是一个Long类型,比如用class ArticleId来包装article的ID,这样在service层统一使用aggregate的ID而不是实例,由service方法来决定是否要构造这个实例,这也是一种非常常见的方案。

Web应用开发架构的演变

拓荒时期,1995~1998

彼时开发主要以CS模式占主导地位,每一个产品的Client端和Server端的通信都是私有的二进制协议或者基于某种文本描述的协议,例如SOAP( 1998 ),XML-RPC,或者DCOM,CORBA类似远程过程调用框架。其中远程过程调用的概念出现的非常早,而且这一概念一直延续到今天,目前看来大家在分布式系统中更倾向于这种通信方式,比如facebook的Thrift。Google的ProtocolBuffer和Apache的Avro更像一种数据打包方案。Java语言从一开始就集成了对CORBA的支持和对RPC的支持——RMI。拉回web开发来说,1994年网景浏览器发布(基于http 0.9 1991年发布),给大家无限可能的想象,把整个世界装在一个浏览器窗口,而http协议本身在1996年才完成了1.0的草案。那时候BS模式的开发绝对是超级激进的方式,绝大多数的网站都是以展现静态内容为主,没有web应用( Web Application )的概念,website这个词使用了很长一段时间。老程序员们对当年手写一个静态页面100块的价格应该比较能感受到技术的价值。接下来,CGI( Common Gateway Interface )方案登上web后台的开发舞台,作为web后台开发的基础,这种架构一直延续到今天,并引发很多响应的变种Fast-CGI,WSGI,Rack,其本质就是在server端设置一些环境变量然后启动相应的脚本解析器执行内嵌的脚本,然后输出到浏览器端,好处是传统程序员擅长并且非常容易接受这种转变模式。

这个时候后端的代码呈现出来的特征很像后期jQuery繁荣时的一样,以页面为业务组织单位,哪里需要动态内容就插入脚本标签,html标签和业务脚本代码完全混在一起。

这个阶段一些奠定web基石的基础性软件apache http server ( 1995 ),PHP ( 1995 ),Perl 5 ( 1995 ),Java( 1995 )发展繁荣,搭配mysql作为后端数据库的web站点呈现爆发式的增长。

J2EE的统治时期,1999~2005

经历了web开发的无序混乱的拓荒时期,大家对BS这种模式已经不再有怀疑,但对能做到什么程度,是CS模式的部分补充还是将会慢慢的占据统治地位,这个时期是不存在大规模的现代意义上面对终端用户的的互联网业务的。广大开发者和企业一方面对BS的模式感到兴奋,一方面都在翘首盼望着一个杀手级企业应用开发框架来证明BS架构才是未来。1999年底,J2EE横空出世,2是跟随Java的大版本号Java 1.2,从这个版本开始,Sun一口气推出了J2SE,J2EE,J2ME三大平台,野心很大,也很有前瞻性,有这样的观点——Android也不过是J2ME的在一个移动平台的『借尸还魂』。当时最引人瞩目的无疑是J2EE平台。它的出现让整个企业级开发市场沸腾了,规范覆盖了企业级开发需要的方方面面,老程序员应该对当时Sun官方给出的几个示范应用,PetStore,Duke银行应该还有印象。

J2EE

J2EE规范把大量的底层支撑功能从业务实现层剥离到容器实现层,容器提供商负责事务,ORM,RPC调用,线程池管理,数据库连接池管理,服务实例池管理,消息队列各种『脏活累活』,程序员只管负责实现业务逻辑。蓝图很美好,队友很给力,Tomcat( 1999 ),Weblogic ( 1999 ),Websphere ( 1999 ),全世界的程序员都很兴奋,一时间,所有的企业级应用都在往J2EE平台上转,考虑到那个时候几乎没有多少面向终端用户的软件业务,可以说J2EE是整个软件开发世界当之无愧的王者。在一片叫好声中,也有一些负面的反馈,EJB体系过于复杂,学习曲线陡峭,我记得当时自己花了很长时间去理解EJB的Home接口,Local接口,Remote接口,什么时候应该用stateful Bean,什么时候应该用stateless Bean。至于前端基本奠定了Java bean + html的server渲染模式,跟CGI倒也差别不大。早期的软件开发世界,以一人之力撼动真个业界的现象一再出现,在J2EE开发领域,一个澳大利亚的程序员,Rod Johnson,今天说起来是个传奇了——Spring的创造者。他在2002出版的一本书在整个Java社区引起了巨大的共鸣,大家受够了EJB的复杂性,却苦于找不到更好的解决方案。

Expert One-on-One J2EE Design and Development

然而,这本书其实是『名不副实』的,它基本抛弃了所以的曾经J2EE鼓励的开发实践,他从一个普通程序员的角度,循序渐进的指出应该如何友好的去构建一个系统,正确的面向对象设计实践。直到今天,这本书对于软件开发来说仍然具有相当的指导意义,我自己是常看常新。2004的他的另一本书再次受到了社区的追捧,JavaEye组织的翻译也很好。但就我认识而言,这本书的深度远不及一本书,它没有涉及过多的思考过程,而是机械的告诉怎么做是对的。

Expert One-on-One J2EE Development without EJB

今天看来,结果就是,Spring社区已经一统Java server端的开发,想想还是很震撼,仅凭一己之力。

互联网繁荣期 2005~至今

2005年影响web开发世界最大的就是Ruby on Rails开发框架横空出世,又是一己之力,一个丹麦的程序员,一个24小时汽车拉力赛的世界冠军,当然也是我的偶像。

David Heinemeier Hansson 

他给我最大的启示就是一个程序员要有自己的思想,通过学习,实践的过程来求证,人云亦云最要不得。Rails给Ruby带来了2006的Tiobe的年度编程语言荣誉。一门新兴的语言依托一个强大的框架,直接在互联网开发领域占有一席之地。

我们就简单列一下流行的互联网服务搭建在Rails上,不出名的就不赘言了。

Twitter, GitHub, Shopify, Airbnb, Twitch, SoundCloud, Hulu, Zendesk, Square, Highrise, Basecamp

2005年Python领域的Django web框架也发布了,如今基于Python stack的互联网服务也很多很多了,Dropbox,Instagram,Pinterest,国内的豆瓣。

这个时候一个特点就是『快』,互联网红利完全体现在快这个字上,新兴的业务永远Beta快速发布,甚至Alpha发布,传统的企业级开发模式显然是跟不上这个节奏的。

这个阶段的开发框架大都遵循着『约定大于配置』的简单观念,放弃大部分不必要的灵活性,一个重要的结构烙印深深地刻在这些框架身上,遵循简单的Model-View-Controller架构,搭配框架内置的tool chain,一个命令就可以产生良好的应用主体代码框架,然后再遵循着框架约定,填入业务代码。

MVC

2006年有两件不能不提的以后会影响整个web开发的事是,

  1. 微软在2006年提出了异步请求的概念,并把它的实现加入到了IE 7之中,这就是我们今天熟知的XMLHttpRequest。之所以叫XML是因为它首先作为一个ActiveX控件被Outlook团队开发出来用来从server获取xml格式数据,然后IE 团队就直接拿过来使用了。
  2. 2006年jQuery发布,jQuery影响有多大,想想很多很多的人提请标准委员会浏览器直接内置jQuery实现便得窥一二。好吧,也是一个人,John Resig。可能更年轻的前端开发者已经不太知道大神的地位了,我记得现在流行的前端框架的开发者当时是非常兴奋的在twitter上发布说『John关注我们了,对,就是那个John』。

在2009年之前,这一段时期的web开发,已经有相当多的开发量从后端转移到了浏览器端的javascript的开发,javascript已经不再被当做玩具语言了,早期的时候,甚至浏览器可以配置是否禁止javascript,你可以想象是多么的可有可无。这个时期的代码体现在『绚』和『乱』上,各种绚丽的效果,大家都很兴奋头一次看到这么绚的UI交互效果,大量的jQuery plugin被开发出来。乱,就跟当初server端的代码一样,各种业务逻辑与dom操作混在一起。一些大的互联网公司封装了一些今天看来很重的js的前端框架,其核心思想就是要用开发传统桌面程序的方式来开发web程序,YUI和ExtJS ( 2007 )是其中的代表。

2009的时候,一个新的运行时平台,一个今天异常繁荣的社区出现了,nodejs平台,node社区,还是一个人,Ryan Dahl,他基于Google开源的javascript引擎V8实现了大部分的javascript类库,可不严格的类比于jdk之于JVM。在javascript发展历史上,这是极其决定性的一步。有了这一步,大家才开始考虑要有依赖管理,modular开发,面向对象,异步,同步,这也反过来极大的推动了javascript语言规范的发展,今天再看ES2015语言规范,跟当初的javascript已经完全不是一个样子了。

nodejs平台推出以后,有些技术的先行者就开始考虑能否用开发后端的工程化方式来开发前端,开发完成后,依赖一些构建工具来打包压缩所有的js引入页面。server端只作为api提供json格式的数据,整个应用架构看起来像下面这样子,

API

2009发布的backbone在当时绝对是让人瞠目结舌的,在此之后,更进一步,为了解决双向绑定,局部动态更新的dom操作效率的问题,提出了Virtual Dom和MVVM的方案,然后架构演变成下面这个样子,

MVVM

这样架构的一个潜在问题在于,前端代码的膨胀越来越厉害,即使经过了压缩,于是又有一些聪明人想结合传统的server端渲染技术,在server端引入js运行时来生成静态html代替原来完全在浏览器端用javascript来构建整个前端,这就是所谓的server端渲染技术。

ServerRendering

前端开发架构演变之快,实在是一日千里,单单拎出来前端部分,你看最近几年冒出来多少来势汹汹的前端开发框架backbone,ember,angular,react,vue,再包括各种先进的依赖管理,构建工具体系bower,grunt,gulp,webpack,然而其思想是统一的,工程化,模块化,组件化前端开发。

这样的开发方式,好与不好,见仁见智,对于大团队合作开发必然是有优势的,在我看来,有一点始终是需要提醒自己的,

不是所有的公司都是google和facebook

在软件开发领域,有一句著名的准则,

organizations which design systems … are constrained to produce designs which are copies of the communication structures.

直白来说,软件系统的划分基本上复制了开发这个系统的组织划分。

所以对于小的开发组织,创业公司,才用这种方式要慎之又慎,基本上要求每个成员都是多面手和熟手,不然每个项目面临下面的沟通方式,必然难以为继。

Conway

今天前端开发再不是传统意义上我们所称的『前端』——切一些图,写一些css和简单的javascript了。如今看来,其出现和繁荣可归于两个主要的原因,

  1. 移动互联网的迅速崛起,在移动端,用户需要更快速的响应,更丰富的交互体验,传统的单一请求响应方式,不足以满足这种需求。前端的开发复杂度急剧增大,必须有一种有效的工程化开发方式来指导和构建。
  2. node社区的急速繁荣,这得益于以下原因,
    1. javascript天生就是为web开发准备的,各大浏览器断无可能再发展另一种语言。
    2. Google V8 引擎提供了一个高效的运行时环境,使得基于javascript的server端开发成为可行的途径,V8之于javascript如JVM之于java。
    3. javascript基于回调的机制使其在IO敏感的应用领域表现出更好的并发性,而IO敏感正是绝大多数应用的瓶颈所在,只有极少数的应用需要大量的运算,对CPU有很高的要求。

天下大势,合久必分,分久必合,IT技术领域,不外如是。我们常常会看到当年一些被认为落伍的观念『借尸还魂』,以一种新的姿态,新的概念出现,加之其背后往往有强大的厂商推动,一些开发人员就会鼓掌叫好,跟随学习。这些都值得我们重新审视,从桌面应用到web应用,从J2EE到Rails,从web到native app,从native app到hyprid app,技术历程,一路走来,如果我们能够多一些梳理和反思,少一些狂热与追捧,也许我们便更可能看清自己的技术之路。

我所经历过的一道微软面试题

一个老工程师,一言不合就让我写白板,让我在白板上手写一个简单的正则表达式匹配,只用匹配*和?号,惭愧,当时没写对啊。不过,我做面试官的时候,对于现在的小朋友,从来不敢出超过二分查找难度的题的,省的两个人都尴尬。

BOOL MatchString(LPCWSTR source, LPCWSTR expression)
{
    if(expression == NULL)
    {
        return (source == NULL);
    }
    if(*expression == L"")
    {
        return (*source == L"");
    }
    if(*expression == L"*")
    {
        int sourceLen = lstrlen(source);
        for(int i=0; i<=sourceLen; i++)
        {
            if(MatchString(source + i,expression + 1))
                return TRUE;
        }
        return FALSE;
    }
    else if(*expression == L"?")
    {
        for(int i=0; i<2; i++)
        {
            if(MatchString(source + i,expression + 1))
                return TRUE;
        }
        return FALSE;
    }
    else
    {
        if(*source == *expression)
        {
            return MatchString(++source,++expression);
        }
        else
        {
            return FALSE;
        }
    }
}

LLVM tutorial写的很棒

LLVM project的 tutorial 真心不错,从lex, parser, AST, JIT, optimization 甚至debug info generating都介绍到了,而且还用了最新的C++ 11/14标准,实在是编译器入门兼练习C++新标准的好资料啊,即使是门外汉,一个星期也能撸个自己设计的语言原型了,带JIT的哦!想起自己刚学习编译原理入门的时候,哪有这种好的资料,都是死啃龙书,LR1那一章不知道撸了多少遍啊……

面向对象设计的日常 (一)

无论是developer或者architect,先掌握好面向对象设计的技术吧,自从设计模式普及推广开来,面向对象设计似乎变得没那么难了,大家熟练运用,甚至死记硬背了Adapter,Singleton,Factory,Abstract Factory,Strategy,Command,Vistor几种模式后,code似乎都写的像模像样了,但最难的把真实世界(系统)抽象化的能力根本还是一团浆糊,大多数情况下是堆叠一堆设计模式去“套”业务,实在套不上的,来个全是静态方法的Util类,还不行?再来个含糊的全能Context类总行了吧,我自己感觉Spring的广泛使用对这种畸形设计起到了推波助澜的作用,多美好啊,再也不用认真思考对象之间的关系,对象之间的交互过程,千篇一律的无参构造函数,惨绝人寰的set/get方法冗长列表,需要的接口统统注入进来,还容易沾沾自喜,我这可是面向接口编程哦。
举个简单的例子,一个REST的api sdk,大多数api都需要一个http header包含了一个token,这个token可以通过post username和password调用另一个authentication service endpoint去获得,
一般的思路,对于接口AuthenticationService我们会有下面的考虑,这本身是非常正常的,因为对于一般的authentication service它的职责就是认证用户,但在这个case里,其实authentication service的职责是提供访问token,所以下面的这种就是一个生搬硬套,过于死板的设计,依赖于经验,我们很多人的第一个版本很可能都是下面这样,包括我自己。

class AuthenticationService {
    public AuthenticationService(String endPoint){
        this.endPoint = endPoint; 
    }

    public String getToken(username, password) {
        return fetchToken(this.endPoint,username,password, ....);
    }   
}

而下面这种设计则更好的体现了在这种上下文中的authentication service的职责,

class AuthenticationService {
    public AuthenticationService(String endPoint,String userName, String password){
        this.endPoint = endPoint;
        this.userName = userName;
        this.password = password;
    }

    public String getToken() {
        return fetchToken(this.endPoint,this.userName,this.password, ....);
    }
}

这两种版本的根本区别在于是不是认为username和password是AuthenticationService本身的一部分,在这个case里它显然应该是。

我自己总结的面向对象设计的三个基本准则,

  • 对象应该尽量是immutable的,所以当业务类有大量的set方法时,要慎重考虑设计,Spring框架注入需要的不在此列,

  • 接口是行为抽象的集合,可以这么想,我有一个实现了接口的类对象,现在已经完全构造好了,那么我有什么方法可以调用,

  • 你的设计出来以后,给别的developer使用友不友好,使用者是不是要大量的了解某种细节才能使用

最后一点非常容易自我检查设计是不是有坏味道。

一家之言,大家可以自己体会和发表见解。

Redux是如何工作的 (一)

凡是用reactjs开发的项目,但凡规模稍微大一些,都很可能要引入redux来管理组件状态的变迁和组件彼此之间的通信,权威的解释和说明当然是官方的这篇文章,但是,但是,但是,这篇长文处处透露着一种玄妙而不可言说的味道,处处是一些principle,best practice,never,absolutely,you should……的字眼,到处是强调,加粗的段落,一些模糊的store,action,reducer之类的抽象用词,我尝试着写篇小文来记录自己对Redux的学习理解过程。

1. 从reactjs本身谈起

正如官方文档强调的,我们只做view层,是的,reactjs利用JSX把UI的开发演进到另一种模式,就个人观点,我并不觉得其比其他基于模版的view技术究竟高到哪里去,有人说virtual dom技术比原来的直接操作dom来得效率高,拜托,它只是节省了dom元素的寻找时间,react使得程序员从数据变化耦合ui变化的面条代码中抽离出来,每个组件保持了极高的内聚性,给使用者带来了极大的方便,使得在view层堆积木式的开发成为可能,每个UI组件的state对于使用者是完全透明的,使用者只要为它设置好初始的props,组件本身可以响应各种事件来改变自身的状态,重新渲染外观。如果每一个组件都不需要跟外界通信,都不需要跟别的组件联动,那传统的state,props便足以对付所有的开发。但是组件之间的通信,这对一个稍微大型的SPA应用来说,都是必须要有的功能。

2. 我们能从传统的设计模式汲取什么

observer/observable,Event Listener是我们最常用的手段来注册,监听,响应外界感兴趣的变化,假设react组件A监听了组件B的变化,当组件B变化时它调用A注册的回调函数,同时把自己的state传递给B,注意,这里已经完全破坏了组件的边界,当A拿到B的state时,它可以做任何修改,所以实现时,B应该clone了一份自己最新的的state给A,那么问题来了,

1. A组件必须理解B组件的实现细节,不然它没办法理解B的state的含义,但state本身是应该对外界透明的。

2. 每个UI组件都必须暴露注册监听的接口

3. 大量的state复制操作

我们想一想如何自己解决这些问题,

1.  想不到,是的,真的想不到啊

2.  加个属性,不好办啊,A,C组件都想监听B的变化怎么办?

3.  前两个解决不了,这个就不是个问题啊

3. Redux 是怎么解决问题的

1.  计算机问题总是可以通过分层来解决,Redux引入了一个称之为store的层,所有的组件不再维护自身的状态,所有的状态都在这一层来维护,作为一个best practice,Redux推荐用扁平的方式来为维护这个store,所以每个状态都必须小心命名以防止同名冲突,一般的我都会加组件名作为前缀。

2.  既然组件不再维护状态的变化,那么组件的渲染必须通过组件属性的变化来完成,所以Redux在原组件外面重新包了一层,然后外层通过更新内层属性来触发内层UI的重新渲染,内层组件所有的数据通过props来绑定,同时调用通过属性传过来的回调函数来与外界通信,所以组件本身的内聚性得到了保证,组件总是通过属性来工作,需要特别指出的是,组件本身并不需要知道是否工作在Redux的上下文,它根本不知道action,reducer是什么东西,它只是根据传给它的属性来决定是否需要调用以及调用哪个作为属性传给它的回调函数,所以Redux本身也不需要必须和reactjs绑定,这个外部组件传进来的回调函数,它简单的dispatch一个action,action本身只是一个必须包含type属性的普通javascript对象,那这个action从哪里来呢?这里引入了一个action creator的概念,说白了就是一堆函数,每个函数返回一个action,他们一般都放在xx_actions.js ( xx代表组件名 ) 的文件里,这样看似多余的封装其实跟我们经常强调封装getXXX函数一样,它提供了一个hook点,使得我们有机会对action的创建有更多的额外控制,比如这个回调函数可以再返回一个function,这个返回的function调用生成一个Promise或者action,就像trunkMiddleware做的一样,第二个好处是同时避免了大量的创建不同action的逻辑耦合在一起。所谓的reducer就是action listener,它根据action的type来更新全局的store,很显然reducer针对同一个action会有多份实现,比如A,C组件对于B组件dispatch出来的同一个action有不同的反应。同时还应该有一个东西把各种必要的属性组合起来传递给我们的UI组件,具体怎么做,且听下回分解。

(未完待续)

React开发移动端h5应用踩过的坑们(持续更新中)

1. webpack 打包字体有时候会破坏字体格式导致浏览器decode失败,有的浏览器认为是警告,有的认为是错误,认为错误的直接不渲染页面

2. Object assign 方法不是全浏览器支持,尤其andriod和微信浏览器,需要自己找个polyfill

3. Promise不是全浏览器支持,尤其andriod和微信浏览器,需要自己找个polyfill

4. 这个跟react无关,主要是IE对flex布局的支持,对于flex:1 1 0,IE和微信浏览器直接扩展最后一个为0px,导致height为0,现象就是页面一片空白,写成flex:1 1 0%解决问题

5. 用ReactCSSTransitionGroup做页面间的滑动切换,只用定义appear的css,千万不要写enter和leave的,enter和leave是用来实现组件加入/离开组的动画效果。

6. 不要有工程洁癖,例如坚决不用jquery,坚决什么都用redux封装,有时候一个很简单的东西会被这种洁癖搞的很痛苦

7. 组件设计的力度不要太细,只对非常通用的组件做比较细力度的设计用来积累团队的code base,可以用于绝大多数项目,比如navbar,footerbar,infinitescroll list

8. 少用国产的React UI组件库,虽然看上去都不错,但是一用上,经常发现各种小坑

9. 认真读官方文档,有的虽然是短短一页,但是信息密度特别大,不仔细看经常会忽略某些非常重要的知识点

10. 一定要测试android browser,chrome for android,微信浏览器,不要只在自己的iphone上测了就OK了

11. 非常值得29欧买个jshybugger

12. Redux维护的state的生命周期是贯穿应用本身的生命周期,可能会积聚大量的数据在浏览器端,传统的组件自身维护state,组件销毁,state就销毁了,需要在不同的场景做取舍。