Backbonejs RESTful 实践

文章目录

    最近接手的工作项目都是些列表展示,增删改查类的前端。所以先抽空总结一下,
    磨刀不费砍柴功。

    使用 bower 简化 backbonejs 项目初始化

    使用 backbonejs 这类框架比较恶心的一点是,每次项目初始化都要手动下载一遍各种依赖。
    以 backbonejs 为例,其依赖

    • underscore
    • jquery (Backbone.View 中的 DOM 操作)

    平常的做法是,手动下载 backbonejs, underscore, jquery 依次放到 js/lib 目录下。
    完成后,5分钟就过去了。特别打击新建项目的积极性。于是上帝创造了 Bower

    sudo npm install -g bower
    

    新建 bower 的配置文件

    bower init
    

    安装 backbone

    bower install backbone --save
    

    可以看到当前目录下多了一个 bower_components 目录, tree bower_components 目录结果如下

    bower_components/
    ├── backbone
    │   ├── backbone.js
    │   ├── backbone-min.js
    │   ├── backbone-min.map
    │   ├── bower.json
    │   └── LICENSE
    └── underscore
        ├── bower.json
        ├── LICENSE
        ├── README.md
        ├── underscore.js
        ├── underscore-min.js
        └── underscore-min.map
    

    但是没有 jquery 的踪影,需要手动执行

    bower install jquery --save
    

    问题来了,bower 会将所有的 jquery 源码文件下载到 bower_components 目录下。
    是否需要将这部分文件置于 git 的管理之下呢?

    参考 git - Should bower_components be gitignored? - Stack Overflow 这里的讨论。

    git 管理 bower_components 目录的好处是,在无法访问三方依赖 git 仓库时,不会影响部署。
    不妥之处也很明显,git 仓库会变的异常臃肿。

    纠结之后,我决定还是将 bower_components 置于 git 仓库管理之下,就是担心部署时出问题。
    单独为 bower_components 写几条 gulp 配置反而增加了初始化复杂度。

    结合 requirejs 规划 backbone 的代码组织

    为何要规划代码组织?要理清这个问题,首先要明确功能点有哪些:

    以招聘类网站后台为例,

    • 招聘信息列表展示
    • 新增招聘信息
    • 更新招聘信息
    • 删除招聘信息
    • 查询招聘信息

    功能很简单,看上去写在一个 js 文件里就可以了嘛!实则不然,用户发布招聘信息页面
    和后台发布招聘信息页面实际上都要用到同一个 model。拆分出一个独立的 model 文件进行复用已是必然。

    ├── collections
    │   └── jobs.js
    ├── models
    │   └── job.js
    ├── templates
    │   ├── edit_job.html
    │   ├── list_job.html
    │   └── tr_job.html
    └── views
    │   ├── edit_job.js
    │   ├── list_job.js
    │   ├── new_job.js
    │   └── tr_job.js
    ├── main.js   // Bootstrap, 用于设置 requirejs 的配置, 定义 Router, 并初始化 router
    

    讨论:

    之所以这里没有定义专门的 router.js 文件,而是合并入 main.js,主要目的是精简文件。

    假设,不采用精简的方式,而是拆分出

    • main.js 用于设置 requirejs 的配置
    • app.js 用于加载 Router
    • router.js 用于定义 router

    这个方式看似合理,但是实际上并没有让代码结构清晰。
    例如,app.js 就是个摆设,实际没有价值。还不如在 main.js 里加载 router.js,
    而 router.js 又没有任何复用价值,所以直接在 main.js 定义并初始化即可。

    实际上 main.js 即是一个 app.js

    后端加载页

    新建一个 html 页面

    <div id="container">
    </div>
    
    <script type="text/javascript" data-main="/static/src/js/main.js" src="/static/bower_components/requirejs/require.js"></script> 
    

    main.js 代码结构

    require.config({
    	baseUrl: '/static/src/js/',
    
    	paths: {
    		jquery: '../../bower_components/jquery/dist/jquery.min',
    		underscore: '../../bower_components/underscore/underscore-min',
    		backbone: '../../bower_components/backbone/backbone-min',
    		text: '../../bower_components/text/text',
    		moment: '../../bower_components/moment/min/moment.min',
    
    		admin_app: './admin_app'
    	},
    
    	// http://requirejs.org/docs/api.html#config-shim
    	// 由于 underscore.js 和 backbone.js 并不支持 AMD, 并且未使用 define 方法,
    	// 所以需要使用 shim 配置.
    	// 历史上,backbone 是支持 requirejs 的,但是作者认为一个类库专门为特定的
    	// 加载器添加支持是错误的做法,所以去掉了对 requirejs 的支持。支持作者:)
    	shim: {
    		underscore: {
    			deps: [],
    			exports: "_"
    		},
    		backbone: {
    			deps: ['jquery', 'underscore'],
    			exports: "Backbone"
    		}
    	},
    });
    
    
    define(function (require) {
    	var Backbone = require('backbone');
    	var JobListView = require('views/list_job');
    	var NewJobView = require('views/new_job');
    	var EditJobView = require('views/edit_job');
    
    	var AdminRouter = Backbone.Router.extend({
    		routes: {
    			'new-job': 'newJob',
    			'edit-job/:jobID':	'editJob',
    
    			// Default
    			'*actions': 'index'
    		},
    
    		newJob: function() {
    			var newJobView = new NewJobView();
    			newJobView.render();
    		},
    
    		editJob: function(jobID) {
    			var editJobView = new EditJobView(jobID);
    			editJobView.render();
    		},
    
    		index: function() {
    			var jobListView = new JobListView();
    			jobListView.render();
    		}
    	});
    
    	var router = new AdminRouter();
    	Backbone.history.start();
    });
    

    list_job.js 代码结构

    define(function(require) {
    	var Backbone = require('backbone');
    	var	Jobs = require('collections/jobs');
    	var JobView = require('views/tr_job');
    	var jobListTemplate = require('text!templates/list_job.html');
    
    	var JobListView = Backbone.View.extend({
    		el: "#container",
    		template: _.template(jobListTemplate),
    
    		events: {
    
    		},
    
    		initialize: function () {
    			this.jobs = new Jobs();
    			this.listenTo(this.jobs, 'reset', this.render, this);
    			this.jobs.fetch({
    				reset: true,
    				success: this.onDataHandler,
    				error: this.onErrorHandler});
    		},
    
    		render: function () {
    			this.$el.html(this.template());
    			var ul = this.$el.find('#jobs-list');
    			this.jobs.each(function(job) {
    				var jobView = new JobView(job);
    				ul.append(jobView.render().el);
    			}, this);
    		},
    
    		onDataHandler: function() {
    			console.log('onData');
    			$('.am-progress-bar').width('100%');
    			setTimeout(function() {
    				$('.am-progress').hide();
    			}, 1000);
    		},
    
    		onErrorHandler: function() {
    			console.log('onError');
    			$('.am-progress-bar').html('网络错误,拉取数据失败,请重试');
    		}
    	});
    
    	return JobListView;
    });
    

    参考

    关于作者 🌱

    我是来自山东烟台的一名开发者,有感兴趣的话题,或者软件开发需求,欢迎加微信 zhongwei 聊聊,或者关注我的个人公众号“大象工具”, 查看更多联系方式