Angular1.x + TypeScript 编码风格

说明:参照了Angular1.x+es2015的中文翻译,并将我的以为不合适、不正确的地方进行了修改,欢迎批评指正。

架构,文件结构,组件,单向数据流以及最佳实践

来自@toddmotto团队的实用编码指南javascript

Angular 的编码风格以及架构已经使用ES2015进行重写,这些在AngularJS 1.5+的变化能够更好帮助您的更好的升级到Angular2.。 这份指南包括了新的单向数据流,事件委托,组件架构和组件路由。css

老版本的指南你能够在这里找到,在这里能看到最新的。html

加入终极的 AngularJS 学习经验,彻底掌握初级和高级的 AngularJS 特性,构建更快,易于扩展的真实应用程序。

目录

  1. 模块结构java

    1. 基本概念
    2. 根模块
    3. 组件模块
    4. 公共模块
    5. 低级别模块
    6. 文件命名规范
    7. 可扩展的文件结构
  2. 组件webpack

    1. 基本概念
    2. 支持的属性
    3. 控制器
    4. 当向数据流和事件
    5. 有状态组件
    6. 无状态组件
    7. 路由组件
  3. 指令git

    1. 基本概念
    2. 推荐的属性
    3. 常量和类
  4. 服务angularjs

    1. 基本概念
    2. 服务的类
  5. 样式
  6. TypeScript 和工具
  7. 状态管理
  8. 资源
  9. 文档
  10. 贡献

Modular architecture

Angular中的每一个模块都是一个模块组件。模块组件是包括了组件逻辑,模板,路由和子组件的根。es6

Module theory

模块的设计直接反映到咱们的文件夹结构,从而保证咱们项目的可维护性和可预测性。 咱们最好应该有三个高层次的模块:根模块,组件模块和经常使用模块。根模块定义用于启动 app 和相应的模板的基础模块。 而后导入咱们须要依赖的组件和通用模块。组件和通用模块而后须要低级别的组件模块,包含咱们的组件,控制器,服务,指令,过滤器和给可重复使用的功能进行测试。github

回到顶部web

Root module

根模块以一个根组件开始,它定义了整个应用程序的基本元素和路由出口,例如使用ui-router展现ui-view

// app.component.ts
export const AppComponent: angular.IComponentOptions  = {
  template: `
    <header>
        Hello world
    </header>
    <div>
        <div ui-view></div>
    </div>
    <footer>
        Copyright MyApp 2016.
    </footer>
  `
};

随着AppComponent导入和使用.component('app', AppComponent)注册,一个根模块就建立了。进一步导入子模块(组件和公共模块)包括与应用程序相关的全部组件。你可能会注意到在这里也导入了样式,咱们将在本直男的后面章节介绍这个。

// app.ts
import angular from 'angular';
import uiRouter from 'angular-ui-router';
import { AppComponent } from './app.component';
import { ComponentsModule } from './components/components.module';
import { CommonModule } from './common/common.module';
import './app.scss';

const root = angular
  .module('app', [
    ComponentsModule,
    CommonModule,
    uiRouter
  ])
  .component('app', AppComponent)
  .name;

export default root;

回到顶部

Component module

一个组件模块是引用全部可复用组件的容器。在上面咱们看到如何导入Components而且将他们注入到根模块,这里给了咱们一个导入全部应用程序须要的组件的地方。咱们要求这些模块与其余模块都是解耦的,所以能够很容易的移动到其余任何应用程序中。

import angular from 'angular';
import { CalendarModule } from './calendar/calendar.module';
import { EventsModule } from './events/events.module';

export const ComponentsModule = angular
  .module('app.components', [
    CalendarModule,
    EventsModule
  ])
  .name;

回到顶部

Common module

公共模块是引用全部为应用程序提供的特殊组件的容器,咱们不但愿它在其余应用程序中使用。这能够是布局,导航和页脚之类的东西。在上面咱们看到如何导入Common而且将他们注入到根模块,这里是给了咱们一个导入应用程序须要的全部的公共组件的地方。

import angular from 'angular';
import { NavModule } from './nav/nav.module';
import { FooterModule } from './footer/footer.module';

export const CommonModule = angular
  .module('app.common', [
    NavModule,
    FooterModule
  ])
  .name;

回到顶部

Low-level modules

Always remember to add the .name suffix to each export when creating a new module, not when referencing one. You'll noticed routing definitions also exist here, we'll come onto this in later chapters in this guide.

低级别的模块是包含每一个功能块逻辑的独立组件。每一个模块都将定义成一个能够被导入较高级别的单独模块,例如一个组件或者公共模块,以下所示。 。必定要记住每次建立一个新的模块,而非引用的时候,记得给每一个export中添加.name的后缀。你会注意到路由定义也在这里,咱们将在随后的部分讲到它。

import angular from 'angular';
import uiRouter from 'angular-ui-router';
import { CalendarComponent } from './calendar.component';
import './calendar.scss';

export const CalendarModule = angular
  .module('calendar', [
    uiRouter
  ])
  .component('calendar', CalendarComponent)
  .config(($stateProvider: angular.ui.IStateProvider,
            $urlRouterProvider: angular.ui.IUrlRouterProvider) => {
    $stateProvider
      .state('calendar', {
        url: '/calendar',
        component: 'calendar'
      });
    $urlRouterProvider.otherwise('/');
  })
  .name;

回到顶部

File naming conventions

使用小写并保持命名的简洁, 使用组件名称举例, calendar.*.ts*, calendar-grid.*.ts - 将文件类型的名称放到中间。使用 index.ts 做为模块的定义文件,这样就能够经过目录名导入模块了。

index.ts
calendar.component.ts
calendar.service.ts
calendar.directive.ts
calendar.filter.ts
calendar.spec.ts
calendar.html
calendar.scss

回到顶部

Scalable file structure

文件目录结构很是重要,它有利于咱们更好的扩展和预测。下面的例子展现了模块组件的基本架构。

├── app/
│   ├── components/
│   │  ├── calendar/
│   │  │  ├── index.ts
│   │  │  ├── calendar.component.ts
│   │  │  ├── calendar.service.ts
│   │  │  ├── calendar.spec.ts
│   │  │  ├── calendar.html
│   │  │  ├── calendar.scss
│   │  │  └── calendar-grid/
│   │  │     ├── index.ts
│   │  │     ├── calendar-grid.component.ts
│   │  │     ├── calendar-grid.directive.ts
│   │  │     ├── calendar-grid.filter.ts
│   │  │     ├── calendar-grid.spec.ts
│   │  │     ├── calendar-grid.html
│   │  │     └── calendar-grid.scss
│   │  ├── events/
│   │  │  ├── index.ts
│   │  │  ├── events.component.ts
│   │  │  ├── events.directive.ts
│   │  │  ├── events.service.ts
│   │  │  ├── events.spec.ts
│   │  │  ├── events.html
│   │  │  ├── events.scss
│   │  │  └── events-signup/
│   │  │     ├── index.ts
│   │  │     ├── events-signup.controller.ts
│   │  │     ├── events-signup.component.ts
│   │  │     ├── events-signup.service.ts
│   │  │     ├── events-signup.spec.ts
│   │  │     ├── events-signup.html
│   │  │     └── events-signup.scss
│   │  └── components.module.ts
│   ├── common/
│   │  ├── nav/
│   │  │     ├── index.ts
│   │  │     ├── nav.component.ts
│   │  │     ├── nav.service.ts
│   │  │     ├── nav.spec.ts
│   │  │     ├── nav.html
│   │  │     └── nav.scss
│   │  ├── footer/
│   │  │     ├── index.ts
│   │  │     ├── footer.component.ts
│   │  │     ├── footer.service.ts
│   │  │     ├── footer.spec.ts
│   │  │     ├── footer.html
│   │  │     └── footer.scss
│   │  └── index.ts
│   ├── index.ts
│   ├── app.component.ts
│   └── app.scss
└── index.html

顶级目录仅仅包含了index.htmlapp/, app/目录中则包含了咱们要用到的根模块,组件,公共模块,以及低级别的模块。

回到顶部

Components

Component theory

组件实际上就是带有控制器的模板。他们即不是指令,也不该该使用组件代替指令,除非你正在用控制器升级“模板指令”,它是最适合做为组件的。 组件还包含数据事件的输入与输出,生命周期钩子和使用单向数据流以及从父组件上获取数据的事件对象备份。这些都是在AngularJS 1.5及以上推出的新标准。咱们建立的全部模板和控制器均可能是一个组件,它多是是有状态的,无状态或者路由组件。你能够将“组件”看做一段完整的代码,而不只仅是.component()定义的对象。让咱们来探讨一些组件最佳实践和建议,而后你应该能够明白如何经过有状态,无状态和路由组件的概念来组织结构。

回到顶部

Supported properties

下面是一些你可能会使用到的.component()属性 :

Property Support
bindings Yes, 仅仅使用 '@', '<', '&'
controller Yes
controllerAs Yes, 默认是$ctrl
require Yes (新对象语法)
template Yes
templateUrl Yes
transclude Yes

回到顶部

Controllers

控制器应该仅仅与组件一块儿使用,而不该该是任何地方。若是你以为你须要一个控制器,你真正须要的多是一个来管理特定行的无状态组件。

这里有一些使用Class构建控制器的建议:

  • 始终使用constructor来依赖注入
  • 不要直接导出Class,导出它的名字去容许使用$inject注解
  • 若是你须要访问scope中的语法,请使用箭头函数
  • 另外关于箭头函数,let ctrl = this;也是能够接受的,固然这更取决于使用场景
  • 将全部公开的函数直接绑定到Class
  • 适当的使用生命周期钩子,$onInit, $onChanges, $postLink$onDestroy

    • 注意:$onChanges$onInit以前被调用,查看这里的扩展阅读对生命周期有进一步的理解
  • $onInit使用require去引用其余继承的逻辑
  • 不要使用controllerAs语法去覆盖默认的$ctrl别名,固然也不要再其余地方使用controllerAs

回到顶部

One-way dataflow and Events

单向数据流已经在Angular1.5中引入了,而且从新定义了组件之间的通讯。

这里有一些使用单向数据流的建议:

  • 在组件中始终使用单向数据绑定语法<来接收数据
  • 不要在任何地方再使用双向绑定语法'='
  • bindings 的组件应该使用 $onChanges 克隆单向绑定数据而阻止经过引用传递对象,而且更新父级数据
  • 在父级方法中使用 $event 做为一个函数参数(查看有状态组件的例子$ctrl.addTodo($event)
  • 从无状态组件传回一个 $event: {} 对象(查看无状态组件的例子this.onAddTodo

    • Bonus:使用 .value() 包装 EventEmitter 以便迁移到 Anuglar2,避免手动创一个 $event 对象
  • 为何?这方便迁移到Angular2,而且在组件内部保持一致性。而且可让状态可预测。

回到顶部

Stateful components

咱们来定义下什么叫做“有状态组件”:

  • 本质上经过服务于后端API通讯获取状态
  • 不直接改变状态
  • 状态改变的时候渲染子组件
  • 能够做为小的容器组件引用

下面的是一个状态组件案例,它和一个低级别的模块组件共同完成(这只是演示,为了精简省略了一些代码)

/* ----- todo/todo.component.ts ----- */
import { TodoController } from './todo.controller';
import { TodoService } from './todo.service';
import { TodoItem } from '../common/model/todo';

export const TodoComponent: angular.IComponentOptions  = {
  controller: TodoController,
  template: `
    <div class="todo">
      <todo-form
        todo="$ctrl.newTodo"
        on-add-todo="$ctrl.addTodo($event);">
      <todo-list
        todos="$ctrl.todos"></todo-list>
    </div>
  `
};


/* ----- todo/todo.controller.ts ----- */
export class TodoController {
  static $inject: string[] = ['TodoService'];
  todos: TodoItem[];

  constructor(private todoService: TodoService) { }

  $onInit() {
    this.newTodo = new TodoItem('', false);
    this.todos = [];
    this.todoService.getTodos().then(response => this.todos = response);
  }
  addTodo({ todo }) {
    if (!todo) return;
    this.todos.unshift(todo);
    this.newTodo = new TodoItem('', false);
  }
}

/* ----- todo/index.ts ----- */
import angular from 'angular';
import { TodoComponent } from './todo.component';


export const TodoModule = angular
  .module('todo', [])
  .component('todo', TodoComponent)
  .name;

/* ----- todo/todo.service.ts ----- */
export class TodoService {
  static $inject: string[] = ['$http'];

  constructor(private $http: angular.IHttpService) { }

  getTodos() {
    return this.$http.get('/api/todos').then(response => response.data);
  }
}


/* ----- common/model/todo.ts ----- */
export class TodoItem {
    constructor(
        public title: string,
        public completed: boolean) { }
    )
}

这个例子展现了一个有状态的组件,在控制器经过服务获取状态,而后再将它传递给无状态的子组件。注意这里并无在模版中使例如如ng-repeat和其余指令。相反,将数据和函数代理到 <todo-form><todo-list> 这两个无状态的组件。

回到顶部

Stateless components

咱们来定义下什么叫做“无状态组件”:

  • 使用 bindings: {} 定义输入输出
  • 数据经过属性绑定进入组件(输入)
  • 数据经过事件离开组件(输出)
  • 状态改变,按需传回数据(离去点击和提交事件)
  • 不关心数据来自于哪里,它是无状态的
  • 可高频率复用的组件
  • 也被称做哑吧或者展现性组件

下面是一个无状态组件的例子 (咱们使用 <todo-form> 做为例子) , 使用低级别模块定义来完成(仅仅用于演示,省略了部分代码):

/* ----- todo/todo-form/todo-form.component.ts ----- */
import { TodoFormController } from './todo-form.controller';

export const TodoFormComponent: angular.IComponentOptions = {
  bindings: {
    todo: '<',
    onAddTodo: '&'
  },
  controller: TodoFormController,
  template: `
    <form name="todoForm" ng-submit="$ctrl.onSubmit();">
      <input type="text" ng-model="$ctrl.todo.title">
      <button type="submit">Submit</button>
    </form>
  `
};


/* ----- todo/todo-form/todo-form.controller.ts ----- */
import { EventEmitter } from '../common/event-emitter';
import { Event } from '../common/event';

export class TodoFormController {
  static $inject = ['EventEmitter'];

  constructor(private eventEmitter: EventEmitter) {}
  $onChanges(changes) {
    if (changes.todo) {
      this.todo = Object.assign({}, this.todo);
    }
  }
  onSubmit() {
    if (!this.todo.title) return;
    // with EventEmitter wrapper
    this.onAddTodo(
      eventEmitter({
        todo: this.todo
      });
    );
    // without EventEmitter wrapper
    this.onAddTodo(new Event({
        todo: this.todo
      }));
  }
}

/* ----- common/event.ts ----- */
export class Event {
    constructor(public $event: any){ }
}

/* ----- common/event-emitter.ts ----- */
import { Event } from './event';

export function EventEmitter(payload: any): Event {
    return new Event(payload);
}

/* ----- todo/todo-form/index.ts ----- */
import angular from 'angular';
import { EventEmitter } from './common/event-emitter';
import { TodoFormComponent } from './todo-form.component';

export const TodoFormModule = angular
  .module('todo.form', [])
  .component('todoForm', TodoFormComponent)
  .value('EventEmitter', EventEmitter)
  .name;

请注意 <todo-form> 组件不获取状态,它只是简单的接收,它经过控制器的逻辑去改变一个对象,而后经过绑定的属性将改变后的值传回给父组件。 在这个例子中 $onChanges 生命周期钩子克隆了初始的 this.todo 对象并从新赋值,这意味着父组件的数据在咱们提交表单以前不受影响,同时还要新的单向数据绑定语法'<' 。

回到顶部

Routed components

咱们来定义下什么叫做“路由组件”:

  • 它本质上是一个具备路由定义的有状态组件
  • 没有 router.ts 文件
  • 咱们使用路由组件去定义他们本身的路由逻辑
  • 数据经过路由 resolve “输入” 组件(可选,依然能够在控制器中使用服务调用)

在这个例子中,咱们将利用已经存在的 <todo> 组件,咱们使用路由定义和组件上的 bindings 接收数据(这里使用ui-router的秘诀是咱们建立的reslove属性,这个例子中todoData直接映射到了bindings)。咱们把它看做一个路由组件,由于它本质上是一个“视图”:

/* ----- todo/todo.component.ts ----- */
import { TodoController } from './todo.controller';

export const TodoComponent: angular.IComponentOptions = {
  bindings: {
    todoData: '<'
  },
  controller: TodoController,
  template: `
    <div class="todo">
      <todo-form
        todo="$ctrl.newTodo"
        on-add-todo="$ctrl.addTodo($event);">
      <todo-list
        todos="$ctrl.todos"></todo-list>
    </div>
  `
};

/* ----- todo/todo.controller.ts ----- */
import { TodoItem } from '../common/model/todo';

export class TodoController {
  todos: TodoItem[] = [];

  $onInit() {
    this.newTodo = new TodoItem();
  }

  $onChanges(changes) {
    if (changes.todoData) {
      this.todos = Object.assign({}, this.todoData);
    }
  }

  addTodo({ todo }) {
    if (!todo) return;
    this.todos.unshift(todo);
    this.newTodo = new TodoItem();
  }
}


/* ----- common/model/todo.ts ----- */
export class TodoItem {
    constructor(
        public title: string = '',
        public completed: boolean = false) { }
}


/* ----- todo/todo.service.ts ----- */
export class TodoService {
  static $inject: string[] = ['$http'];

  constructor(private $http: angular.IHttpService) { }

  getTodos() {
    return this.$http.get('/api/todos').then(response => response.data);
  }
}


/* ----- todo/index.ts ----- */
import angular from 'angular';
import { TodoComponent } from './todo.component';
import { TodoService } from './todo.service';

export const TodoModule = angular
  .module('todo', [])
  .component('todo', TodoComponent)
  .service('TodoService', TodoService)
  .config(($stateProvider: angular.ui.IStateProvider, $urlRouterProvider: angular.ui.IUrlRouterProvider) => {
    $stateProvider
      .state('todos', {
        url: '/todos',
        component: 'todo',
        resolve: {
          todoData: TodoService => TodoService.getTodos();
        }
      });
    $urlRouterProvider.otherwise('/');
  })
  .name;

回到顶部

Directives

Directive theory

指令给了咱们 templatescope 绑定 ,bindToControllerlink 和许多其余的事情。使用这些咱们应该慎重考虑如今的 .component()。指令不该该再声明模板和控制器了,或者经过绑定接收数据。指令应该仅仅是为了装饰DOM使用。这样,使用 .component() 建立就意味着扩展示有的HTML。简而言之,若是你须要自定义DOM事件/ APIs和逻辑,在组件里使用一个指令将其绑定到模板。若是你须要的足够的数量的 DOM操做,$postLink 生命周期钩子值得考虑,可是这并非迁移全部的的DOM操做,若是能够的话,你可使用指令来处理非Angular的事情。

下面是一些使用指令的建议:

  • 不要使用templates、scope,bindToController 或者 controllers
  • 指令一般使用restrict: 'A'
  • 在须要的地方使用 compilelink
  • 记得在$scope.$on('$destroy', fn);中销毁或者解绑事件处理

回到顶部

Recommended properties

Due to the fact directives support most of what .component() does (template directives were the original component), I'm recommending limiting your directive Object definitions to only these properties, to avoid using directives incorrectly:

因为指令实际上支持了大多数 .component() 的语法 (模板指令就是最原始的组件), 我建议将指令对象定义限制在这些属性上,去避免错误的使用指令:

Property Use it? Why
bindToController No 在组件中使用 bindings
compile Yes 预编译 DOM 操做/事件
controller No 使用一个组件
controllerAs No 使用一个组件
link functions Yes 对于DOM 操做/事件的先后
multiElement Yes 文档
priority Yes 文档
require No 使用一个组件
restrict Yes 使用 'A' 去定义一个组件
scope No 使用一个组件
template No 使用一个组件
templateNamespace Yes (if you must) 文档
templateUrl No 使用一个组件
transclude No 使用一个组件

回到顶部

Constants or Classes

这里有使用 TypeScript 和 directives 实现的几种方式,不论是使用箭头函数仍是更简单的复制,或者使用 TypeScript 的 Class。选择最适合你或者你团队的,Angular2中使用的是Class

下面是使用箭头函数表达式使用常量的例子() => ({}),返回一个对象字面量(注意与使用.directive()的不一样):

/* ----- todo/todo-autofocus.directive.ts ----- */
import angular from 'angular';

export const TodoAutoFocus = ($timeout: angular.ITimeoutService) => (<angular.IDirective> {
  restrict: 'A',
  link($scope, $element, $attrs) {
    $scope.$watch($attrs.todoAutofocus, (newValue, oldValue) => {
      if (!newValue) {
        return;
      }
      $timeout(() => $element[0].focus());
    });
  }
});

TodoAutoFocus.$inject = ['$timeout'];

/* ----- todo/index.ts ----- */
import angular from 'angular';
import { TodoComponent } from './todo.component';
import { TodoAutofocus } from './todo-autofocus.directive';

export const TodoModule = angular
  .module('todo', [])
  .component('todo', TodoComponent)
  .directive('todoAutofocus', TodoAutoFocus)
  .name;

或者使用 TypeScript Class (注意在注册指令的时候手动调用new TodoAutoFocus)去建立一个新对象:

/* ----- todo/todo-autofocus.directive.ts ----- */
import angular from 'angular';

export class TodoAutoFocus implements angular.IDirective {
  static $inject: string[] = ['$timeout'];
  restrict: string;

  constructor(private $timeout: angular.ITimeoutService) {
    this.restrict = 'A';
  }

  link($scope, $element: HTMLElement, $attrs) {
    $scope.$watch($attrs.todoAutofocus, (newValue, oldValue) => {
      if (!newValue) {
        return;
      }

      $timeout(() => $element[0].focus());
    });
  }
}


/* ----- todo/index.ts ----- */
import angular from 'angular';
import { TodoComponent } from './todo.component';
import { TodoAutofocus } from './todo-autofocus.directive';

export const TodoModule = angular
  .module('todo', [])
  .component('todo', TodoComponent)
  .directive('todoAutofocus', ($timeout: angular.ITimeoutService) => new TodoAutoFocus($timeout))
  .name;

回到顶部

Services

Service theory

服务本质上是包含业务逻辑的容器,而咱们的组件不该该直接进行请求。服务包含其它内置或外部服务,例如$http,咱们能够随时随地的在应用程序注入到组件控制器。咱们在开发服务有两种方式,使用.service() 或者 .factory()。使用TypeScript Class,咱们应该只使用.service(),经过$inject完成依赖注入。

回到顶部

Classes for Service

下面是使用 TypeScript Class 实现<todo> 应用的一个例子:

/* ----- todo/todo.service.ts ----- */
export class TodoService {
  static $inject: string[] = ['$http'];

  constructor(private $http: angular.IHttpService) { }
  getTodos() {
    return this.$http.get('/api/todos').then(response => response.data);
  }
}


/* ----- todo/index.ts ----- */
import angular from 'angular';
import { TodoComponent } from './todo.component';
import { TodoService } from './todo.service';

export const todo = angular
  .module('todo', [])
  .component('todo', TodoComponent)
  .service('TodoService', TodoService)
  .name;

回到顶部

Styles

利用Webpack 咱们如今能够在 *.module.js 中的 .scss文件上使用import 语句,让 Webpack 知道在咱们的样式中包含这样的文件。 这样作可使咱们的组件在功能和样式上保持分离,它还与Angular2中使用样式的方式更加贴近。这样作不会让样式像Angular2同样隔离在某个组件上,样式还能够普遍应用到咱们的应用程序上,可是它更加易于管理,而且使得咱们的应用结构更加易于推理。

If you have some variables or globally used styles like form input elements then these files should still be placed into the root scss folder. e.g. scss/_forms.scss. These global styles can the be @imported into your root module (app.module.js) stylesheet like you would normally do.

若是你有一些变量或者全局使用的样式,像表单的input元素,那么这些文件应该放在根scss文件夹。例如scss/_forms.scss。这些全局的样式能够像一般意义被@imported到根模块(app.module.ts)。

回到顶部

TypeScript and Tooling

TypeScript
  • 使用Babel 编译 TypeScript 代码和其余 polyfills
  • 考虑使用 TypeScript让你的代码迁移到Angular2
Tooling
  • 若是你想支持组件路由,使用ui-routerlatest alpha(查看Readme)

    • 不然你将会被 template: '<component>' 和 没有 bindings 困住
  • 考虑使用Webpack来编译你的 TypeScript 代码
  • 使用ngAnnotate 来自动注解 $inject 属性
  • 如何使用ngAnnotate with TypeScript

回到顶部

State management

考虑在Angular1.5中使用Redux用于数据管理。

回到顶部

Resources

回到顶部

Documentation

For anything else, including API reference, check the Angular documentation.

Contributing

Open an issue first to discuss potential changes/additions. Please don't open issues for questions.

License

(The MIT License)

Copyright (c) 2016 Todd Motto

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

本文github仓库 准备持续翻译一些文章,方便的话给个star!谢谢~
相关文章
相关标签/搜索