3

经常在各种框架之间切换使用是种什么体验?

 1 year ago
source link: https://blogread.cn/it/article/7995?f=hot1
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

经常在各种框架之间切换使用是种什么体验? -- JavaScript -- IT技术博客大学习 -- 共学习 共进步!

您现在的位置首页 --> JavaScript --> 经常在各种框架之间切换使用是种什么体验?

经常在各种框架之间切换使用是种什么体验?

浏览:1262次  出处信息

在一个喜欢尝鲜的团队里面工作,总会碰到这种情况.前一个项目用的这个框架这种构建,下一个项目就变成新的框架那种构建,上来就blablabla先学一通,刚觉得安心,接到个另外需求,到手一看.又变了一套 T,T , 又要重复上述步骤..如果能在各种框架和开发思路里面游刃有余,当然是最好的,可是多少总会对开发同学产生一些影响,那么各个框架之间的差异到底有多大呢?切换来去又会影响到哪些开发体验呢?且看我边做示例边分解…

我挑选了三个我认为比较有代表性的框架来实现同一个todo list的小项目,项目基本介绍如下:

示意图:

主要功能是协助记录一些计划,防止遗忘,完成后有直观反馈。
共有4需要个交互的地方:

  • - 可输入的计划内容的区域。

  • - 确认新增计划到,计划列表上的行为。

  • - 每个计划需要一个可改变状态的行为,让计划在’完成/未完成’的状态切换。

  • - 有可以清理已实现所有的计划的行为。

每个交互直接对应到了上图中的几个箭头。其中列表的状态展示会改变其样式。下面介绍用各种不同框架时的开发思路以及代码:

backbonejs

backbonejs的特点是其推荐使用MVC的方式来分层和组织代码.依赖jQuery,underscore
这里虽然是个单页应用,但并没有明显的操作路径,交互点和功能都是通过事件触发来推进,所以这里Controller层的概念会被淡化到事件中去.没有一个总控制器,基本数据流模型就如下图:

官网上还有其它数据流模型,有兴趣的同学可以去看看,下面是我的目录结构,lib里面是基础公共库,app里面是业务文件

在保证项目结构清晰的情况下.我会尽量拆分文件,这样便于管理和扩展,业务文件我按照model和view做了下拆分.程序入口则是todo.html,如下,文档结构非常简单

<—todo.html—>
<!DOCTYPE html>
<html>
<head>
   <meta charset=’utf-8’>
   <title>BtoDo</title>
   <script type=’text/javascript’src=’lib/underscore.min.js’></script>
   <script type=’text/javascript’src=’lib/jquery-2.1.4.min.js’></script>
   <script type=’text/javascript’src=’lib/backbone.min.js’></script>
   <style type=’text/css’>
       .list-wrapper{
           margin:20pxauto;
           width:300px;
       .done-true {
         text-decoration:line-through;
         color:grey;
       input[type=’checkbox’]{
           vertical-align:middle;
       .archive-btn{
           color:blue;
           cursor:pointer;
   </style>
</head>
<body>
   <div class=’list-wrapper’id=’todo_panel’>
       <h2>Todo</h2>
       <span id=’remaining’></span>remaining[archive]
       <ul id=’todo_list’>
       </ul>
       <form onsubmit=”returnfalse”>
           <input placeHolder=’foo foo’  type=’text’><button class=’add_btn’>Add</button>
       </form>
   </div>
   <script type=’text/template’id=’list_template’>
       <%for(varpindata){%>
       <li<%if(data[p].done){%>class=’done-true’<%}%>>’  <%if(data[p].done){%>checked<%}%>><%=data[p].todo%></li>
       <%}%>
   </script>
   <script type=’text/javascript’src=’app/todo.model.js’></script>
   <script type=’text/javascript’src=’app/todo.view.js’></script>
</body>
</html>

下面是Model,Model可以看成一个描述业务的数据模型,描述越详细,越原子越好,而在此项目中,最原子的数据单元,就是每条计划,所有的需求交互都是围绕每一条计划来行动。而计划是批量的,每个计划都具有相同的行为和操作,所以我声明了一个CollectionCollection是所有相同Model的一个集合,用一个Collection来描述业务数据。声明里给了两个默认计划。全局上挂载这个对象,方便外部文件存取.(项目简单,就直接写了,一般会建议包装一个方法来导出单个文件内部方法或者变量。便于全局管理)

* [todo backbone model file]
(function(global,Backbone){
   vartodoCollection=newBackbone.Collection([{todo:‘some demos’,done:false},{todo:’some other todo’,done:true  }]);
       global.todoCollection=todoCollection;
})(window,Backbone);

再来看看View ,我定义了一个基础类型的View 叫 BaseView , BaseView的数据源是刚才我导出的todoCollection,然后实例化当前类时候会侦听该数据源的动作。然后定义了RemainView ,ListView,PanelView具体作用见下面我的注释。
值得注意的是 RemainView 会覆盖 initialize方法,这里稍微特别。

* [todo backbone view files]
(function(global,Backbone){
   vartodoCollection=global.todoCollection;
    * BaseView
    * 基础视图,指定了默认数据源,初始化行为
   varBaseView=Backbone.View.extend({
           collection:todoCollection,
           initialize:function(){
               this.listenTo(this.collection,‘add’,this.render);
               this.listenTo(this.collection,‘remove’,this.render);
        * RemainView
        * 当前剩余计划数与总计划数的视图
        * 方便用户了解当前所有任务完成状态。
       RemainView=BaseView.extend({
            * RemainView表示列表计划的完成状况,需要在状态改变时也做出对应
            * 行为,所以它有特别的初始化行为。
            * changed事件本来也可以归到父类的初始化函数当中,但这个事件其
            * 实只在此视图中有使用,这样可以减少渲染函数调用次数.
           initialize:function(){
               BaseView.prototype.initialize.apply(this,[]);
               this.listenTo(this.collection,‘changed’,this.render);
           render:function(model,collection){
               vartpl=_.template(‘<%=rest%>of<%=total%>‘),
                   rest=0  ,
                   data=collection.toJSON();
                   for(pindata){
                       if(!data[p].done){
                           ++rest;
               this.$el.html(tpl({rest:rest,total:data.length}));
        * ListView
        * 展示具体计划的列表视图
       ListView=BaseView.extend({
           events:{
             ‘click.check_box’:‘checked’
           render:function(model,collection){
               vartpl=_.template($(‘#list_template’).html());
               this.$el.html(tpl({data:collection.toJSON()}));
           checked:function(e){
               vartarget=e.target,
                   key=target.getAttribute(‘data-index’),
                   model=this.collection.at(key);
                   model.set(‘done’,target.checked);
                    * 手动触发
                   this.collection.trigger(‘changed’,{},this.collection);
        * PanelView
        * 操作面板视图,这里决定新增以及完成动作的行为引起的视图变化
       PanelView=BaseView.extend({
           events:{
             ‘click.add_btn’:‘add’,
             ‘click.arc_btn’:‘archived’
           add:function(e){
               vartarget=e.target,
                   $input=$(target.parentNode).find(‘input’);
               this.collection.add({
                   todo:$input.val(),
                   done:false
               $input.val(‘’);
           archived:function(){
               this.collection.remove(this.collection.where({done:true}));
        * 文档加载完全后开始实例化各个类
       global.onload=function(){
           varlist=newListView({
               el:$(‘#todo_list’)
           remaining=newRemainView({
               el:$(‘#remaining’)
           Panel=newPanelView({
               el:$(‘#todo_panel’)
            * 放入默认数据
           todoCollection.add([{todo:‘some demos’,done:false},{todo:’some other todo’,done:true  }]);
})(window,Backbone);

angularjs

angularjs 自身集成了一套数据视图双向绑定的模版语法,同时约定了一个大致的应用开发流程.

一些基本概念如下:

<-!html->
<!doctype html>
<html ng-app=”todoApp”>
 <head>
   <script type=”text/javascript”src=’lib/angular.min.js’></script>
   <script src=”app/todo.js”></script>
   <style type=”text/css”>
   .done-true {
     text-decoration:line-through;
     color:grey;
   .list-wrapper{
       margin:20pxauto;
       width:300px;
   </style>
 </head>
 <body>
   <div class=’list-wrapper’>
       <h2>Todo</h2>
       <div ng-controller=”TodoListController as todoList”>
         <span>{{todoList.remaining()}} of {{todoList.todos.length}} remaining</span>
         [<ahref="””">archive</a>]
         <ul class=”unstyled”>
           <li ng-repeat=”todo in todoList.todos”>
             <input type=”checkbox” ng-model=”todo.done”>
             <span class=”done-{{todo.done}}”>{{todo.text}}</span>
           </li>
         </ul>
         <form ng-submit=”todoList.addTodo()”>
           <input type=”text”ng-model=”todoList.todoText”  size=”30”
                  placeholder=”foo foo”>
           <input class=”btn-primary”type=”submit”value=”add”>
         </form>
       </div>
   </div>
</body>

这是个取自官网的示例图,可以看到代码非常精简,基本流程如下.声明了一个module容器,然后在controller中处理几个行为,界面行为完全在html模版中来处理,controller里面不再耦合任何dom操作。

//app.js
angular.module(‘todoApp’,[])
   .controller(‘TodoListController’,function(){
       vartodoList=this;
       todoList.todos=[
           {text:’learn angular’,done:true},
           {text:’build an angular app’,done:false}];
       todoList.addTodo=function(){
           todoList.todos.push({text:todoList.todoText,done:false});
           todoList.todoText=‘’;
       todoList.remaining=function(){
           varcount=0;
           angular.forEach(todoList.todos,function(todo){
             count+=todo.done?0:1;
           returncount;
       todoList.archive=function(){
           varoldTodos=todoList.todos;
           todoList.todos=[];
           angular.forEach(oldTodos,function(todo){
               if(!todo.done)todoList.todos.push(todo);

Reactjs

Reactjs官网上说明了它的几个基本特点,

  1. 专注于UI,都是前端库,强调专注于UI又如何理解呢,与backbone,angular相比又有哪点更UI呢?

  2. 等我放完代码后再简单分析。

  3. VIRTUAL DOM,为了方便打字,我简称它为vdom,React提供了一种新的html模版语法形式,和一整套新的接口来支持这个vdom的特性,通过新语法编译和接口调用,来覆盖视图渲染的过程。让界面渲染变得相对透明,这样才可以衍生出服务端渲染和ReactNative的玩法,不用做大改动的前提下,同一套代码能在各种载体中渲染。

  4. DATA FLOW,数据流的规范,推荐和弱强制使用的单向数据流方式来约束代码和界面模块组织,顺着这个思路来确实会有些不同的编码体验。特别是在我刚用前面两个框架写完demo后立即开始写这个的时候,感受比较明显。

下面进入正题看代码,文档并没有特别需要说明的地方,为了方便和我没有配置整套的React环境,只是用了demo环境,引入了一个翻译jsx的库:

<!-html->
<!DOCTYPEhtml>
<html>
 <head>
   <title>HelloReact</title>
   <script src=”lib/react.js”></script>
   <script src=”lib/JSXTransformer.js”></script>
   <style type=”text/css”>
       .done-true {
         text-decoration:line-through;
         color:grey;
       .list-wrapper{
           margin:20pxauto;
           width:300px;
       input[type=’checkbox’]{
           vertical-align:middle;
       .archive-btn{
           color:blue;
           cursor:pointer;
   </style>
 </head>
 <body>
   <div class=’list-wrapper’id=”example”></div>
   <script type=”text/jsx”src=”app/todo.js”></script>
 </body>
</html>

业务代码,专注于UI的库,首先需要的是拆分视图,这点与其它库并无太大区别。但是如何拆,如何确定各个视图之间的关系则是有套路的,且大致过下下面的代码.

// todo.js文件
* ToDoTips 剩余与总计计数展示视图
varToDoTips=React.createClass({
   render:function(){
       varitems=this.props.items,
           remains=0;
           items.map(function(item){
               if(!item.done){
                   ++remains;
       return<span>{remains}/{items.length}</span>;
* TodoList
*      计划列表展示视图
*      包含了两个行为状态,切换计划状态,以及清理已完成计划数  
varTodoList=React.createClass({
   getInitialState:function(){
       return{items:this.props.items};
   shouldComponentUpdate  :function(nextProps){
       this.state.items.push(nextProps.nextItem);
       returntrue;
   archived:function(){
       varitems=this.state.items.concat([]),
           remainItems=[];
           items.forEach(function(item){
               if(!item.done){
                   remainItems.push(item);
           this.setState({items:remainItems});
   toggle:function(e){
       varindex=e.target.getAttribute(‘data-index’),  
           items=this.state.items.concat([]);
           items[index].done=!items[index].done;
           this.setState({items:items});
   render:function(){
       varthat=this;
       varcreateItem=function(item,index){
           varitemClass=item.done?‘done-true’:‘’;
           return<li className={itemClass}><input type=’checkbox’data-index={index}onClick={that.toggle}checked={item.done}/>{item.todo}</li>;
       return(
           <div>
               <span>remaining<ToDoTips items={this.state.items}/> [archive]</span>
               <ul>{this.state.items.map(createItem)}</ul>
           </div>
*  TodoApp 面板入口
*  新增计划行为以及状态。
varTodoApp=React.createClass({
   getInitialState:function(){
       return{items:this.props.sourceData};
   handleSubmit:function(e){
     e.preventDefault();
       vartexts=e.target[‘text’].value  ;
       this.setState({
           nextItem:{
               todo:texts,
               done:false
       e.target[‘text’].value=‘’;
   render:function(){
       return(
           <div>
               <h3>TODO</h3>
               <TodoList items={this.state.items}nextItem={this.state.nextItem}  />
               <form onSubmit={this.handleSubmit}>
                 <input
                   placeholder=’foo foo’
                   name=’text’   />
                 <button>{‘Add#’+(this.state.items.length+1)}</button>
               </form>
           </div>
* [sourceData 初始数据]
* @type {Array}
varsourceData=[
           {todo:’learn React’,done:true},
           {todo:’build an React app’,done:false}];
React.render(
           <TodoApp  sourceData={sourceData}/>,
           document.getElementById(‘example’)

以上是基本视图切分,没看到任何MVC的影子,可以看出基本代码组织方式就是这样,像是一个一个相对高内聚的小型界面组件。然后下面是数据流,顺序是从左到右。这个顺序是由什么来决定的呢?

其实在render方法里面就能发现蛛丝马迹,下面我用一条直观的图来描述这个顺序的诞生。

render方法的调用条件是当前组件数据改变而调用setState时。每个组件内部会保有自己的state,state决定组件的展示状态.可以看到,每个引起状态改变的行为都会相对上层,最原子的状态界面是在最下层,所以在编码初始就需要想清楚哪些行为驱动数据发生变化,才能系统有条理的拆分组件。

为何更强调UI,我个人理解,React基本不提供任何关于url路径的处理方式,
以前B/S架构下出来的模式理念也被淡化成了强调组件,我个人更倾向于把它看成更偏客户端的一种代码组合方式,同时React比较新,工程化痕迹相比前面两个框架会更重。

几种代码都已经浏览到,个人觉得非常影响编码体验主要在以下几点:

  • 逻辑分层方式

上述框架并没有优劣之分.框架我认为都是经验的聚合,总是诞生于自身业务开发中,带着一些业务痕迹,去照顾到各种团队的开发习惯和效率。

最后这篇文章的目的,有需要的时候,为大家的技术选型提供一些其它的思路。
欢迎大家探讨和拍砖 。

建议继续学习:

QQ技术交流群:445447336,欢迎加入!
扫一扫订阅我的微信号:IT技术博客大学习

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK