用ES6編寫AngularJS程序是怎樣一種體驗

AngularJS不用我贅述,前端開發人員必定耳熟能詳。有人稱之爲MVWhatever框架,意思是使用AngularJS,你能夠參考任意範式進行應用開發,不管是MVC、仍是MVVVM都信手拈來,只要你懂,範式在AngularJS手下,均可以輕鬆適配。javascript

隨着各類現代瀏覽器、以及nodeES6的支持,已經有愈來愈多的ES6特性能夠在程序中使用,她們給開發過程帶來的便利不言而喻,舉個小例子,我想從一個數組裏找一些符合條件的數據,放入另外一個數組內,過去咱們這麼寫:css

var list = [],
    i;

for (i = 0; i < originalList.length; i++) {
    var item = originalList[i];
    if (item.gender === 'male') {
        list.push(item);
    }
}

console.log(list); //符合條件的新數組

若是改用數組的高階函數,再配合ES6arrow function,代碼能夠簡潔如斯:html

const list = originalList.filter(item => item.gender === 'male');

console.log(list); //符合條件的新數組

既然有如此優雅的語法糖能讓咱們的開發變得high到爆,那過去咱們認爲屌炸天的AngularJS(如今也屌炸天,只不過還有Angular2, React, vue橫空出世)是否是能夠用ES6來寫?少年不要懷疑,真的能夠哦!
圖片描述前端

一個良好、快速、簡潔的starter工具備利於咱們對ES6編寫AngularJS的深刻認知,因此我要用一個骨架生成器generator-es6-angular來建立新項目,該generator是依託於yeoman的腳手架。vue

安裝yo

npm install -g yo

請注意前綴sudo,若是你使用的是unix like操做系統的話html5

安裝generator-es6-angular

npm install -g generator-es6-angular

請注意前綴sudo,若是你使用的是unix like操做系統的話java

使用generator-es6-angular建立項目

先找個你喜歡的目錄,而後運行下面的命令,由於一會新項目會直接建立在該目錄下。node

yo es6-angular

上面命令回車後,生成器會問你以下問題,老實做答便可(注意: 對單頁應用沒經驗的孩紙,在Use html5 model這個問題上,請選擇No; 當問你Which registry would you use?時,國內用戶選擇第一個淘寶鏡像安裝速度會快不少)webpack

圖片描述

當命令執行完畢後,你就能在當前目錄下看到剛纔建立的項目了,本例中我使用的project namees6-demogit

開啓調試之旅

#進入剛建立的項目目錄
cd es6-demo
#啓動調試服務
npm start

而後你就能夠在http://localhost:8080下,看到剛建立的項目的運行效果了:

圖片描述

項目簡介

骨架結構

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, 分爲featuresfw兩大部分。這個內容略多,我後面詳述吧。

  • 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命令會用這個配置,生成的結果都會給文件名加hashjavascript文件也會壓縮。

可用工具介紹

  • 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();
            }
        };
    }
};

同時固然也能夠聲明諸如:組件、過濾器之類的公共工具,詳見:common

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';
//引入features
import 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中要用到的service
import 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: '<home></home>'//路由對應組件
    }
];

用ES6寫<home>組件

//引入該組件對應的css,注意這裏不會有像vue那樣的做用域,
//不過能幫助你分離css內容,也不錯的
import './home.css';

//導出組件聲明對象
export default {
    template: `
        <logo></logo>
        <description></description>
        <github></github>
        <todo-app list="$ctrl.todos" loading="$ctrl.loading" on-toggle="$ctrl.toggleTodo(todo)" on-add="$ctrl.addTodo(todo)" on-archive="$ctrl.archive()"></todo-app>
        <footer></footer>
    `,
    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上給我提issue也何嘗不可呢

相關文章
相關標籤/搜索