秒懂ReactJS

这篇文章是为ReactJs小白准备的,希望他们快速抓住ReactJs的要点并能在实践中随机应变。

两句话版本

  • ReactJs把视图更新简化为一个render函数
  • render函数接收两个参数,分别是配置项和状态

长版本

ReactJs是一个专注于View的Web前端框架。Web前端的View就是浏览器中的Dom元素,改变View的唯一途径就是修改浏览器中的Dom元素,因此ReactJs的核心任务就是如何修改Dom元素,作为一个成功的框架,ReactJs使修改Dom元素变得高效而又简单。

ReactJs把修改Dom的操作简化成一个函数renderInto(parentDom, props, states)=>htmlString,这个函数的意图就是根据props,states计算出视图对应的html字符串并添加为parentDom的子节点。props和states就是普通的javascript对象,这个函数的核心逻辑就是计算html元素的机构及元素属性然后拼接成字符串返回。作为框架,ReactJs用JSX形式的DSL解决了拼接html的任务并接管了更新到parentDom的职责。看一个例子,理解这个函数并理解ReactJs怎么使用这个函数你就可以一个人开始ReactJs之旅了。

var props = {name: 'myname'}; 
var states = {amount: 1000}; 


function render(props, states) { 
 var title = ’Hello, ' + props.name; 
 var description = 'You have ' + states.amount + ' score now.'; 


 return ( 
 <div className="score-board"> 
 <h1>{title}</h1> 
 <p>{description}</p> 
 </div> 
 ); 
}

函数第一行根据props计算title,第二行根据states计算description,最后以JSX形式返回拼接好的html字符串。

如果你用过AngularJs,EmberJs等类似的前端框架,你可能会觉得没什么了不起,不就是把模板和逻辑放到一起吗?是的,没错,但这不仅仅是组织形式上的改变,而是编程隐喻的转变—从复杂的MVC或MVVM模式到简单的render函数。还有一点不同是JSX最终编译成调用react-dom的javascript语句,而不是直接生成字符串。

render函数还只是ReactJs这座冰山的一角,”React”会在render函数的输入变化时再次调用这个函数。再看一个例子。

var props = {name: 'myname'}; 
var states = {amount: 1000}; 


function handleClickAdd() { 
 states = {amount: states.amount + 1}; 
} 


function render(props, states) { 
 var title = ’Hello, ' + props.name; 
 var description = 'You have ' + states.amount + ' score now.'; 


 return ( 
 <div className="score-board"> 
 <h1>{title}</h1> 
 <p>{description}</p> 
 <button onClick={handleClickAdd}>+1</button> 
 </div> 
 ); 
}

这个例子增加了一个”+1”按钮,当用户点击按钮时会修改states,ReactJs在states变化时的”React”就是再次调用render函数,然后用新输出更新浏览器的dom。

可能你会问,props和states不就是Model吗?是的,可以理解成Model,但此Model非彼Model,props和states都是为View服务的而非和View平起平坐。

可能你还会问,为啥不把props和states合并成一个对象?要回答这个问题,就涉及到复杂视图的场景。想想看,当视图内的元素不断增加时,代码上如何处理,还要在一个render函数里折腾吗?肯定不会。我猜你已经想到了,要把大问题拆小。ReactJs给出的解决方法就是把大视图拆成若干个小视图,每个视图都有自己的render函数,在JSX中可以直接使用视图标签。看一个例子。

var Score = React.createClass({ 
 initialState: function() { 
 return {amount: 1000}; 
 }, 


 function handleClickAdd() { 
 this.setState({amount: this.states.amount + 1}); 
 } 


 render: function() { 
 var title = ’Hello, ' + this.props.name; 
 var description = 'You have ' + this.states.amount + ' score now.'; 


 return ( 
 <div className="score-board"> 
 <h1>{title}</h1> 
 <p>{description}</p> 
 <button onClick={handleClickAdd}>+1</button> 
 </div> 
 ); 
 } 
}); 


var ScoreList = React.createClass({ 
 render() { 
 return ( 
 <ul className="score-list"> 
 <li><Score name="Tom" /></li> 
 <li><Score name="Jerry" /></li> 
 </ul> 
 ); 
 } 
}); 


ReactDOM.render( 
 <ScoreList />, 
 document.getElementById('content') 
);

这个例子中有两类View,分别是Score和ScoreList。ScoreList的render函数中使用Score标签并给出配置项name的值。详细看一下Score,ReactJs提供createClass方法定义视图,在render函数中通过this.props访问外部传入的配置项,通过this.states访问视图内部的状态。从意图上看,props外部传入视图的配置项,拥有者是父视图,视图内部只能读取配置项,states的拥有者是视图自身。

区分props和states的结果就是,子视图没办法直接改变父视图,视图改变一定是自触发改变的视图开始向子视图传播。对上面的例子,当Tom的Score改变时,ScoreList其他部分一定不会改变,所以视图更新从Tom的Score视图开始就可以,这就保证了能更高效地计算视图变化,再加上VirtualDom的使用,使ReactJs的效率大大超过其他框架。

当子视图需要改变父视图时,也一定是从父视图开始向下更新。假如上面的例子中ScoreList还有平均分的视图,当Tom的分数改变时,需要更新ScoreList中的平均分。这就需要Score视图在处理”+1”输入时把变化通知到ScoreList,做法时给Score增加配置项,ScoreList中定义更新平均分的函数并把函数作为配置项传给Score。当ScoreList更新时,因为Jerry的props和states都没变化,所以Jerry的Score视图不需要更新。

这就是ReactJs的全部秘密了(不过Web前端本身是一个复杂系统,你还需要了解更多其他内容)。

Share

前、后端分离,谁值得拥有?

近两年来,前、后端分离的架构得到越来越多的认可,越来越多的团队在尝试、推广这种架构。但在团队采纳这种架构之前依然需要冷静思考,这是不是自己需要的?

什么是前、后端分离?

字面理解,前端与后端分离。以Web系统为例,浏览器一端的显示、交互、逻辑处理是系统的前端;前端需要获取数据、持久化数据、通知其他系统,这些无法在浏览器中单独完成,需要后端提供服务。很明显前端系统、后端系统已经分离,那为什么还要强调分离呢?此分离非彼分离,系统的实例是分离的,但系统的母体(代码)未必分离。所以,在这里前、后端分离指的是前、后端代码(在组织形式、调用结构等方面)分离。

有些团队会认为单页面应用(Single Page Application)就是前、后端分离,持这种说法的往往是后端背景较重的团队,因为单页面应用往往用到一些前端框架,把后端同学从输出前端元素的痛苦中解救出来。

还有人认为前端和后端通过API通讯就是前、后端分离,这只是分离前、后端代码的一种手段。

前、后端代码的组织形式

前、后端代码的组织形式有三种,分离、不分离、分离也不分离。第一种形式-分离,最基本的要求是前端代码和后端代码各自有独立的代码库,更好的做法是前端代库自带假后端,可以独立进行开发测试,而后端代码中包括前、后端交互的测试用例。第二种形式-不分离,前端代码、后端代码在同一个代码库,没有明显的前、后端概念,读取数据库和生成HTML、CSS、Javascript的代码混在同一个文件或目录中。可能使用了一些模板技术,但总体还是输出前端元素的思路。第三种形式-分离也不分离,这是介于第一和第二种之间的形式。可能有或没有独立的代码库,但前端代码和后端代码至少在不同的目录中。后端代码不关心或很少关心前端元素的输出。但不像第一种形式,前端代码往往不带假后端,不能独立进行开发测试,而后端往往没有前、后端交互的测试用例。

目前很多正在转换或刚刚转换到前、后端分离架构的系统往往采用第三种形式。比如,Rails背景的团队会分离出Rails API,把前、后端放在不同的代码库中,但开发过程中,往往会把前、后端代码放在同一个编辑环境中,因为前端代码目录中没有足够的信息进行独立开发,而后端代码目录也没有足够的信息确定是否会影响到前端。再比如,有的团队可能用到了AngularJS,EmberJS,ReactJS等前端框架,这些框架的独立性和前端为中心的思考方式导致很自然地分离出单独的前端目录,但是开发过程中依然需要前端代码和后端代码同时存在才能顺利进行。

前、后端代码分离的原因

产品是组织沟通结构的缩影 – 康威定律

前、后端代码的分离又一次验证了康威定律。前、后端代码分离表明前端团队和后端团队的越来越独立,以至于他们之间需要一个明确的界限。与其说前、后端团队的独立,不如说是前端团队的壮大。前端不再是人数很少的几个美工、设计角色的附属人员,前端也是有框架开发、组件开发、应用开发、UX设计等角色的独立团队了。

很容易理解前端团队的独立。一方面Web应用的前端越来越“重”,体现在交互越来越丰富、对页面操作的体验要求更快更炫,这使得前端的逻辑愈加复杂、页面渲染多样化。另一方面,多种端化的需求越来越多。这一切给前端融入了很多的技术,前端已今非昔比,其技术复杂性可以和后端相提并论,也就需要相应能力和数量的团队。

前、后端代码分离带来的好处有很多,比如通过分离关注点使代码职责单一产生更容易维护的代码,减少前端代码对后端开发人员的干扰,提高前端响应速度,提高后端性能,等等。这些好处都是结果,其原因还是技术复杂性带来的团队结构变化。

有些框架或平台花费很大精力做到前后端统一,比如Rails,Meteor,它们就不强调前后端,把前后端很好地融合在一起。这或许是一种方向,把技术复杂性降低,以至于开发人员可以同时掌握前、后端开发的所有技能。如果能做到这一点,也就不需要独立的前端团队,自然也就不需要强调前后端分离。不过就目前看,这类框架和平台还是有其适用范围,在系统的规模和性能需求上升到一定规模后还是不能掩盖技术的复杂性。

谁需要前、后端分离?

Web应用的需求和多终端化推动了前端技术的进步,但不意味着所有系统都有非常复杂的前端,因此不应该不假思索地采用前、后端分离。

我建议先区分系统的前端类型再考虑团队的人员结构,因为前端类型不同意味着不同的前端开发工作量。可以根据前端的轻重把系统分为三类,轻前端、重前端、不轻不重的前端。系统的类型没有严格的界限,取决于当时的技术水平以及决策人对技术的了解程度,对于一个非常熟悉Responsive的人可能不认为适应多终端是个难题,但放在两年前对于一个对CSS没有兴趣的人会认为适应多终端是个很重的需求。我根据自己仅有的一点点前端技术给出一个我认为的划分。

轻前端类型的系统具有以下特点:

  • 对页面布局、配色、字体没有具体要求,好看就行
  • 只有比较简单的特效
  • 只有简单的表单验证、表单提交
  • 几乎没有自定义的拖拽、滚动操作
  • 不需要Responsive,在不同终端布局能适应即可
  • 不需要Native App

重前端类型的系统具有以下特点:

  • 对页面布局、配色、字体有具体要求,甚至有一些创新性的设计
  • 有很多特效
  • 有复杂的业务逻辑
  • 有自定义的拖拽、滚动操作
  • 需要Responsive
  • 需要Native App(允许Hybird)

不轻不重的前端介于轻前端和重前端之间:

  • 对页面布局、配色、字体有一些指导性的要求
  • 有一些特效
  • 有简单的业务逻辑,后端愿意承担更多的业务逻辑以简化前端
  • 有或没有自定义的拖拽、滚动操作
  • 不需要Responsive,在不同终端布局能适应即可
  • 需要Native App(Hybird即可)

对于轻前端的系统,不必追求前、后端分离。一方面因为涉及的前端技术并不复杂,开发人员应该能够掌握大部分技能,不会需要花费很多时间学习新技能。另一方面,把前、后端代码组织在一起也便于开发过程中查找相关信息。当然了,并不提倡把前、后端的代码混杂在一起,还是要做到关注点分离(Separate of Concern)。前、后端代码还是应该在顶级目录做区分,只不过在设计决策时让后端承担更多的职责。比如,后端提供前端专用的API接口,使得前端少做或不做转换。

对于不轻不重的系统,建议综合考虑团队的人员结构和未来的发展方向。如果团队前端人员占比不高并且不认为未来会持续做下去,那么就不用过分追求前、后端分离。如果团队前端人员占比不高,但对未来有更高期望,那么建议立即转向前、后端分离,并注重建设前端团队,在设计决策时要避免后端过度承担职责。如果已经有相当规模的前端团队,建议立即转向前、后端分离,并且尽早做到独立代码库,前端有假后端,后端有针对前对的测试用例,使前、后端开发真正分离。

对于重前端的系统,建议采用前、后端分离架构,并且应该尽早完善前端团队建设,确保前端团队和后端团队的平等地位。

Share