不用我赘述,前端开发人员一定耳熟能详。有人称之为MVWhatever
框架,意思是使用AngularJS
,你可以参考任意范式进行应用开发,无论是MVC
、还是MVVVM
都信手拈来,只要你懂,范式在AngularJS
手下,都可以轻松适配。
随着各种现代浏览器、以及对的支持,已经有越来越多的ES6
特性可以在程序中使用,她们给开发过程带来的便利不言而喻,举个小例子,我想从一个数组里找一些符合条件的数据,放入另一个数组内,过去我们这么写:
var list = [], i;for (i = 0; i < originalList.length; i++) { var item = originalList[i]; if (item.gender === 'male') { list.push(item); }}console.log(list); //符合条件的新数组
如果改用数组的高阶函数,再配合ES6
的,代码可以简洁如斯:
const list = originalList.filter(item => item.gender === 'male');console.log(list); //符合条件的新数组
既然有如此优雅的语法糖能让我们的开发变得high到爆,那过去我们认为屌炸天的AngularJS
(现在也屌炸天,只不过还有Angular2
, React
, vue
横空出世)是不是可以用ES6
来写?少年不要怀疑,真的可以哦!
一个良好、快速、简洁的starter
工具有利于我们对ES6
编写AngularJS
的深入认知,所以我要用一个骨架生成器来创建新项目,该generator
是依托于的脚手架。
安装yo
npm install -g yo
请注意前缀
sudo
,如果你使用的是unix
like操作系统的话
安装generator-es6-angular
npm install -g generator-es6-angular
请注意前缀
sudo
,如果你使用的是unix
like操作系统的话
使用generator-es6-angular
创建项目
先找个你喜欢的目录,然后运行下面的命令,因为一会新项目会直接创建在该目录下。
yo es6-angular
上面命令回车后,生成器会问你如下问题,老实作答即可(注意: 对单页应用没经验的孩纸,在Use html5 model
这个问题上,请选择No
; 当问你Which registry would you use?
时,国内用户选择第一个淘宝镜像安装速度会快很多)
当命令执行完毕后,你就能在当前目录下看到刚才创建的项目了,本例中我使用的project name
是es6-demo
。
开启调试之旅
#进入刚创建的项目目录cd es6-demo#启动调试服务npm start
然后你就可以在下,看到刚创建的项目的运行效果了:
项目简介
骨架结构
es6-demo├── etc│ └── config.js├── img│ └── ...├── js│ ├── features│ │ ├── about│ │ │ ├── components│ │ │ │ ├── about.co│ │ │ │ ├── about.css│ │ │ │ └── subs│ │ │ │ ├── more│ │ │ │ │ ├── index.co│ │ │ │ │ └── more.css│ │ │ │ └── why│ │ │ │ ├── index.co│ │ │ │ └── why.css│ │ │ ├── main.js│ │ │ └── routes.js│ │ ├── common│ │ │ ├── components│ │ │ │ ├── main.js│ │ │ │ ├── menu.co│ │ │ │ └── menu.css│ │ │ ├── directives│ │ │ │ ├── autofocus.js│ │ │ │ └── main.js│ │ │ ├── main.js│ │ │ └── runners│ │ │ ├── main.js│ │ │ └── routeIndicator.js│ │ ├── home│ │ │ ├── components│ │ │ │ ├── home.co│ │ │ │ └── home.css│ │ │ ││ │ │ ├── main.js│ │ │ ├── routes.js│ │ │ └── service│ │ │ └── HomeService.js│ │ └── main.js│ ├── fw│ │ ├── config│ │ │ ├── SSOConfig.js│ │ │ ├── main.js│ │ │ └── routerConfig.js│ │ ├── ext│ │ │ └── main.js│ │ ├── helper│ │ │ ├── event.js│ │ │ ├── ngDeclare.js│ │ │ └── object.js│ │ └── value│ │ ├── main.js│ │ └── routesValue.js│ ├── application.co│ ├── application.css│ ├── index.js│ └── main.js├── index.html_vm├── package.json├── postcss.config.js├── webpack.config.js└── webpack.config.prod.js
etc
, 一些公共配置性内容,可以放在这里,方便查找、通用img
, 用我多说么?放图片的啦js
, 分为features
和fw
两大部分。这个内容略多,我后面详述吧。index.html_vm
, 单页应用html
模版,最终的html
会由webpack
根据这个模版生成package.json
, 项目的npm
描述文件,那些具体的工具命令(譬如刚才用过的npm start
,都在这里面定义好了)postcss.config.js
,postcss
的配置文件webpack.config.js
, 开发、调试环境使用的webpack
配置webpack.config.prod.js
, 正式运行环境使用的webpack
配置。npm run release
命令会用这个配置,生成的结果都会给文件名加hash
,javascript
文件也会压缩。
可用工具介绍
npm start
, 启动调试服务器,使用webpack.config.dev.js
作为webpack
配置,不直接生成物理文件,直接内存级别响应调试服务器资源请求。而且内置hot reload
,不用重启服务,修改源码,浏览器即可刷新看到新效果npm run release
, 使用webpack.config.prod.js
作为webpack
配置,生成压缩、去缓存化的bundle
文件到es6-demo/build
目录下。也就是说,如果你要发布到生产环境或者其它什么测试环境,你应该提供的是es6-demo/build
目录下生成的那堆东西,而不是源码。
js
目录介绍
features
common
那些通用的组件、指令、过滤器、服务。。。通通应该放在这里,譬如为了演示方便,我已经在features/common/directives
里写了一个autofocus.js
的指令,拿去用,不要客气。代码如下:
export default { type: 'directive',//声明这是一个指令 name: 'autofocus',//声明指令名 //声明指令构造函数,详见:https://docs.angularjs.org/api/ng/type/angular.Module#directive directiveFactory: function() { 'ngInject'; return { restrict: 'A', link($scope, element) { element[0].focus(); } }; }};
同时当然也可以声明诸如:组件、过滤器之类的公共工具,详见:
about
home这两个就是纯粹为了演示“功能 <对应> 路由”这个小原则而做的,你可以分别在这两个feature
下找到一个routes.js
,里面的内容就描述了该功能对应一个(或多个)路由,是何等的easy。至于最后这个路由会怎样被这个骨架使用,小伙伴们,好好研究哦!
fw
这里面都是些所谓"框架"级别的设置,有兴趣的话挨个儿打开瞧瞧嘛,没什么大不了的。
特别注意,大部分时候,你的开发都应该围绕
features
目录展开,之所以叫fw
,就是和具体业务无关,除非你需要修改框架启动逻辑,路由控制系统。。。,否则不需要动这里的内容
源码介绍
js/index.js
入口文件
/** * * 这里连用两个ensure,是webpack的特性,可以强制在bundle时将内容拆成两个部分 * 然后两个部分还并行加载 * *///第一个部分是一个很小的spinner,在并行加载两个chunk时,这个非常小的部分90%会竞速成功//于是你就看到了传说中的loading动画require.ensure(['splash-screen/dist/splash.min.css', 'splash-screen'], function(require) { require('splash-screen/dist/splash.min.css').use(); require('splash-screen').Splash.enable('circular');});//由于这里是真正的业务,代码多了太多,所以体积也更大,加载也更慢,于是在这个chunk加载完成前//有个美好的loading动画,要比生硬的白屏更优雅。//放心,这个chunk加载完后,loading动画也会被销毁require.ensure(['css/main.css', 'splash-screen', './main'], function(require) { require('css/main.css').use(); //这里启动了真正的“框架” var App = require('./main').default; (new App()).run();});
js/main.js
“框架”启动器
//引入依赖部分import angular from 'angular';//引入Object帮助库import {pluck} from './fw/helper/object';//引入feature注册工具import {declareFeatures, declareValues, declareDirectives, declareComponents, declareRunners, declareFilters} from './fw/helper/ngDeclare';//引入三方依赖import Extensions from './fw/ext/main';//引入项目配置import Configurators from './fw/config/main';//引入项目常量设置import Values from './fw/value/main';//引入featuresimport Things from './features/main';//引入根组件import Application from './application';//引入启动spinner控制器import {Splash} from 'splash-screen';class App { constructor() { //这里相当于ng-app的名字 this.appName = 'es6-demo'; //找到所有的features this.features = Things.filter(t => t.type === 'feature' && t.name); } //检查项目基本设置 validate() { if (!this.features || this.features.length === 0) { return console.warn('No features loaded'); } const modNames = pluck(this.features, 'name').sort(); for (let i = 0; i < modNames.length - 1; i++) { if (modNames[i] === modNames[i + 1]) { throw new Error('Duplicated Module: [ ' + modNames[i] + ' ], you have to specify another name'); } } } //从features实例中提取AngularJS module name //并将这些name作为es6-demo的依赖 //会在下面createApp时用到 findDependencies() { this.depends = [...Extensions, ...this.features.map(f => f.name)]; } //创建angular应用 createApp() { declareFeatures(this.features); this.app = angular.module(this.appName, this.depends); this.app.component('application', Application); } //配置es6-demo configApp() { Configurators.forEach(Configurator => { this.app.config(Configurator.config); }); } //注册fw下的“框架”级service registerServices() { declareValues(this.app, Values); declareDirectives(this.app, Things.filter(t => t.type === 'directive')); declareComponents(this.app, Things.filter(t => t.type === 'component')); declareRunners(this.app, Things.filter(t => t.type === 'runner')); declareFilters(this.app, Things.filter(t => t.type === 'filter')); } //看到了么,这里我会销毁loading动画,并做了容错 //也就是说,即便你遇到了那微乎其微的状况,loading动画比业务的chunk加载还慢 //我也会默默的把它收拾掉的 destroySplash() { Splash.destroy(); require('splash-screen/dist/splash.min.css').unuse(); setTimeout(() => { if (Splash.isRunning()) { this.destroySplash(); } }, 100); } //启动AngularJS app launch() { angular.bootstrap(document, [this.appName]); } //顺序激活所有模块 run() { this.validate(); this.findDependencies(); this.createApp(); this.configApp(); this.registerServices(); this.destroySplash(); this.launch(); }}export default App;
用ES6写Feature
features/home/main.js
//引入路由import routes from './routes';//引入所有本feature中要用到的组件import home from './components/home';import logo from './components/subs/logo';import description from './components/subs/description';import github from './components/subs/github';import todoApp from './components/subs/todo';import footer from './components/subs/footer';//引入本feature中要用到的serviceimport HomeService from './service/HomeService';export default { type: 'feature',//声明该模块是一个feature name: 'home',//声明feature的名字,必须的 routes,//倒入路由 component: {//注册所有用到的组件 home, logo, description, github, todoApp, footer }, service: {//注册所有用到的service HomeService }};
用ES6写路由
简单到没朋友
export default [ { id: 'home',//为该路由起一个唯一标识符 isDefault: true,//声明是否为默认路由 when: '/home',//路由路径 template: ''//路由对应组件 }];
用ES6写<home>组件
//引入该组件对应的css,注意这里不会有像vue那样的作用域,//不过能帮助你分离css内容,也不错的import './home.css';//导出组件声明对象export default { template: ``, controller: class { //下面是依赖注入的关键,通过https://github.com/schmod/babel-plugin-angularjs-annotate实现 /*@ngInject*/ constructor(HomeService) { this.HomeService = HomeService; this.todos = []; this.loading = true; } $onInit() { this.HomeService .getInitTodos() .then(todos => { this.todos = todos; this.loading = false; }); } addTodo(todo) { this.todos = [todo, ...this.todos]; } toggleTodo(todo) { this.todos = this.todos.map(t => { if (t.txt !== todo.txt) { return t; } return { finished: !todo.finished, txt: t.txt }; }); } archive() { this.todos = this.todos.filter(todo => !todo.finished); } $onDestroy() {} }};
最后,你可能还有其它问题,直接来,或者Github
上给我提也未尝不可呢