Repository: hstarorg/HstarDoc
Branch: master
Commit: 0e54b7123b0e
Files: 168
Total size: 618.9 KB
Directory structure:
gitextract_j4qofkey/
├── .Net Platform/
│ ├── 01_Dotnet Core尝鲜.md
│ ├── 02_Dotnet Core V2.md
│ ├── C#中处理耗时任务的几种方式.md
│ ├── [20140913]可替代反射的几种方式.md
│ ├── 实战Asp.Net Core:DI生命周期.md
│ ├── 实战Asp.Net Core:中间件.md
│ ├── 实战Asp.Net Core:构建一个Core Lib.md
│ └── 实战Asp.Net Core:部署应用.md
├── .gitignore
├── AngularJS相关/
│ ├── Angular1.x升级指南.md
│ ├── AngularJS官方FAQ.md
│ ├── AngularJS教程:1W字综合指南.md
│ ├── AngularJS:Looking under the hood.md
│ ├── Angular从0到1:function(上).md
│ ├── Angular从0到1:function(下).md
│ ├── Angular再回首(1)-Component组件.md
│ ├── Angular再回首(2)-那些容易忽略的Component细节.md
│ ├── Angular再回首(3)-我们来实现一个组件.md
│ ├── Angular开发Tips.md
│ ├── Angular:指令、Controller数据共享.md
│ ├── [20140917]Angular:如何编写一个指令.md
│ ├── 用AngularJS开发Web应用程序.md
│ └── 详解angular之$q.md
├── Angular系列/
│ ├── 01_Angular2初体验.md
│ ├── 02_Angular2组件生命周期.md
│ ├── 03_Angular2的那些Decorator.md
│ ├── 04_Angular2指令简析.md
│ ├── 05_Angular2组件简析.md
│ ├── 06_Angular2管道(Pipe)简析.md
│ ├── 07_Angular2使用路由.md
│ ├── 08_Angular2动态加载组件.md
│ ├── 09_Angular2使用ui-router-ng2.md
│ ├── Angular2知识点.xmind
│ ├── Angular2踩坑大全.md
│ ├── 利用Angular实现多团队模块化SPA开发框架.md
│ └── 跟我学Angular2(1-初体验).md
├── CSS3学习之路/
│ ├── CSS3入门之文本与字体.md
│ ├── CSS3入门之转换.md
│ └── CSS3入门之边框与背景.md
├── Canvas学习札记/
│ └── 01_初识Canvas,绘制简单图形.md
├── ES6入门/
│ ├── ES6入门系列一(基础).md
│ ├── ES6入门系列三(特性总览下).md
│ ├── ES6入门系列二(特性总览上).md
│ └── ES6入门系列四(测试题分析).md
├── GoLang学习笔记/
│ └── 01_开始GO.md
├── JS札记/
│ ├── ES6 Class如何管理私有数据.md
│ ├── JS实现继承的几种方式.md
│ ├── JavaScript之毒瘤.md
│ ├── JavaScript之糟粕.md
│ ├── JavaScript的深拷贝的实现.md
│ ├── [20141121]JavaScript之Array常用功能汇总.md
│ ├── 那些不常见的JavaScript题目(上).md
│ └── 那些不常见的JavaScript题目(下).md
├── LICENSE
├── MongoDB入门基础/
│ ├── 01_记一次MongoDB裸奔.md
│ └── 02_Mongo权限探索.md
├── Other/
│ ├── Go Go.md
│ ├── NPM使用详解(上).md
│ ├── NPM使用详解(下).md
│ ├── Thrift简单实践.md
│ ├── TypeScript札记:初体验.md
│ ├── Windows下把Nginx,PM2包装为服务.md
│ ├── 开发者讨厌你API的十个原因.md
│ ├── 浅析12306前端优化点.md
│ └── 程序集强签名.md
├── PHP学习之路/
│ ├── 01_PHP简易安装环境.md
│ └── 02-PHP基础语法(上).md
├── README.md
├── React Native Cookbook/
│ ├── 章节一-new.md
│ └── 章节一.md
├── React Native 开发笔记/
│ ├── RN Aspect-01-环境准备.md
│ ├── RN Aspect-02-Hello React Native.md
│ ├── RN Aspect-03-修改名称与icon.md
│ ├── RN Aspect-04-打包App.md
│ └── React Native开发之多屏适配.md
├── React面面观/
│ ├── JSX中的那些小细节.md
│ ├── sources/
│ │ └── Redux交互流程.vsdx
│ ├── 【译】参考手册-React组件.md
│ ├── 【译】快速起步-JSX简介.md
│ ├── 【译】快速起步-事件处理.md
│ ├── 【译】快速起步-列表与KEY.md
│ ├── 【译】快速起步-条件渲染.md
│ ├── 【译】快速起步-渲染元素.md
│ ├── 【译】快速起步-状态和生命周期.md
│ ├── 【译】快速起步-状态提升.md
│ ├── 【译】快速起步-组件与属性.md
│ ├── 【译】快速起步-组合与继承.md
│ ├── 【译】快速起步-表单.md
│ ├── 【译】高级指南-不受控组件.md
│ ├── 【译】高级指南-深入JSX.md
│ └── 【译】高级指南-高阶组件.md
├── RxJS小记/
│ └── 02_RxJS之Observable.md
├── Sass学习之路/
│ ├── 01_Sass学习之路:Sass、Compass安装与命令行.md
│ └── 02_Sass学习之路:注释、变量以及导入.md
├── Vue实践之路/
│ ├── 01_认识Vue.md
│ └── 02_Vue组件(上).md
├── catalog_builder.js
├── jQuery拆解/
│ ├── 01-目录篇.md
│ ├── 02-模块化加载&防冲突处理.md
│ ├── 03-基础结构.md
│ └── jQuery中那些有趣的代码.md
├── 从0开始Stylus/
│ └── 01_Stylus简介&基本使用.md
├── 从零开始H5/
│ ├── HTML5探索一(那些新增的标签和属性).md
│ ├── 从零开始H5(一):升级你的HTML到HTML5.md
│ └── 从零开始H5(二):HTML5新技术点.md
├── 前端相关/
│ ├── CORS详解.md
│ ├── CSS布局(上).md
│ ├── CSS布局(下).md
│ ├── Google JavaScript Style Guide(上).md
│ ├── Iframe跨域通信的几种方式.md
│ ├── JSONP详解.md
│ ├── JWT详解.md
│ ├── Nginx常规用法解析.md
│ ├── TypeScript配置文件tsconfig简析.md
│ ├── VsCode简易配置手册.md
│ ├── Web API接口之FileReader.md
│ ├── Web API接口之Geolocation.md
│ ├── Webpack In Angular2.md
│ ├── Webpack初体验.md
│ ├── Webpack小抄.md
│ ├── Web前端基础测试题.md
│ ├── Yarn vs. Npm.md
│ ├── [20140311]前端构建之gulp与常用插件.md
│ ├── [20141025]从0开始Grunt.md
│ ├── [20150107]Web离线存储的几种方式.md
│ ├── 一个元素实现3个回图形.md
│ ├── 再说Promise.md
│ ├── 前端模块化:RequireJS.md
│ ├── 如何用Node编写命令行工具.md
│ ├── 探索Decorator.md
│ ├── 浏览器 Pointer Events.md
│ ├── 浏览器关闭事件分析.md
│ ├── 浏览器内容安全策略解析.md
│ ├── 浏览器历史history对象.md
│ ├── 简单学ES6.md
│ ├── 认识AMD、CMD、UMD、CommonJS.md
│ ├── 记一次Bug排查(Spider).md
│ ├── 说说如何部署node程序.md
│ ├── 这些年我们处理过的跨域.md
│ ├── 那些容易出错的Dom操作.md
│ └── 那些年我们认识的iframe.md
├── 微信小程序/
│ └── 微信小程序.xmind
├── 数据库之路/
│ ├── [20141114]这些年你需要注意的SQL.md
│ └── 说说你所熟知的MSSQL中的substring函数.md
├── 最佳实践系列/
│ └── Express异步进化史.md
├── 正则表达式/
│ └── 你真的理解正则修饰符吗.md
├── 测试相关/
│ ├── MOCHA测试代码汇总.md
│ ├── 使用chai-http实现API测试.md
│ ├── 利用Karma、Mocha搭建测试环境.md
│ └── 利用Nightwatch.js实现e2e测试.md
├── 编写高质量JS代码的68个有效方法-读书笔记/
│ ├── [20140926]编写高质量JS代码的68个有效方法(一).md
│ ├── [20141011]编写高质量JS代码的68个有效方法(二).md
│ ├── [20141030]编写高质量JS代码的68个有效方法(三).md
│ ├── [20141129]编写高质量JS代码的68个有效方法(四).md
│ ├── [20141205]编写高质量JS代码的68个有效方法(五).md
│ ├── [20141213]编写高质量JS代码的68个有效方法(六).md
│ ├── [20141220]编写高质量JS代码的68个有效方法(七).md
│ ├── [20141227]编写高质量JS代码的68个有效方法(八).md
│ ├── [20150110]编写高质量JS代码的68个有效方法(九).md
│ ├── [20150123]编写高质量JS代码的68个有效方法(十).md
│ ├── [20150214]编写高质量JS代码的68个有效方法(十一).md
│ ├── [20150304]编写高质量JS代码的68个有效方法(十二).md
│ └── [20150312]编写高质量JS代码的68个有效方法(十三).md
└── 运维&部署/
├── Docker容器管理平台Humpback进阶-私有仓库.md
├── Docker部署:Mysql Master-Slave.md
├── 一个简单易用的容器管理平台-Humpback.md
├── 前端监控系统实现.md
└── 记一次Zookeeper数据找回.md
================================================
FILE CONTENTS
================================================
================================================
FILE: .Net Platform/01_Dotnet Core尝鲜.md
================================================
---
title: 01_Dotnet Core尝鲜
date: 2017/02/21 14:47:10
---
# 0、About Dotnet Core
`Dotnet Core` 是新一代的 `.Net Framework`,是一个具有跨平台能力的应用程序开发框架。它本身是由多个子项目组成的。包括 ``Core Fx``、``Core CLR``、``.Net Compiler Platform`` 等等。
`Dotnet Core` 具有高效的开发效率,高性能和跨平台能力,是 ``.Net平台`` 的一次大跃进。
# 1、尝试 Dotnet Core
### 1.1、Install
``Dotnet Core`` 从发布至今,已经有很长一段时间了。期间也发布了beta,rc等版本。就在前不久,正式版也已经发布了,经过了之前大量的api变化,现在core已经非常稳定了。这个阶段,已经值得我们去尝试、去使用它了。
要尝试 ``Dotnet core``, 我们先进入它的网站[https://dotnet.github.io/](https://dotnet.github.io/),[https://www.microsoft.com/net/core#windows](https://www.microsoft.com/net/core#windows) 。
根据我们的操作系统版本,选择合适的开发包。我这里是Windows下开发,理所当然的下载 [the .NET Core SDK for Windows](https://go.microsoft.com/fwlink/?LinkID=809122) 。
安装好之后,在命令行输入 ``dotnet --version`` ,如果输出了版本信息,那就表示安装成功了。
**注意:如果之前尝试过Dotnet Core,请保证在安装最新版本的SDK之前,先卸载干净。**
### 1.2、Console App
在安装好SDK之后,我们就可以开始创建项目了。新建一个文件夹,进入控制台,执行 ``dotnet new``,即可看到在目录下生成了如下文件结构:
```
Program.cs
project.json
```
内容如下:
```csharp
//Program.cs
using System;
namespace ConsoleApplication
{
public class Program
{
public static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}
```
```json
//project.json
{
"version": "1.0.0-*",
"buildOptions": {
"debugType": "portable",
"emitEntryPoint": true
},
"dependencies": {},
"frameworks": {
"netcoreapp1.0": {
"dependencies": {
"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.0.0"
}
},
"imports": "dnxcore50"
}
}
}
```
此时我们可以通过 ``dotnet restore`` 来安装依赖。
**注意:就算project.json中的dependencies属性为空对象,我们也要执行 ``dotnet restore``,该命令会生成project.lock.json文件。**
**注意2:如果我们的网络环境的走的代理,那么可能会在安装依赖这个步骤遇到407错误,此时我们需要配置Nuget的代理设置,找到 ``%AppData%/NuGet/NuGet.Config`` 文件,然后添加如下配置项:**
```xml
```
**通过这样的设置,就可以使用代理来安装依赖了。**
当我们安装好依赖之后,通过执行 ``dotnet run`` 来编译和运行我们的程序,此时可以在控制台看到输出: ``Hello World!``
以上,就是我们使用 ``Dotnet Core`` 的一般步骤了。
### 1.3、Web App
在尝试了Console App之后,我们也来试试Web App 在 ``Dotnet Core`` 下是如何运行的。
首先,基本步骤如上,先创建一个项目模板。
接着,首先要使用Web功能,我们需要指定依赖,在project.json中的dependencies属性中增加依赖:
```
"dependencies": {
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0"
}
```
**注意:在设定依赖的时候,一定要注意版本号,如果依赖库的版本号不兼容Core的版本,那么很可能会出现一些莫名其妙的错误,而找不到原因。**
增加依赖之后,我们再次通过 ``dotnet restore`` 来安装依赖。
这个时候,我们来编写Web宿主程序,内容如下:
```csharp
//Program.cs
using System;
using Microsoft.AspNetCore.Hosting;
namespace WebApplication
{
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseStartup() //此处使用了类型Startup,来自于Startup.cs
.UseUrls("http://localhost: 10000")
.Build();
host.Run();
}
}
}
```
```csharp
//Startup.cs
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
namespace WebApplication
{
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.Run((context) =>
{
return context.Response.WriteAsync("Hello, From Core.");
});
}
}
}
```
编写好如上代码之后,我们在执行 ``dotnet run``,访问 ``http://localhost:10000`` 就可以看到我们的Web程序已经成功运行起来了。
以上则是一个简单的Web程序所需要的代码。
# 2、Other
### 2.1、``Dotnet Core`` 与 ``Node.js`` 的性能测试。
在 ``Dotnet Core`` 的官方宣传中,号称比 ``Node.js`` 快8倍。实际上,通过计算从0到100000000的累加来看,两者的差距并不大(几十ms的差别,Node稍弱)。
由于时间环境关系,没有进行更复杂测试,但我想在高并发下,Node的机制可能会有更高的性能。在大量IO操作处理中,``Dotnet Core``会有绝对的优势【后续可验证】。
### 2.2、如何直接运行Dotnet Core程序?
在开发模式下,我们通过 ``dotnet run`` 来运行程序,那我们如何来运行发布好的程序呢?
首先,我们可以通过 ``dotnet publish`` 来生成好我们的应用程序(在Windows下生成的是dll,其他平台未测试)。
在发布模式,我们的程序所依赖的包,也会被一同发布到目录下,我们可以在 ``/appRoot/bin/Debug/netcoreapp1.0/publish`` 中找到我们发布好的文件。
此时,我们就可以将publish目录拷贝到其他电脑运行了。
由于发布好的文件入口点是 ``.dll`` 文件,我们要运行它的话,需要通过 ``dotnet xxx.dll`` 来进行启动。
**注意:在publish的时候,我们可以使用参数``dotnet publish -c Release`` 生成Release版本的发布包,目录对应变更为 ``bin/Release/``**
**注意2:请不要删除publish目录下的文件,否则可能导致无法运行。经测试,.deps.json 和 .pdb可以删除,但依赖和 .runtimeconfig.json是绝对不能删除的。**
### 2.3 后续
此文为 ``Dotnet Core`` 系列第一篇,后续计划将Web开发所需要用到的一些基本知识点,库等均在 ``Dotnet Core`` 调试通,且成文。
**加油,``Dotnet!``**
**【6.30号更新】**
### 2.4、如何创建web项目
在 1.3 中,我们知道如何把一个Console App 改造为一个Web项目,但这对应开发一个Web应用来说还不够。
其实,``dotnet new`` 可以默认创建 Web 项目开发模板。
通过 ``dotnet new -t --help`` 我们可以看到 ``dotnet new`` 能帮我们创建的项目类型有如下四种:
1. Console
2. Web
3. Lib
4. xunittest
我们可以直接通过 ``dotnet new -t Web`` 来创建一个 Web 项目模板,简单快捷。
### 2.5、如果在Linux下发布(CentOS7)
``Dotnet Core`` 开发的程序,具有跨平台能力,那如何在非Windows上发布呢?各大操作系统方式并不同。
在CentOS7(仅支持7+)上发布非常简单。
首先是在CentOS7上安装 ``Dotnet Core``,不知道如何安装?请查阅 [https://www.microsoft.com/net/core#centos](https://www.microsoft.com/net/core#centos) 。
安装好之后,只需要在Windows把开发好的程序,通过 ``dotnet publish`` 生成发布目录,然后将该目录拷贝到CentOS上即可。
最后,在CentOS上执行 ``dotnet xxx.dll`` 即可运行项目了。
**注意: xxx.dll是你开发的项目的主程序**
================================================
FILE: .Net Platform/02_Dotnet Core V2.md
================================================
---
title: 02_Dotnet Core V2
date: 2017-05-20 09:14:21
---
# 前言
时隔三个月,再来看 `.Net Core`,已经到 `2.x preview` 了,虽然这个版本有赶工的嫌疑,但并不妨碍它成为当前的主力版本(可用API翻倍,API相对更稳定)。
在 `.Net Core` 的开发中,可以采用 `VSC` 或者是 `VS`,在我个人体验来看,如果要开发稍大的(一个解决方案,多个子项目)项目,还是选择VS吧。对于`.Net Core` ,一个简单的编辑器,体验还是太弱了。
**注意:要在VS中使用Core2.x Preview,请安装 [VS 15.3 Preview](https://www.visualstudio.com/vs/preview/) 版本。**
# V2命令行
在安装好 [.Net Core 2.0 Preview 1](https://www.microsoft.com/net/core/preview#windowscmd) 之后,通过 `dotnet new -h` 可以发现可以创建更多的模板了,其中一部分是项目模板:
1. Console Application (控制台应用程序)
2. Class Library (类库)
3. Unit Test Project (使用微软测试库的单元测试)
4. xUnit Test Project (使用xUnit作为测试库的单元测试)
5. ASP.NET Core Empty (Core的空项目)
6. ASP.NET Core Web App(MVC) (网站项目-Application)
7. ASP.NET Core Web App(Razor Pages) (网站项目 - WebPage,和Application不一样的是一个cshtml对应一个.cs,两者之前的区别类似.Net的WebApplication和WebPage)
8. ASP.NET Core Web API (Web API项目)
还有另外一部分是一些其他的单个文件,如:
1. Nuget Config
2. Web Config
3. Solution Config
4. Razor Page
5. MVC ViewImports
6. MVC ViewStart
我们最常用的还是上面的一些项目模板(如果用VS,这都不是事~)
除了这些,还提供了不少的命令行,不用去死记硬背,用的时候通过帮助命令查看使用说明即可,如 `dotnet -h` 列出所有的命令, `dotnet build -h` 列出命令的具体用法。
# Console App
在升级到2.0之后,曾今的 `project.json` 就不再使用了,回归到了 `.csproj` 这样的项目文件,但这个和 `.Net` 中的 `.csproj` 并不一样,最大的一个区别是,`.Net Core` 中,并不会将文件路径映射到 `.csproj` 中。这对于开发Web非常有好处(现代Web有构建等一大套工具链)。
通过 `dotnet new console -o ConsoleDemo` (创建一个控制台项目,并输出到ConsoleDemo目录中)创建一个项目,我们可以看到一个基本的 `ConsoleDemo.csproj` 内容如下:
```
Exe
netcoreapp2.0
```
仅仅是标记一下我们的Framework版本。
**Console不做过多尝试,相信和.Net差不多。更多的,我们还是使用 `.Net Core` 来做Web应用。``
# Web Application
我们还是先使用常用的MVC方式创建一个WebApplication,`dotnet new mvc -o MvcDemo`,创建好之后,可以看到如下结构:
```
Controllers/ --标准MVC三大目录
Models/ --标准MVC三大目录
Views/ --标准MVC三大目录
wwwroot/ --此处用来放置静态资源
.bowerrc --Bower配置文件
appsettings.Development.json 特定环境配置文件(Development环境)
appsettings.json 标准配置文件
bower.json -- Bower项目文件
bundleconfig.json -- 资源打包配置
MvcDemo.csproj -- 项目文件
Program.cs -- 自托管入口文件
Startup.cs -- 启动配置
```
其中,`bower` 相关和 `bundleconfig`,并没有什么大用,直接干掉。至于 `bower` 是什么,查看这里:[Bower官网](https://bower.io/)。
抛开MVC三大目录不说,我是建议创建一个src目录,用于放置相关的前端资源文件(JS,CSS,LESS,TS等),然后通过构建打包工具(gulp,webpack这类),将代码生成到 `wwwroot` 中。
另,需要注意项目配置文件 `appsettings.json` 是采用的类似继承的方式读取值,也就是说:最终的配置 = (appSettings.json).extend(appsettings..json)。如果将环境设置为 `Development`,那么会根据节点,先从 `appsettings.Development.json` 中找,找不到则从 `appsettings.json` 中找。
注意看 `Program.cs` 和 `Startup.cs` 的内容。其中 `Program.cs` 是非常独立的一个文件,仅依赖 `Startup.cs`,而 `Startup.cs` 则全是Web相关的配置文件。
看一下 `Program.cs` 的关键代码:
```csharp
namespace MvcDemo
{
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup()
.Build();
}
}
```
为什么是这样的结构呢?`.Net Core` 支持IIS托管和自托管。在 `.Net` 中,我们知道Web程序是不会有 `Program.cs`,也就没有 `Main` 方法的。
实际上,这个文件在 `.Net Core` 中的作用就是自托管Web程序,从它的内容中,可以理解到自托管就是利用控制台程序创建了一个WebHost,然后将我们Web程序的真正入口文件 `Startup` 传递到Web容器中运行。
在IIS中托管程序的话,这个 `Program.cs` 通过调试,发现也会执行,但其中的一些配置则不会生效,如:`.UseUrls("http://*:5000")`。
**注意:具体在IIS中是如何执行的,还有待分析。**
## Web Application QA
1、如何设置环境?
在上面的内容中,有提到实际配置和环境是有关系的,那我们如何设定环境呢?最简单的方式,就是在 `Program.cs` 中,通过如下方式设置:
```csharp
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseEnvironment("Development") // 设置环境
.UseStartup()
.Build();
```
2、如果在自托管的时候设置多个IP/端口绑定?
同上,也是在 `Program.cs` 中进行配置:
```csharp
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseUrls("http://*:5000;http://*:6000") // 多个用分号隔开
.UseStartup()
.Build();
```
3、如何在视图中根据不同的环境加载不同的内容?
MVC为我们提供了相关的Helper,所以我们可以在View(一般是Layout视图)中,通过 `` 来实现不同的内容加载,如下:
```html
```
4、既然有配置文件,那我们就需要读取配置,应该如何读取呢?
在 `.Net Core` 中,配置是以JSON字符串的方式存在的,那么我们怎么来读取呢?
在 `Startup.cs` 中,构造函数中,注入了一个 `IConfiguration` 对象,并赋值到了这个类的 `Configuration` 属性上,这个属性就类似于 `.Net` 下的 `ConfigurationManager`。所以我们可以用它来进行读取,
假设有如下配置文件:
```json
{
...忽略默认配置,
"v1": "xxx",
"s2": {
"v2": 1
},
"s3": {
"Key1": "key1",
"Key2": "key2"
}
}
```
那么读取方式如下:
```csharp
Configuration.GetValue('v1'); // 读取指定的配置项,返回 "xxx"
Configuration.GetSection('s2').GetValue('v2'); // 从嵌套对象中读取配置项, 返回 1
```
当配置项过多的时候,这种方式可以比较麻烦,这个时候,我们可以创建一个配置类来映射,如下:
```csharp
public class S3
{
public string Key1{ get; set; }
public string Key2{ get; set; }
}
// 然后我们可以获取到s3这个节点,然后绑定到S3上。
var s3 = new S3();
Configuration .GetSection("s3").Bind(s3);
Console.WriteLine(s3.Key1); // 可以打印出 "key1" 了。
```
# 总结
`.Net Core` 正在变得越来越好,赶紧跟上吧~
================================================
FILE: .Net Platform/C#中处理耗时任务的几种方式.md
================================================
---
title: C#中处理耗时任务的几种方式
date: 2017/02/21 14:47:10
---
## 0、准备
首先,我们先创建几个耗时任务:
public class TestTasks
{
//无参、无返回值任务
public void Task1()
{
Console.WriteLine("task1.");
Thread.Sleep(5000);
Console.WriteLine("task1 completed.");
}
//有参数、无返回值任务
public void Task2(int x)
{
if (x < 2000)
{
x += 2000;
}
Console.WriteLine("task2.");
Thread.Sleep(x);
Console.WriteLine("task2 completed.");
}
//有参数,有返回值任务
public int Task3(int x)
{
if (x < 2000)
{
x += 2000;
}
Console.WriteLine("task3.");
Thread.Sleep(x);
Console.WriteLine("task3 completed.");
return x;
}
}
## 1、创建新线程执行方法
var tt = new TestTasks();
new Thread(tt.Task1).Start();
//针对有参数的任务,需要用Lambda进行包装或者使用ParameterizedThreadStart对象
new Thread(x=>tt.Task2((int)x)).Start((object)1000);
//使用ParameterizedThreadStart,要求要执行的方法参数必须为object,同时无返回值。
//new Thread(new ParameterizedThreadStart(tt.Task2)).Start((object)1000);
**注意:使用该方式无法执行带返回值的方法。**
**推荐指数:★★**
## 2、使用异步调用方式执行方法
var tt = new TestTasks();
Action ac = tt.Task1;
Action ac2 = tt.Task2;
ac.BeginInvoke(null, null);
ac2.BeginInvoke(1000, null, null);
//以下是调用有参数,有返回值的方法
//代码一
private delegate int AsyncCaller(int x); //该代码放在方法体外部
AsyncCaller ac = new AsyncCaller(tt.Task3);
var asyncResult = ac.BeginInvoke(1000,null,null);
int result = ac.EndInvoke(asyncResult); //接收返回值
//代码二,使用Func简化代码
Func ac = tt.Task3;
var asyncResult = ac.BeginInvoke(1000,null,null);
int result = ac.EndInvoke(asyncResult);
**注意:通过这种方式生成新线程是运行在后台的(background),优先级为normal**
**推荐指数:★★**
## 3、通过ThreadPool(线程池)执行方法
var tt = new TestTasks();
ThreadPool.QueueUserWorkItem(o => tt.Task1());
ThreadPool.QueueUserWorkItem(o => tt.Task2(1000));
**注意:该方式不支持返回值,可以将返回值保存在引入类型的参数上,然后进行迂回实现**
**推荐指数:★★★**
## 4、通过BackgroundWorker(后台Worker)执行方法
var tt = new TestTasks();
var bw = new BackgroundWorker();
bw.DoWork += (sender, e) => tt.Task1();
bw.DoWork += (sender, e) => tt.Task2(1000);
//要接收返回值,必须将返回值赋值给Result。
bw.DoWork += (sender, e) => e.Result = tt.Task3(1000);
bw.RunWorkerAsync();
//注册事件使用返回值
bw.RunWorkerCompleted += (sender, e) => Console.WriteLine(e.Result);
**注意:使用BackgroundWorker注册DoWork事件的任务只能挨个执行,如果要同时执行多个任务,需要多个BackgroundWorker。要使用返回值,一定要记得赋值给Result。**
**推荐指数:★★**
## 5、同时Task执行方法
var tt = new TestTasks();
var t1 = Task.Factory.StartNew(tt.Task1);
var t2 = Task.Factory.StartNew(() => tt.Task2(1000));
var t3 =Task.Factory.StartNew(() => tt.Task3(1000));
//等待t1,t2,t3执行完成
Task.WaitAll(t1,t2,t3);
Console.WriteLine(t3.Result);
**注意:Task具有灵活的控制能力,同时可以单个等待,多个等待。**
**推荐指数:★★★★★**
## 6、使用async/await执行方法
private async void AsyncRunTask()
{
var tt = new TestTasks();
await Task.Factory.StartNew(tt.Task1);
await Task.Factory.StartNew(() => tt.Task2(1000));
var result = await Task.Factory.StartNew(() => tt.Task3(1000));
Console.WriteLine(result);
}
AsyncRunTask();
Console.WriteLine("不用等待,我先执行了。");
**注意:需要Framework4.5的支持**
**推荐指数:★★★★**
## The End
**没有原理,没有言语,相信以大家聪明的大脑,已经学会如何在C#中执行耗时任务和使用多线程了。**
================================================
FILE: .Net Platform/[20140913]可替代反射的几种方式.md
================================================
---
title: C#可替代反射的几种方式
date: 2014/09/13
---
##标准的反射代码##
var type = obj.GetType();
var fieldInfo = type.GetField("age", BindingFlags.Instance | BindingFlags.NonPublic);
fieldInfo.SetValue(obj, 20);
// Console.WriteLine("设置年龄成功:{0}", (obj as ModelTest).Age);
var s1 = type.InvokeMember("TestMethod1", BindingFlags.InvokeMethod, null, obj, null);
var s2 = type.InvokeMember("TestMethod2", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, obj, null);
// Console.WriteLine(s1);
// Console.WriteLine(s2);
###说明###
1. 能动态获取对象属性、方法、字段等信息
2. 能访问对象的私有字段,属性,方法等成员
3. 能动态修改对象属性
4. **注:**访问public方法使用BindingFlags.InvokeMethod,访问私有方法时,必须加上BindingFlags.NonPublic|BindingFlags.Instance,否则会出现找不到方法的异常
##采用dynamic对象达到发射的效果##
dynamic d = obj;
var s1 = d.TestMethod1();
Console.WriteLine(s1);
###说明###
1. 可使用公开的属性,字段,方法等成员
2. 代码足够简洁
3. **注:**不能访问非公开的成员
##依赖Microsoft的测试组件Microsoft.VisualStudio.TestTools.UnitTesting来达到反射的效果##
var privateObj = new PrivateObject(obj);
privateObj.SetField("age", 20);
var age = privateObj.GetProperty("Age");
Console.WriteLine(age);
privateObj.Invoke("TestMethod1");
privateObj.Invoke("TestMethod2");
###说明###
1. 采用第三方组件实现
##性能说明##
测试代码如下:
private static void Main(string[] args)
{
RunSpecialTest(new SimpleReflection(), 1000);
RunSpecialTest(new DynamicReflection(), 1000);
RunSpecialTest(new PrivateObjectReflection(), 1000);
Console.ReadKey();
}
private static void RunSpecialTest(ITest test, int runCount)
{
var modelTest = new ModelTest();
var stopwatch = new Stopwatch();
stopwatch.Start();
for (int i = 0; i < runCount; i++)
{
test.TestRun(modelTest);
}
stopwatch.Stop();
Console.WriteLine("运行{0} {1}次,共耗时:{2}ms", test.Name, runCount, stopwatch.ElapsedMilliseconds);
}
结果:
运行SimpleReflection 1000次,共耗时:2ms
运行DynamicReflection 1000次,共耗时:717ms
运行PrivateObjectReflection 1000次,共耗时:14ms
##疑问##
1. 采用标准的反射用法,除了第一次耗时较慢意外,后面耗时都很短,可以说是最快的方式,猜测是缓存,具体未知。
2. PrivateObjectReflection这个和发射有类似的情况,初次慢,后面快。何解?
================================================
FILE: .Net Platform/实战Asp.Net Core:DI生命周期.md
================================================
---
title: 实战Asp.Net Core:DI生命周期
date: 2018-11-30 21:54:52
---
# 1、前言
`Asp.Net Core` 默认支持 `DI(依赖注入)` 软件设计模式,那使用 `DI` 的过程中,我们势必会接触到对象的生命周期,那么几种不同的对象生命周期到底是怎么样的呢?我们拿代码说话。
**关于 DI 与 IOC:**
个人理解:`IOC(控制反转)` 是目的(降低代码、服务间的耦合),而 `DI` 是达到该目的的一种手段(具体办法)。
# 2、DI生命周期
DI的生命周期,根据框架、库的不同,会略有差异。此处,我们就以微软的DI扩展为例,来说下DI中常用的几种生命周期。
首先,我们想象一个这样一个场景。假设我们有寄快递的需求,那么我们会致电快递公司:“我们要寄快递,派一个快递员过来收货”。接着,快递公司会如何做呢?
1. 一直派遣同一个快递员来收货。
2. 第一周派遣快递员A、第二周派遣快递员B收货。
3. 每次都派遣一个新的快递员收货。
这对应到生命周期就是:
1. 单例(Singleton),单一实例,每次使用都是该实例。
2. 作用域实例(Scoped),在一个作用域(比如单次请求)内是同一个实例,不同的作用域实例不同。
3. 瞬时实例(Transient),每次使用都创建新的实例。
快递公司也就是我们在DI中常说的容器(Container)了。
## 2.1、验证准备
首先,我们需要三个Services(Service1\Service2\Service3)内容一致,如下:
```c#
// Service1.cs,Service2、Service3除类名以外,内容一致
public class Service1
{
private int value = 0;
public int GetValue()
{
this.value++;
return this.value;
}
}
```
然后,我们需要一个业务类,再一次注入这三个Service,内容如下:
```c#
// DefaultBusiness.cs
public class DefaultBusiness
{
private readonly Service1 s1;
private readonly Service2 s2;
private readonly Service3 s3;
public DefaultBusiness(Service1 s1, Service2 s2, Service3 s3)
{
this.s1 = s1;
this.s2 = s2;
this.s3 = s3;
}
public int GetS1Value()
{
return this.s1.GetValue();
}
public int GetS2Value()
{
return this.s2.GetValue();
}
public int GetS3Value()
{
return this.s3.GetValue();
}
}
```
最后,还需要在Startup.cs进行注入
```c#
// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// 单例模式
services.AddSingleton();
// 作用域模式
services.AddScoped();
// 瞬时模式
services.AddTransient();
// 为了保证结果,将Business类注册为瞬时模式,每次注入都是全新的。
services.AddTransient();
}
```
## 2.2、验证单例(Singleton)
对于单例来说,我们期望,在整个程序启动期间,都是同一个实例,所以,我们只需要在Service中,增加一个局部变量,做累加就可以验证。
```c#
// DefaultController.cs
[Route("singleton")]
public IActionResult SingletonTest()
{
this.defaultBiz.GetS1Value();
return Json(new
{
s1 = s1.GetValue()
});
}
```
然后我们访问 `http://localhost:5000/singleton` 多次,输入如下:
```c#
// 第一次
{ s1: 2 }
// 第二次
{ s1: 4 }
// 第三次
{ s1: 6 }
```
可以发现,每请求一次,value都会增加2。分析下怎么来的呢?
1. 首先是执行 `defaultBiz.GetValue()` 中,根据内部代码,此处会对注入的实例,value + 1。
2. 之后,`Json()`中的,`s1 = s1.GetValue()`,此处再一次增加了1。
3. 综上,一次请求,s1 中的value值会增加2,由于是单例模式,在整个服务运行期间,都是一个实例,所以,每次请求都会累加2。
## 2.3、验证作用域实例(Scoped)
```c#
// DefaultController.cs
[Route("scoped")]
public IActionResult ScopedTest()
{
this.defaultBiz.GetS2Value();
return Json(new
{
s2 = s2.GetValue()
});
}
```
然后我们访问 `http://localhost:5000/scoped` 多次,输入如下:
```c#
// 第一次
{ s2: 2 }
// 第二次
{ s2: 2 }
// 第三次
{ s2: 2 }
```
从结果可以看出,每次请求的返回值是固定的,都为2,也就是证明了Service2中,value++执行了两次。对于执行value++的代码,只有 `defaultBiz.GetS2Value()` 和 `s2 = s2.GetValue()`,所以这两处操作的是同一个实例。这也就证明了,对于 `Scoped` 生命周期,在作用域(可以简单理解为单次请求,实际上并不准确,**注意,此处为考虑多线程的情况**)内,都是使用的同一个实例。在不同的请求之间,则是不同的实例。
## 2.4、验证瞬时实例(Transient)
```c#
// DefaultController.cs
[Route("transient")]
public IActionResult TransientTest()
{
return Json(new
{
s3 = s3.GetValue()
});
}
```
然后我们访问 `http://localhost:5000/transient` 多次,输入如下:
```c#
// 第一次
{ s3: 1 }
// 第二次
{ s3: 1 }
// 第三次
{ s3: 1 }
```
从结果来看,每次请求的都是相同的返回值,`s3 = 1`,这说明了,两次操作的value++,是针对的不同实例。也就是每次使用 Service1,都是全新的实例。
# 3、扩展(Autofac DI 类库)
Asp.Net Core中默认的DI,相对还是比较简单的,只有三个生命周期。对于时下比较的依赖注入库,一般都会有更多的生命周期,有些还会有生命周期事件可以监控。
以 `Autofac` 为例,该类库提供了如下一些生命周期,可以做到更精细化的控制:
1. 单次依赖(Instance Per Dependency)- 也就是Transient,每次获取实例都是全新的。
2. 单例(Single Instance) - 也就是单例,整个服务周期都是一个实例。
3. 作用域隔离的实例(Instance Per Lifetime Scope) - 也就是一个作用域一个,示例代码如下:
```c#
// 先创建作用域
using(var scope1 = container.BeginLifetimeScope())
{
for(var i = 0; i < 100; i++)
{
// 在作用域内,Resolve 的都是同一个实例
var w1 = scope1.Resolve();
}
}
// 创建另一个作用域
using(var scope2 = container.BeginLifetimeScope())
{
for(var i = 0; i < 100; i++)
{
// 在作用域内,Resolve 的都是同一个实例,但是这个实例和 scope1 作用域中的 w1 不是同一个。
var w2 = scope2.Resolve();
}
}
```
4. 带标签的作用域隔离实例(Instance Per Matching Lifetime Scope)
5. 单次请求作用域实例(Instance Per Request) - 每个请求作为一个作用域。
6. 指定Owner的作用域实例(Instance Per Owned)- 对于同一个Owner,实例保持一致
7. 线程作用域实例(Thread Scope)
**更多 Autofac 生命周期相关内容,请参考:[https://autofac.readthedocs.io/en/latest/lifetime/instance-scope.html](https://autofac.readthedocs.io/en/latest/lifetime/instance-scope.html)**
# 4、总结
本文主要简单演示了 Asp.Net Core 中默认的几种服务生命周期效果,也抛砖引玉的说了下 Autofac 中的服务生命周期。合理的利用生命周期,可以减少对象的创建,提交程序性能。但是,用错了生命周期,则容易产生隐含的bug。在使用 DI 类库的时候,一定要理解清楚不同的生命周期的应用场景。
本文示例代码:[https://github.com/hstarorg/HstarDemoProject/tree/master/dotnet_demo/servicelifttime-demo/ServicelifttimeDemo](https://github.com/hstarorg/HstarDemoProject/tree/master/dotnet_demo/servicelifttime-demo/ServicelifttimeDemo)
================================================
FILE: .Net Platform/实战Asp.Net Core:中间件.md
================================================
================================================
FILE: .Net Platform/实战Asp.Net Core:构建一个Core Lib.md
================================================
================================================
FILE: .Net Platform/实战Asp.Net Core:部署应用.md
================================================
# 1、前言
某一刻,你已经把 .Net Core 的程序写好了。接下来,还可以做什么呢?那就是部署了。
作为一名开发工程师,如果不会部署自己开发的应用,那么这也是不完整的。接下来,我们就来说说,如何部署我们的 .Net Core 应用程序(主要是 Asp.Net Core 应用)。
# 2、Asp.Net Core 的部署方式
对于虚拟机中执行的语言来说,大都会有 SDK(Software Development Kit) 以及 XRE(X Runtime Environment)。对于 C#来说,也不例外。在 C#中,这两者的区别在于:
- .Net Core SDK 用于开发者构建 App,包含有较多开发相关的工具包(实际上,SDK 也是包含 Runtime)
- .Net Core Runtime 仅作为运行时使用,不包含开发工具包,体积较小。
既然要部署 Asp.Net Core,自然离不开 Runtime(如果装 SDK 也能跑,不过不推荐在运行环境装 SDK)。以下的部署方式的前提都是已经安装 Runtime 环境。
**下载地址:[https://dotnet.microsoft.com/download](https://dotnet.microsoft.com/download)**
## 2.1、控制台直接运行
Asp.Net Core 程序在发布后,会产生一个入口 dll 文件,要运行该程序,只需要通过 dotnet 命令执行该 dll 文件。所以,第一种方式就是直接找到 dll 文件,并使用 dotnet 命令来运行。(你说 dotnet 命令哪来的?安装了 Runtime 就有了。)
```bash
# 进行控制台执行
dotnet xxx.dll
```
**优势:**
1. 足够简单,拷贝文件就开整。
2. 兼容多平台部署。
**缺陷:**
1. 想象一下,如果控制台关掉了,服务器重启了会怎样?此时需要手动重新去重新执行。(你说,可不可以设置为开机启动?如果创建一个批处理,放在启动项中,还是能做到的)
## 2.2、IIS 部署
用 .Net Framework 开发的应用,大家都比较熟悉用 IIS 来部署。那 .Net Core 呢?虽然两者的运行模式并不相同,但微软为了减少迁移难度,自然也提供了用 IIS 的部署方法。
与 Asp.Net 不同,ASP.NET Core 不再是由 IIS 工作进程(w3wp.exe)托管,而是使用自托管 Web 服务器(Kestrel)运行,IIS 则是作为反向代理的角色转发请求到 Kestrel 不同端口的 ASP.NET Core 程序中,随后就将接收到的请求推送至中间件管道中去,处理完你的请求和相关业务逻辑之后再将 HTTP 响应数据重新回写到 IIS 中,最终转达到不同的客户端(浏览器,APP,客户端等)。
如果要使用 IIS 部署 Asp.Net Core 程序,步骤如下:
1. 首先,需要安装 .Net Core 托管捆绑包,[点此下载捆绑包](https://www.microsoft.com/net/permalink/dotnetcore-current-windows-runtime-bundle-installer)
2. 进行 IIS 控制台,确保能在模块中找到 AspNetCoreModule 托管模块。(如果执行了步骤 1,未找到,可以尝试控制台执行 `iisreset`)
3. 按照常规部署 .Net Framework 程序的方式,创建应用,在选择应用程序池的时候,注意,一定要选择`无托管代码`,如图:
4. 至此,就算部署成功了。
**优势:**
1. 学习成本低,和 .Net Framework 应用的部署方式保持类似。
2. 能够自动开机自启。
3. 可以在可视化界面中,配置端口域名绑定。
**劣势:**
1. 该方式仅能在 Windows 服务器上使用。
2. 通过 IIS 桥接一层,有性能损失。
**了解更多,请参考:[IIS 部署.Net Core 应用](https://docs.microsoft.com/zh-cn/aspnet/core/host-and-deploy/iis/?view=aspnetcore-2.2#install-the-net-core-hosting-bundle)**
## 2.3、部署为 Windows Service
在 2.2 的部署方式中,较大的缺陷就是性能损失。那么,有没有什么办法能够可以避开这个问题呢?。答案就是 Windows Service,通过 Windows Service,我们能够解决 2.1 中的开机启动和持久运行问题,也能避开 2.2 中的性能损失。具体如何做呢?如下提供一种方式(当然,也可以用其他方式来部署 Windows Service):
1. 借助 nssm 来管理 Windows Service,[Nssm](https://nssm.cc/),用法,请参考:[https://nssm.cc/usage](https://nssm.cc/usage)
2. 配置 Service 开机启动。
**优势:**
1. 高性能部署,稳定性好。
2. 支持开机启动。
**劣势:**
1. 仅能用于 Windows 服务器。
2. 引入了一个外包依赖 NSSM。
## 2.4、Linux 部署
由于 .Net Core 天生支持跨平台,如果在廉价又稳定的 Linux 上部署 .Net Core 程序逐渐成为主流。对于 Linux 上的部署,和 Windows 上并没有什么区别。首先是安装 Runtime 环境,然后拷贝程序,并通过命令行运行。
再进一步,可以使用后台模式,让程序在后台运行。
更进一步,也可以效仿 Windows,把程序启动管理作为一个服务,来达到开机启动和灵活管理的目的。
## 2.5、Docker 部署
作为当前个人认为的最棒的 .Net Core 应用部署方式,建议大家都了解下。
首先,是 Docker 的基本使用:
1. 编写 Dockerfile
2. 使用 `docker build` 构建镜像
3. 使用 `docker run` 创建容器并运行
好,我们来依次说明,对于 Docker 来说,需要先安装 Docker 环境。
接着,我们假设发布包路径如下:
```bash
root-folder/
app/ # 发布包目录
xxx.dll # 程序入口点
Dockerfile # Dockerfile文件
```
然后针对该程序,编写如下 Dockerfile:
```bash
# 根镜像
FROM microsoft/dotnet:2.2-runtime
# 拷贝程序发布包
COPY app /app
# 设置工作目录
WORKDIR /app
# 导出的端口
EXPOST 80
# 程序运行命令
CMD ["dotnet", "xxx.dll"]
```
接下来,通过在 `root-folder` 中执行 `docker build -t xxx:0.0.1 .` 来构建一个镜像。
接着,再通过 `docker run -it -p 8000:80 --name xxx-demo xxx:0.0.1` 来创建并运行容器。
这样,就可以通过 `http://localhost:8000` 来访问到你的应用程序了。
此处只是大概写下 Docker 部署的步骤,抛砖引玉。真正需要将其用于产线,还需要去学习下足够的 Docker 知识。
**额外提一下,如何选择基础镜像**
> 对于 .Net Core 来说,一般有如下几类基础镜像:
* sdk -- 相信这个都比较容易理解,就是包含了 .Net Core SDK。
* runtime -- 这个也相对容易理解,包含了.Net Core Runtime。
* runtime-deps --这个就不是很好理解, runtime? deps? 什么意思呢?就是说,这个连 Runtime都不是全的,需要你在打包的时候,选择自寄宿模式,把Runtime也打进去。
**综上,我个人推荐大家选择 runtime 这类作为基础镜像。**
# 参考文档
1. [https://docs.microsoft.com/zh-cn/aspnet/core/host-and-deploy/iis/?view=aspnetcore-2.2#install-the-net-core-hosting-bundle](https://docs.microsoft.com/zh-cn/aspnet/core/host-and-deploy/iis/?view=aspnetcore-2.2#install-the-net-core-hosting-bundle)
2. [https://hub.docker.com/r/microsoft/dotnet](https://hub.docker.com/r/microsoft/dotnet)
3. [https://docs.microsoft.com/en-us/dotnet/core/docker/building-net-docker-images?view=aspnetcore-2.2](https://docs.microsoft.com/en-us/dotnet/core/docker/building-net-docker-images?view=aspnetcore-2.2)
================================================
FILE: .gitignore
================================================
.DS_Store
Thumbs.db
db.json
*.log
node_modules/
public/
.deploy*/
================================================
FILE: AngularJS相关/Angular1.x升级指南.md
================================================
---
title: Angular1.x升级指南
date: 2017/02/21 14:47:10
---
## 0、导言
Angular从1.2.x到1.3.x是一个大的跳跃。在1.3.x之后,也有1.4.x,1.5.x这么两个大版本。
Newkit从1.2.x跳跃到1.3.x经历过一次大的变化,在之后的较大版本升级中,基本上没有大多大改动。但是其中的差异点,我们也需要去了解。
1.3.x以前的版本,我们就不去深究了,毕竟现在不是主流,我们就从1.3.x开始,来看看Angular到底有了哪些变化。
## 1、从1.3.x到1.4.x
从1.3到1.4,Angular的变化涉及到很多个方面,一一列举如下。
### 1.1、动画
在1.4中,动画功能进行了很大的重构,但是API基本保持一致。在新的版本中,我们通过注入 ``$animationCss`` 来实现用JS创建CSS动画。
**1.1.1、定义CSS动画如下:**
```javascript
angular.module('app', ['ngAnimate'])
.animation('.slide-animation', ['$animateCss', function ($animateCss) {
return {
enter: function (element, doneFn) {
var animation = $animateCss(element, {
from: { background: 'black' },
to: { background: 'blue' },
duration: 10 // one second
});
animation.start().done(doneFn);
},
leave: function(element, doneFn){
var animation = $animateCss(element, {
from: { fontSize: '12px' },
to: { fontSize: '25px' },
duration: 10 // one second
});
animation.start().done(doneFn);
}
}
}]);
```
如何使用?
```html
AAAAAAAAAAA
```
只需要在元素上设置一个class,然后当元素从隐藏到显示,则会执行 ``enter`` 动画,从显示到隐藏,则会执行 ``leave`` 动画。
**1.1.2、监听动画事件**
```javascript
// < 1.4
element.on('$animate:before', function(e, data) {
if (data.event === 'enter') { ... }
});
element.off('$animate:before', fn);
// 1.4+
$animate.on('enter', element, function(data) {
//...
});
$animate.off('enter', element, fn);
```
在1.4版本之前,我们需要通过在element上去监听动画开始和结束,另外还必须要通过 ``data.event`` 来判断动画类型。
在1.4及以后,我们可以直接通过 ``$animate`` 来监控了。
**1.1.3、触发动画**
```javascript
var $el = $('.slide-animation');
$animate.enter($el, $el.parent()).then(function(){
console.log('enter');
});
```
我们需要使用如上的方式,来手动启动动画,此时会触发元素的对应动画事件。
**注意:触发动画的回调中,如果要操作$scope,在<1.4中会失败,需要借助$apply,但在1.4+,就不需要了。示例如下:**
```javascript
$animate.enter(element, elementParent).then(function() {
$scope.$apply(function() {
$scope.explode = true;
});
});
// 1.4+
$animate.enter(element, elementParent).then(function() {
$scope.explode = true;
});
```
**1.1.4、启用/禁用动画**
```javascript
// < 1.4
$animate.enabled(false, element);
// 1.4+
$animate.enabled(element, false);
```
实现该操作的方法参数1.4+刚好和小于1.4相反。
### 1.2、表单
**1.2.1、ngMessages**
```javascript
```
在1.3版本中,``ng-messages-include`` 跟随在 ``ng-messages`` 元素上,这样并不灵活。
在1.4+中,``ng-messages-include`` 不允许更随在 ``ng-messages`` 元素上,必须放在内部,这样使得使用远程模板非常灵活。
另外,当存在多个form时,我们在使用ng-messages获取指定form的方法也有变化,如下:
```javascript
// < 1.4
...
// 1.4 +
...
ctrl.getMessages = function($index) {
return ctrl.form['field_' + $index].$error;
}
```
**1.2.2、ngOptions**
ngOptions仅仅只是内部实现变化,在使用上并没有多大差异。其中当遍历Object时,之前的版本是用的 ``for in``,导致输出的key是字符序的。新版本采用 ``Object.keys``,输出的key是定义时候的顺序。
另外当ngOptions表达式执行之后,将不会再触发ngOptions了。
**1.2.3、select**
在 **select** 元素中,这是一个非常大的变更。简单理解,如下:在1.3中,ngModel和<option>的value比较仅仅是 ``==``,所以 ``200 == '200' //true``。在1.4+中,比较方式成了 ``===``,所以 ``200 === '200' //false``。
这个时候我们可以通过如下方式处理:
```javascript
ngModelCtrl.$parsers.push(function(value) {
return parseInt(value, 10); // Convert option value to number
});
ngModelCtrl.$formatters.push(function(value) {
return value.toString(); // Convert scope value to string
});
```
实现指令如下:
```javascript
app.directive('convertNumber', function() {
return {
require: 'ngModel',
link: function(scope, el, attr, ctrl) {
ctrl.$parsers.push(function(value) {
return parseInt(value, 10);
});
ctrl.$formatters.push(function(value) {
return value.toString();
});
}
}
});
```
当然,如果我们保证ngModel的值为string类型,那就没啥问题了(在不使用ng-value的情况下)。
**1.2.4、Form**
表单的变化,主要是name属性,在 < 1.4 的版本中,我们可以设置name为 "my:form1",在1.4+版本中,不在允许这种特殊的用法。
### 1.3、模板相关
**1.3.1、ngRepeat**
< 1.4 版本中,ng-repeat的遍历顺序是字母序。
1.4+版本中,顺序是有浏览器的 ``for in`` 来返回的。
**1.3.2、ngInclude**
```javascript
// < 1.4
$scope.findTemplate = function(templateName) {
return $sce.trustAsResourceUrl(templateName);
};
// 1.4+
var templateCache = {};
$scope.findTemplate = function(templateName) {
if (!templateCache[templateName]) {
templateCache[templateName] = $sce.trustAsResourceUrl(templateName);
}
return templateCache[templateName];
};
// Or
angular.module('myApp', []).config(function($sceDelegateProvider) {
$sceDelegateProvider.resourceUrlWhitelist(['self', 'https://example.com/templates/**'])
});
```
### 1.4、Cookie
在1.4+中,cookie增加了新的API:
```javascript
get
put
getObject
putObject
getAll
remove
```
另外,``$cookieStore`` 将不推荐使用。
### 1.5、HTTP
在1.4+中,$http的 ``transformRequest`` 将不允许修改请求header。但是我们可以使用如下方式进行动态的header设置:
```javascript
$http.get(url, {
headers: {
'X-MY_HEADER': function(config) {
return 'abcd'; //you've got access to a request config object to specify header value dynamically
}
}
})
```
### 1.6、Filter
``fliter``如果作用在非数组上,将会抛出一个异常,之前则是返回一个空数组。
``limitTo``作用在不合法的value上,现在将会原样返回,之前是返回空对象,空数组。
## 2、从1.4.x到1.5.x
从 1.4.x 到 1.5.x 是一个为升级ng2做准备的变更版本。
该版本主要是增加了一些功能,所以从1.4.x升级到1.5.x基本不需要太大的改动。
在 1.5.x 版本中,增加了一些接近ng2的新特性,如:
1. angular.component() //一种偏向于angular2风格的组件(特殊的指令)
2. $onInit //生命周期钩子
3. ngAnimateSwap // ngAnimate中一个新的指令
接着,我们来看下它的一些具体变更。
### 2.1、Core
**2.1.1、$parse**
``$parse`` 增加新特性,可以使用locals来覆盖原本的context。用法如下:
```javascript
var context = { user: { name: 'Jay' } };
var locals = { user: { name1: 'Local Jay' } };
var getter = this.$parse('user.name');
console.log(getter(context)); // 'Jay'
console.log(getter(context, locals)); // 'Local Jay'
```
**2.1.2、ngOptions**
如果元素上有 ``ngOptions`` 指令,那么 ``ngModel`` 指令也必须存在,否则就会抛出错误。
另外 ``ngOptions`` 接受假值('', 0, false, null)
**2.1.3、orderBy**
对 ``undefined`` 或者 ``null`` 进行 ``orderBy`` 将会抛出错误。
### 2.2、ngAria
取消了部分元素的可访问性设置。
### 2.3 ngMessages
将 ``ngMessages`` 指令的优先级设置为1,如果有优先级低于1的指令有transclude功能,那么需要设置为更高的优先级。
### 2.4、ngResource
``$resource`` 增加了 ``$cancelRequest()`` 方法。
### 2.5、ngRoute
增加了 ``$resolveAs`` 配置属性,允许对 ``$resolve`` 指定别名。
### 2.6、ngTouch
默认禁用了 ``ngClickOverrideEnabled``,在触摸屏上,可能还有300ms的延迟。
如果要启用,那么可以使用如下方式:
```javascript
angular.module('myApp').config(function($touchProvider) {
$touchProvider.ngClickOverrideEnabled(true);
});
```
另外,建议使用FastClick来实现该功能。
需要注意,某些现代浏览器在某些场景下已经删除了300ms延迟:
Chrome 和 Firefox for Android 发现设置了 ```` 将会删除300ms延迟。
IE当设置 ``touch-action`` 为 ``none`` 或者 ``manipulation`` 移除延迟。
**注意:这些变化并不影响ngSwipe指令**。
## 3、总结
Angular1.x基本已经进入维护期了,很少会有新特性加入了。现在的重心在angular2,所以我们也可以优先对angular2做一些技术储备。
================================================
FILE: AngularJS相关/AngularJS官方FAQ.md
================================================
---
title: AngularJS官方FAQ
date: 2015/3/13 11:24:39
---
## 相关:最佳实践,反模式
### 1、为什么会觉得jQuery插件缺失?
请记住:当你在使用jQuery插件时,请在AngularJS之前加载jQuery库
**分析:** 因为AngularJS自带jqLite(可以理解为jQuery的精简版),如果先引入AngularJS的话,那么AngularJS会采用jqLite,而不是完整的jQuery库。
### 2、如何从一个Controller中访问DOM元素?
不要从Controller中执行DOM的选择/遍历。HTML还没有被渲染。查一查”directive“
### 3、为什么Angular说 controller/directive等缺失?
调用 `angular.module('myApp',[])` 总是会创建一个新的模块(同时干掉已有的重名模块)。相反,使用一个参数的方式调用 angular.module('myApp') 来引用已经存在的模块。
### 4、如何渲染未转义的数据?
```javascript
$sce.trustAsHtml(data)
如何禁用$sce?
app.config(['$sceProvider', function($sceProvider) {
$sceProvider.enabled(false);
}]);
```
### 5、当array/object/$resource-result变化时,应该如何监视?
```javascript
$scope.$watch 有第三个参数设置来监视值变化(非引用变化)
$watch(watchExpression, listener, [objectEquality]);
[objectEquality]设置为true,则使用angular.equals对象相等,而不是使用引用相等比较。
```
### 6、怎样才能序列化表单数据提交?
**不要这么做!** 不要尝试手动收集输入框值。仅仅只需要在每一个表单元素上附加 `ng-model="data.myField`,在需要使用的地方,使用 `$scope.data` 即可
### 7、总是在 ng-models 上使用(.) =>最佳实践
```html
```
### 8、应该如何从 service 中访问 scope ?
`$rootScope` 相当于 `ng-app` 标记,它能够被引导或者是服务注入,可以用于在所有的 scopes 上添加新功能和值
**注意:避免这样做--这相当于定义全局变量**
### 9、`module().factory()` 和 `module().service()` 不同点是什么?
[查看讨论信息](https://groups.google.com/forum/?fromgroups#!topic/angular/56sdORWEoqg)
### 10、如何防止无样式的内容闪现(页面显示双大括号绑定表达式)?
在一些地方使用 `ng-bind` 来替换双括号表达式
### 11、为什么 `` 不工作?
仅有的 `ng-*` 属性中,需要 `{{xxx}}` 的只有 `ng-src` 和 `ng-href`,因为最终的结果必须是一个字符串,不是一个表达式。所以其他的不能工作。
### 12、嵌套 routes/views?
或许吧
### 13、可以在行内指定模板或者是分部视图吗?
可以。可以采用 `` ,Angular会使用它来替换。
### 14、 如何在 ngResource 地址中使用端口?
如下:$resource('example.com\\:8080')
### 15、为什么插件触发的change事件似乎不工作?
Angular监视 [input](https://developer.mozilla.org/en-US/docs/Web/Events/input) 事件,不是'change' 事件。
### 16、不要使用jQuery来切换crap(待定:无效元素),在行内使用一些变量标记。
```html
...
```
### 17、如何从DOM检查上查看 scope ?
Google Chrome:安装 Batarang extension,检查一个DOM元素,然后在console中键入$scope
Firefox/Firebug:检查一个DOM元素,然后在console中键入 `angular.element($0).scope()`
或者 `$($0).scope()`
IE10+: 使用F12工具,检查一个元素。然后在console中键入 `angular.element($0).scope()`
或者 `$($0).scope()`
### 18、你有一些好的指令示例/库吗?
[AngularUI](http://angular-ui.github.com/) 是非常棒的AngularJS工具集合(甚至是更好的示例代码)
### 19、IE?
针对IE8.0或者更早,你需要[阅读这个](https://docs.angularjs.org/guide/ie)和[使用这个](http://angular-ui.github.io/#ieshiv)
### 20、必须对路由使用#?
参考 [$locationProvider](https://docs.angularjs.org/api/ng/provider/$locationProvider)
### 21、你应该在尝试用指令包装jQuery插件前,优先尝试使用[AngularUI Passthru Directive (uiJq) ](http://angular-ui.github.io/#directives-jq)
### 22、为什么我的 $scope.$watch() 递归触发?
如果你在 $scope.$watch(newVal,oldVal)中改变 newVal ,它会重复触发。在 $watch 运行后,$scope 会重新评估,被观察对象将被重新触发。
### 23、何时我需要使用 $scope.$apply() ?
仅仅需要在没有angular 事件/回调时 使用 $scope.$apply()。它通常不属于任何地方。
### 24、启用了 html5Mode ,如何获取<a href />的后退行为?
如果你想一个链接能够全页面刷新,那么只需要在a标记上添加 target="_self"
### 25、如何 .preventDefualt() 或 .stopPropagation() ?
所有的 ng-click 和相关的绑定都注入了 $event 事件对象,你可以用它来调用 .preventDefualt(),甚至是对象传递给你的方法。
### 26、AngularJS在我的Chrome扩展中不工作!
你需要使用 [ng-csp](http://docs.angularjs.org/api/ng.directive:ngCsp)
### 27、如何缓存 $http 和html 分部视图
```javascript
//使用装饰器,添加缓存功能
myAppModule.config(function($routeProvider, $provide) {
$provide.decorator('$http', function($delegate){
var get = $delegate.get;
$delegate.get = function(url, config){
url += (url.indexOf('?') !== -1) ? '?' : '&';
url += 'v=' + cacheBustVersion;
return get(url, config);
};
return $delegate;
});
});
```
## 测试
### 1、拒绝/解决一个 $q.defer() 不通过
你必须在处理它们的时候添加 `$scope.$apply()`
### 2、Jasmine spyOn() 不执行 spy'd 功能
不一定是AngularJS的问题,但是你需要追加 `.addCallThrough()`
================================================
FILE: AngularJS相关/AngularJS教程:1W字综合指南.md
================================================
---
title: AngularJS教程:1W字综合指南
date: 2015/3/13 11:24:39
---
# AngularJSj教程:1W字指南(译)
**原文地址:**[http://www.airpair.com/angularjs](http://www.airpair.com/angularjs)
## 1、AngularJS简介
Angular 是用于编写引人注目的Web应用程序的是客户端 MVW JavaScript框架。它由Google创建好维护,(offers a futuristic spin on the web and its upcoming features and standards.
Read more at http://www.airpair.com/angularjs#tY7q00WpGrTLB71Z.99)
MVW 即 Model-View-Whatever,它是能在开发应用程序时,为我们提供灵活性的一种设计模式。我们可以选择MVC(Model-View-Controller)或者是MVVM(Model-View-ViewModel)方式。
本教程可以作为一个最终的资源来开始学习AngularJS,它的概念和它背后的API,同时能帮助您学习如何实现现代的Web应用程序。
AngularJS自身作为增强HTML的一个框架。它从多种语言包括JavaScript和服务端语言中获得灵感,使得HTML也成为了动态语言。这意味着我们获得了一个完全的数据数据方式来开发应用程序,不再需要刷新实体,更新DOM和其他费时任务如浏览器bug和不一致。我们可以只关注数据,让数据关心HTML的方式来编写我们的应用程序。
## 2、JavaScript框架中的工程概念
AngularJS在处理提供数据绑定和其他工程概念上,和其他框架如Backbone.js和Ember.js采取了不同了做法。我们坚持使用熟悉的、令人喜欢的HTML,使Angular拦截它,并增强它。Angular将纯粹的JavaScript对象用于数据绑定,保证任何模型变化都会更新DOM。当模型值更新了一次,Angular会更新来自应用程序的状态来源对象。
### 2.1、MVC 和 MVVM
如果你已经习惯了构建静态网站,你可能更熟悉手动一块一块的构建HTML,通过数据一遍一遍的打印相同的HTML。这可能是grid中的列,一个导航结构,一个链接列表或者是图片等等。在这个实例中,你需要习惯一点小东西的变化都需要手动更新HTML的痛苦,你必须更新模板来保持其他用途的一致性。你还要为每个导航项目杜绝相同的HTML。
深呼吸一下,通过Angular我们能实现恰当的关注点分离以及动态HTML。这意味着数据在模Model中,HTML是作为一个微小的模板被渲染为View,我们能使用Controller来连接它们两个,并驱动Model和View值的变化。
================================================
FILE: AngularJS相关/AngularJS:Looking under the hood.md
================================================
---
title: AngularJS:Looking under the hood
date: 2015/3/13 11:24:39
---
原文地址:[https://www.binpress.com/tutorial/angular-js-looking-under-the-hood/153](https://www.binpress.com/tutorial/angular-js-looking-under-the-hood/153)
**用AngularJS写得越多,你就越惊叹于它的神奇。我对Angular能做的一些奇妙的事情非常好奇,然后我决定分析它的源代码,看看我能否揭示它的一些秘密。我记录了我在23000多行Angular源码中发现的真正有用的,能够解释Angular先进(和隐藏)的方面的一些内容。**
## 1、Dependency Injection annotation process
依赖注入(DI)是除开用代码获取或创建依赖之外的一条不同的请求依赖的方式。简单的说,依赖是作为一个注入对象传递给我们的。Angular允许我们在我们的应用程序中通过像Controllers和Directives的方法来使用DI。我们能创建自己的依赖,同时允许Angular在请求它们的时候被注入。
在Angular中,一个最常用的被请求的依赖是 *$scope*。例如:
function MainCtrl ($scope){
//access to $scope
}
angular.module('app').controller('MainCtrl', MainCtrl);
对于没有使用过Angular提供的依赖注入的JavaScript开发者来说,这看起来像一个局部变量名。实际上,它仅仅是我们所请求的依赖名称的一个占位符。Angular查找这些占位符,然后通过DI将它们转换为真正的依赖对象,让我们来仔细看看。
### 方法参数 ###
直到我们压缩我们的应用前,方法参数都运行正常。当你压缩你的代码,你的方法定义将会用字符表示参数而不是单词-这意味着Angular不能找到你想要的!Angular使用了一个方式来解决,调用function的 *toString()* 方法。这将返回函数的字符串形式!接下来我们就能访问正在被请求的参数。Angular
var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
var FN_ARG_SPLIT = /,/;
var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
Angular做的第一件事就是将函数转换为字符串,这是JavaScript中非常有用的一个特性。这将给我们一个字符串类型的函数,如:
'function MainCtrl ($scope) {...}'
接下来,Angular使用如下方法,移除所有的注释:
fnText = fn.toString().replace(STRIP_COMMENTS, '');
紧接着,Angular从处理好的function中分割参数来创建真正有用的部分,
argDecl = fnText.match(FN_ARGS);
Angular接下来使用 *.split()* 来移除空白字符,同时返回我们请求的参数数组。为了更完美,Angular使用了一个内部的forEach方法来迭代这个数组,并匹配参数名称然后将它们添加到 *$inject* 数组中。
forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) {
arg.replace(FN_ARG, function(all, underscore, name) {
$inject.push(name);
});
});
这是你能想象的一个昂贵的处理流程。对每个函数的4个正则查找和一很多转换会造成性能损耗。当我们得到了Angular抽象的 *$inject* 数组,我们可以直接切入且田填充 *$inject* 数组来保存Angular困难和开销时间长的操作。
### $inject 对象 ###
我们可以通过在函数上添加 *$inject*属性来指定依赖自身,其中,如果存在的话,Angular使用DI注解。这是很容易的最可读的语法。例子如下:
function SomeCtrl ($scope) {
}
SomeCtrl.$inject = ['$scope'];
angular.module('app', [])
.controller('SomeCtrl', ['$scope', SomeCtrl]);
这样节省了Angular的许多工作-替代了检查方法参数,或者是操纵数组(详情请查看下一章节:Array Arguments),它仅仅返回和运行指定的 *$inject* 数组。简单,高性能。
理想情况下,由于依赖注入在我们自己的时间和Angular的转换时间上开销很大,我们可以使用任务运行工具如Grunt.js或者是Gulp.js 来自动化注入任务或者是数组语法。
**Note:这个并没有实例化被依赖的所有服务,Angular所做的只是标注相关的名字-框架的其他部分关心对象注入。**
### Array Arguments
最后一个例子使用了我们通常看见的数组索引对应函数参数序号的语法,例如:
['$scope', function($scope){}]
数组的顺序是非常重要的,因为函数的参数将会按照同样的顺序,以此来避免依赖被错误的实例化和可能引发的错误。
function SomeCtrl ($scope, $rootScope) {
}
angular.module('app', [])
.controller('SomeCtrl', ['$scope', ‘$rootScope’, SomeCtrl]);
我们需要做的是传递函数作为数组的最后一个项,Angular会删除这个函数,并遍历数组所注明的依赖名称,就好像我们创建的 *$inject* 属性。当Angular解析一个方法的时候,它会检查参数是不是一个数组,如果是,那么最后一项是函数,其他的则是依赖。
else if (isArray(fn)) {
last = fn.length - 1;
assertArgFn(fn[last], 'fn');
$inject = fn.slice(0, last);
}
## 2、Factory vs Service ##
Factory 和 Service 非常类似,但往往开发人员都难以理解它们。
当 *.service()* 已经实例化,那么 *new Service()* 将被引擎调用,返回一个新实例给我们。本质上,*。.service()* 是作为构造函数被使用的。
service 基本上是一个 factory,然而它是创建时被实例化,因为,你需要在 service 中使用this来注册变量和函数,来替代在factory中返回一个对象的方式。
factory 是非常接近面向对象中的“工厂模式”,当你注入了这个 factory ,你就获得了完整的方法,允许你创建你需要的新的实例-本质上是通过一个对象创建多个新对象。
你可以看下在Angular源码中的内部的工作:
function factory(name, factoryFn) {
return provider(name, { $get: factoryFn });
}
function service(name, constructor) {
return factory(name, ['$injector', function($injector) {
return $injector.instantiate(constructor);
}]);
}
## 3、New $scope creation from $rootScope
Angular中所有的scope都是 *$rootScope* 的下级。 *$rootScope* 是通过 *new Scope()*创建的,进一步的子 scope 是通过 *$scope.$new()* 创建的。
var $rootScope = new Scope();
在 *$new* 方法里面,Angular设置了一个原型链来允许允许 scope 引用它们的父亲,它们的自己跟踪(作为生命周期),和以前的兄弟 scope 。
从下面的代码,如果你请求了一个隔离的 scope ,它会创建一个 *new Scope()* ,否则,它会创建一个从父级继承的子 scope 。
我省略了一些不必要的代码,但这里的是重点:
$new: function(isolate) {
var child;
if (isolate) {
child = new Scope();
child.$root = this.$root;
} else {
// Only create a child scope class if somebody asks for one,
// but cache it to allow the VM to optimize lookups.
if (!this.$$ChildScope) {
this.$$ChildScope = function ChildScope() {
this.$$watchers = null;
};
this.$$ChildScope.prototype = this;
}
child = new this.$$ChildScope();
}
child['this'] = child;
child.$parent = this;
return child;
}
当你使用*$scope.$new()*来测试Controller的时候,这也是非常好的能了解测试目的。这有助于明确对我来说Angular是如何创建新的scope的,为什么用Angular mocks 模块来嘲笑测试驱动开发(TDD)。
## 4、Digest Cycle
Digest Cycle 经常作为 *$digest* 被我们看到,这是Angular双向绑定的能力。当一个模型值更新的时候,它会运行,检查它最后已知的值,如果值有变化,呼叫适当的监听器。这是基本的脏检查 - 它针对所有有可能的值来检查,如果是脏值,那么呼叫相关的监听器,直到他没有脏值。我们快速看一下它是如何工作的:
$scope.name = 'Todd';
$scope.$watch(function() {
return $scope.name;
}, function (newValue, oldValue) {
console.log('$scope.name was updated!');
} );
当你调用 *$scope.$watch*,你注册了两件事。参数一是一个函数,返回你想要监视的值(当你提供一个字符串的时候,Angualr会将他转换为函数)。当 $digest 运行时,监视的参数将被调用,返回任何你想要的值。参数二是当你的参数一变化时,想要执行的函数。看一下Angular是怎样注册watch的。
$watch: function(watchExp, listener, objectEquality) {
var get = $parse(watchExp);
if (get.$$watchDelegate) {
return get.$$watchDelegate(this, listener, objectEquality, get);
}
var scope = this,
array = scope.$$watchers,
watcher = {
fn: listener,
last: initWatchVal,
get: get,
exp: watchExp,
eq: !!objectEquality
};
lastDirtyWatch = null;
if (!isFunction(listener)) {
watcher.fn = noop;
}
if (!array) {
array = scope.$$watchers = [];
}
// we use unshift since we use a while loop in $digest for speed.
// the while loop reads in reverse order.
array.unshift(watcher);
return function deregisterWatch() {
arrayRemove(array, watcher);
lastDirtyWatch = null;
};
}
这个函数推送你提供的参数到 scope 的 *$$watchers* 数组中,同时,返回一个方法允许你停止watch。
然后,每当*$scope.$apply*或者*$scope.$digest* 运行时,digest cycle将被运行。
### 未完...待续...
================================================
FILE: AngularJS相关/Angular从0到1:function(上).md
================================================
---
title: Angular从0到1:function(上)
date: 2017/02/21 14:47:10
---
## 1、前言
Angular作为最流行的前端MV*框架,在WEB开发中占据了重要的地位。接下来,我们就一步一步从官方api结合实践过程,来学习一下这个强大的框架吧。
Note:每个function描述标题之后的★标明了该function的重要程度(1~5星)。
## 2、function(上)
Angular封装了一系列公共方法,帮助我们更简单的使用JS。这些就是Angular的function。
### 2.1、angular.bind(★)
angular.bind类似于Function.prototype.bind,实现函数柯里化,返回一个函数代理。eg:
函数原型
angular.bind(/*this对象*/self, /*要封装的function*/fn, /*参数列表*/args);
//原始函数
function fun(arg1, arg2, arg3) {
console.log(this);
console.log(arg1);
console.log(arg2);
console.log(arg3);
}
fun('arg1', 'arg2', 'arg3');
//如果几个常用参数都是固定的,而且该函数被调用频繁,那么就可以包装一下
var fun2 = angular.bind(window, fun, 'arg1', 'arg2');
//新的调用方式
fun2('arg3');
### 2.2、angular.bootstrap(★★★★)
用于使用angular执行渲染元素。也是angular的启动方法,如果没有在页面上指定ng-app,必须要手动调用该函数进行启动。
angular.bootstrap(/*Dom元素*/element, /*Array/String/Function*/[modules], /*Object*/[config]);
//常规用法
angular.bootstrap(document, ['app'])
//带配置项
angular.bootstrap(document, ['app'], {strictDi: true/*Defaults: false*/})
### 2.3、angular.copy(★★★★★)
Angular.copy用于复制对象,由于angular的双向绑定特点,那么如果直接操作$scope对象,那么很容易就会改变ui的显示,这个时候就需要借助angular.copy来创建一个对象副本,并进行操作。
//原型
angular.copy(source, [destination]);
var obj = {a: 1};
var obj2 = angular.copy(obj);
var obj3;
angular.copy(obj, obj3);
console.log(obj2 === obj) //false
console.log(obj3 === obj) //false
var obj4;
//第二个和参数和返回值是相等的,而且第二个参数不管以前是什么,都会被重新赋值
var obj5 = angular.copy(obj, obj4);
console.log(obj4 === obj5); //true
### 2.4、angular.element(★★★★)
等价与jQuery的选择器,如果在angular之前没有引入jQuery,那么就使用jqLite包装.
angular.element('body');
//等价于
$('body');
//用法
var $body = angular.element('body');
### 2.5、angular.equals(★★)
用于比较两个对象是否相等,如下示例的规则和JS有区别,注意识别。
var obj1 = {a: 1};
var obj2 = obj1;
//引用一致,则相等
console.log(angular.equals(obj1, obj2)); // true
obj2 = {a: 1};
//引用不一致,对象表现一致,则相等
console.log(angular.equals(obj1, obj2)); // true
obj2 = {a: 1,$a: 2};
//对象比较时,忽略以$开头的属性
console.log(angular.equals(obj1, obj2)); // true
obj1 = /aa/;
obj2 = /aa/;
//正则表达式表现相等,则相等
console.log(angular.equals(obj1, obj2)); // true
//NaN与NaN比较,则相等
console.log(angular.equals(NaN, NaN)); // true
### 2.6、angular.extend(★★)
功能上和jQuery.extend没多大差异
//原型-第一个参数为目标,之后的参数为源,同时返回dst
angular.extend(dst, src);
//示例
var obj1 = {a: 1};
var obj2 = angular.extend(obj1, {a: 2}, {b: 3});
console.log(obj1)
console.log(obj1 === obj2); //true
### 2.7、angular.forEach(★★★★★)
angular.forEach用于遍历对象或者数组,类似于ES5的Array.prototype.forEach
//原型
angular.forEach(obj, iterator, [context]);
var values = {name: 'misko', gender: 'male'};
var arr = ['misko', 'male'];
angular.forEach(values, function (value, key) {
console.log(key + ' = ' + value);
});
angular.forEach(arr, function (value, i) {
console.log(i + ' = ' + value);
});
//还可以传递this
var obj = {};
angular.forEach(values, function (value, key) {
obj[key] = value;
}, obj);
console.log(obj);
### 2.8、angular.fromJson(★★★)
angular.fromJson将JSON字符串转换为JSON对象,注意,必须严格满足JSON格式,比如属性必须双引号,该函数内部实现是利用JSON.parse()。
//原型
angular.fromJson(/*string*/ jsonString)
var jsonString = '{"p1": "xx", "p2": 1, "p3": true}';
var jsonObj = angular.fromJson(jsonString);
console.log(jsonObj);
### 2.9、angular.toJson(★★★)
angular.toJson可以将对象,数组,日期,字符串,数字转换为json字符串
//原型
angular.toJson(obj, pretty);
var obj = {p1: 1, p2: true, p3: '2'};
var jsonString = angular.toJson(obj);
console.log(jsonString);
//美化输出格式(设置为true,默认采用2个字符缩进)
jsonString = angular.toJson(obj, true);
console.log(jsonString);
//还可以设置缩进字符数
jsonString = angular.toJson(obj, 10);
console.log(jsonString);
### 2.10、angular.identity(★)
angular.identity返回该函数参数的第一个值。编写函数式代码时,非常有用(待使用)。
//官方示例
function transformer(transformationFn, value) {
return (transformationFn || angular.identity)(value);
};
//简单演示
var value = angular.identity('a', 1, true);
console.log(value); // 'a'
### 2.11、angular.injector(★★)
angular.injector能够创建一个injector对象,可以用于检索services以及用于依赖注入。
//原型,[strictDi]默认false,如果true,表示执行严格依赖模式,
//angular则不会根据参数名称自动推断类型,必须使用['xx', function(xx){}]这种形式。
angular.injector(modules, [strictDi]);
//定义模块A
var moduleA = angular.module('moduleA', [])
.factory('F1', [function () {
return {
print: function () {
console.log('I\'m F1 factory');
}
}
}]);
var $injector = angular.injector(['moduleA'])
$injector.invoke(function (F1) {
console.log(F1.print());
});
//此种写法会报错,因为是严格模式
var $injector2 = angular.injector(['moduleA'], true)
$injector2.invoke(function (F1) {
console.log(F1.print());
});
//可以采用如下写法
$injector2.invoke(['F1', function (F1) {
console.log(F1.print());
}]);
### 2.12、angular.module(★★★★★)
angular.module可以说是最常用的function了。通过它,可以实现模块的定义,模块的获取。
//定义模块A
var moduleA = angular.module('moduleA', []);
//定义模块B,模块B依赖moduleA
var moduleB = angular.module('moduleB', ['moduleB']);
//可以在第三个参数上设置配置函数
var moduleB = angular.module('moduleB', ['moduleB'], ['$locationProvider', function ($locationProvider) {
console.log($locationProvider);
}]);
//等价于
var moduleB = angular.module('moduleB', ['moduleB'])
.config(['$locationProvider', function ($locationProvider) {
console.log($locationProvider);
}]);
//获取模块
angular.bootstrap(document, ['moduleB']);
================================================
FILE: AngularJS相关/Angular从0到1:function(下).md
================================================
---
title: Angular从0到1:function(下)
date: 2017/02/21 14:47:10
---
## 1、前言
## 2、function(下)
### 2.13、angular.isArray(★★)
``angular.isArray``用于判断对象是不是数组,等价于``Array.isArray``
console.log(angular.isArray([])); // true
console.log(angular.isArray({0: '1', 1: '2', length: 2})); // false
### 2.14、angular.isDate(★★)
通过判断toString.call(value)是不是等于'[object Date]' 来判断对象是个是一个Date对象.
console.log(angular.isDate(new Date())); // true
console.log(angular.isDate(223)); // false
### 2.15、angular.isDefined(★★)
判断对象或者属性是否定义
var obj = {a: 1, b: null, c: undefined};
console.log(angular.isDefined(obj.a)); // true
console.log(angular.isDefined(obj.b)); // true
console.log(angular.isDefined(obj.c)); // false
console.log(angular.isDefined(obj.d)); // false
### 2.16、angular.isElement(★★)
此方法判断元素是不是一个元素(包含dom元素,或者jquery元素)
console.log(angular.isElement(document.getElementsByTagName('body')[0])); // true
console.log(angular.isElement($('body'))); // true
### 2.17、angular.isFunction(★★)
此方法判断对象是不是一个function ,等价于 typeof fn === 'function'
console.log(angular.isFunction(new Function('a', 'return a'))); // true
console.log(angular.isFunction(function(){})); // true
console.log(angular.isFunction({})); // false
### 2.18、angular.isNumber(★★)
判断数字是否为number
function isNumber(value) {
return typeof value === 'number';
}
### 2.19、angular.isObject(★★)
function isObject(value) {
return value !== null && typeof value === 'object';
}
### 2.20、angular.isString(★★)
function isString(value) {
return typeof value === 'string';
}
### 2.21、angular.isUndefined(★★)
function isUndefined(value) {
return typeof value === 'undefined';
}
### 2.22、angular.lowercase(★★)
转换字符串为小写模式,如果参数不是字符串,那么原样返回
var lowercase = function(string) {
return isString(string) ? string.toLowerCase() : string;
};
console.log(angular.lowercase(1)); // 1
console.log(angular.lowercase('ABCdef')); // 'abcdef'
### 2.23、angular.uppercase(★★)
转换字符串为大写模式,如果参数不是字符串,那么原样返回
var uppercase = function(string) {
return isString(string) ? string.toUpperCase() : string;
};
console.log(angular.uppercase(1)); // 1
console.log(angular.uppercase('ABCdef')); // 'ABCDEF'
### 2.24、angular.merge(★★)
将多个对象进行深度复制,与extend()不同,merge将会递归进行深度拷贝。该拷贝是完全深拷贝,就连对象引用也不一样。
var o = {};
var obj1 = {a1: 1, a2: 2, a3: [1]};
var obj2 = {b1: [2], b2: /abc/};
var obj3 = [o];
var obj4 = {d: o};
var result = angular.merge({}, obj1, obj2, obj3);
console.log(result);
console.log(result[0] === o); // false
console.log(result.d === o); // false
### 2.25、angular.noop(★★)
一个空函数,调试时非常有用。可以避免callback未定义引发的error。
function foo(callback) {
var result = calculateResult();
(callback || angular.noop)(result);
}
### 2.26、angular.reloadWithDebugInfo(★★)
启用DebugInfo,该设置优先级高于``$compileProvider.debugInfoEnabled(false)``
================================================
FILE: AngularJS相关/Angular再回首(1)-Component组件.md
================================================
---
title: Angular再回首(1)-Component组件
date: 2017/02/21 14:47:10
---
## 0、再谈组件
``Component(组件)`` 在 ``Angular1`` 就已经有雏形了,那就是指令。在 ``Angular2`` 中,组件的概念被大大的强化,甚至是Angular2的核心概念。
在前端这么多年的演变中,组件也反哺到 ``Angular1``,成为 ``Angular1`` 的一种重要特性,在此之前,我们仅仅可以用 ``Directive`` 来实现类似组件的效果。
## 1、Angular组件与指令
在 ``Angular 1.5.x`` 中,新增加了 ``angular.component`` 方法,用于实现组件的构造。
在此之前,我们可能用 ``angular.directive`` 来实现类似的效果。
这个时候我们可能就会疑惑,它们有什么区别呢?
| Feature | Directive | Component |
|---|---|---|
| bindings | No | Yes (binds to controller) |
| bindToController | Yes | (default: false) No (use bindings instead) |
| compile function |Yes | No |
| controller | Yes | Yes (default function() {}) |
| controllerAs | Yes | (default: false) Yes (default: $ctrl)|
| link functions | Yes | No|
| multiElement | Yes| No|
| priority| Yes| No|
| require | Yes| Yes|
| restrict |Yes| No (restricted to elements only)|
| scope |Yes (default: false) |No (scope is always isolate)|
| template |Yes |Yes, injectable|
| templateNamespace| Yes| No|
| templateUrl |Yes |Yes, injectable|
| terminal| Yes| No|
| transclude |Yes (default: false) |Yes (default: false)|
更多信息,请参考 [Angular 官方说明](https://docs.angularjs.org/guide/component)
从上表我们可以看出,对于 ``Directive``,``Component`` 从设计思路上更加完善,也更加纯粹。总得来说,组件显得更易理解,更简单易用。
## 2、组件生命周期
在 ``angular.directive()`` 中,是没有生命周期这个概念的,我们无法在指令的特定阶段插入自己的逻辑。
但是在 ``angular.component()`` 中,则是具有特定的生命周期,以方便我们进行控制。
生命周期如下:
1. $onInit -- 指令初始化时执行(放置初始化代码)
2. $onChanges(changesObj) -- 组件数据变化时执行,并可获取变更对象
3. $doCheck() -- 执行变更检测时执行
4. $onDestroy() -- 组件释放时执行(放置清理代码)
5. $postLink() -- 类似后连接函数 (一般放置dom操作,因为此时组件已经渲染好)
实例:
```javascript
((angular, window) => {
class AlertComponent {
constructor() {
}
$onInit() {
console.log('init');
}
$onChanges(changesObj)
console.log('change', changesObj);
}
$doCheck() {
console.log('check');
}
$onDestroy() {
console.log('destroy');
}
$postLink() {
console.log('post link');
}
}
AlertComponent.$inject = []; // 配置依赖项
angular.module('components').component('jAlert', {
templateUrl: 'components/alert/alert.html',
// scope绑定语法,< 单向绑定(变量),@ 单向绑定(纯字符串), = 双向绑定,& 事件绑定
bindings: {
menuData: '<'
},
controller: AlertComponent,
controllerAs: '$ctrl',
require: '',
transclude: false
});
})(window.angular, window);
```
在页面使用该指令后,可以在控制台看出如下输出:
```
init
check
post link
N个check(脏检查)
```
在切换路由,或者其他会删掉该组件的操作时,会看出控制台输出 ``destroy``。
如果中途有数据变化,控制台还会输出 ``change``。
这就是整个组件的生命周期。
## 3、属性绑定
在 ``directive`` 中,我们要获取数据,一般会采用 ``$scope`` 传参,或者通过link函数来捕获参数。
在新的组件申明中,我们只需要通过 ``bindings`` 就可以实现复杂的参数绑定。
简单思考下,我们可能需要哪些绑定呢?
1. 双向绑定 (双向)
2. 单向绑定变量 (从外到内)
3. 单向绑定属性(字符串)(从外到内)
4. 输出绑定 (从内到外)
在组件的 ``bindings`` 属性中,我们也刚好有四种语法,来一一对应这四种绑定。
具体写法如下:
```javascript
bindings: {
model: '=', // 双向绑定
title: '@', // 单项绑定字符串(直接用组件上的属性值)
key: '<', // 单项绑定变量,取到属性值,然后返回$scope[属性值]
onClick: '&' // 输出绑定,执行外部函数
}
```
假设组件标签为 ``
``,那么用法如下:
```javascript
$scope = {
model: '1',
key: 'abc',
onClick: () => {
}
};
```
```html
```
此时,我们在组件中,就能获取到对应的值:
```javascript
{
model: 1, // 从scope中取
key: 'abc', // 从scope中取
title: 'Title', // 直接用string
onClick: fn // 执行该onClick会触发外部函数$scope.onClick
}
```
**注意:关于输出函数传递参数,需要有特定的写法(一定要注意!!!)**
*在组件中的写法*
在组件中,要给该函数传参,必须使用:
```javascript
this.onClick({
param1: 'xxx',
param2: 'BBB'
});
```
的写法,并建议参数名使用 ``$`` 开头,如:``$event``。
*在组件绑定中的写法*
```html
```
注意onClick的写法,里面的参数名称,必须和组件中参数对象中的key匹配。
## 4、给组件设定外部HTML
在使用组件过程中,我相信很容易遇到需要使用外部html的组件,如 ``Tabs, Panel`` 等,那我们给组件内部传入自定义的HTML呢?
这个时候,我们可以使用 ``ng-transclude``
### 4.1、传递单个HTML片段
首先,主要在注册组件时,开启 ``transclude``(设置transclude为true),然后我们就可以在组件html中,设定占位符,有如下两种方式:
```html
```
然后在使用组件的地方,就可以直接把要使用的HTML放在组件标记中,如:
```html
我会被传递到主键内部
```
### 4.2、传递多个HTML片段
以上,我们知道了如何传递单个HTML片段,但传递多个HTML片段也是非常有必要的,如 ``Dialog``组件,
我们很可能会传递 ``dialog-header``, ``dialog-body`` 等等,那此时又应如何呢?
这个场景,我们可以借助 ``ng-transclude`` 的 ``slot`` 功能实现,
首先,是占位符的变化,如下:
```html
```
其次是组件配置的变化,因为有多个 ``transclude``,那么仅仅设置为 ``true``,就不太能满足需求了。
需要修改如下:
```javascript
transclude: {
header: '?panelHeader', // panelHeader表示内部标签,?表示是可选的
body: 'panelBody' // 没有问号,表示该节点必选
}
```
接下来,就应该是调用时的改变,调用如下:
```html
我是Panel Header(可选)
我是Panel Body(必须)
```
## 5、组件 ``require``
同 ``Directive`` 一样,组件也可以相互依赖,只需要在注册组件时,设置require属性即可,写法如下:
```javascript
require: {
componentCtrl: '^parentComponent'
}
```
## 6、小结
新增的 ``angular.component`` 就是这么一个东西,比起 ``directive`` 更加纯粹,更加强大,更加易用。
建议在后续使用中,多多尝试该方式。
================================================
FILE: AngularJS相关/Angular再回首(2)-那些容易忽略的Component细节.md
================================================
---
title: Angular再回首(2)-那些容易忽略的Component细节
date: 2017/02/21 14:47:10
---
## 0、前言
在 ``Angular 1.5.x`` 中,增加的组件方法,相当实用和易用。但也有许多小细节问题值得注意,
以下为本人在组件实践过程中遇到的问题,或者是需要注意的小细节。
## 1、问题/小细节(需要注意的点)
### 1.1、如何判断是否添加了可选的 ``transclude`` 元素?
在很多时候,我们会给一个组件设定多个 ``transclude``,可能其中有一部分是可选的,那如何判断可选的 ``transclude`` 被用户设置了值呢?
此时,我们可以依靠 ``$transclude`` 来进行判断:
```javascript
class XXXComponent{
constructor($transclude){
this.$transclude = $transclude;
}
$onInit(){
// 判断transclude是否存在
let transcludeName = 'xxx';
let hasXXX = this.$transclude.isSlotFilled(transcludeName);
}
}
XXXComponent.$inject = ['$transclude'];
```
### 1.2、如何监控绑定属性的变更?
属性绑定,分为一次性绑定(@)(也算是单向绑定),单向绑定(<),双向绑定(=)。
**# 监控单向绑定属性**
对于单向绑定的属性,可以通过生命周期钩子 ``$onChanges(changesObj)`` 来进行监控。
```javascript
class XXXController{
$onChanges(changesObj){
console.log(changesObj);
}
}
```
其中参数 ``changesObj`` 是所有变更属性的一个汇总,数据结构如下:
```json
changesObj = {
key1: { // 有变更的绑定属性
currentValue: any // 当前值 (变化后的值)
previousValue: any // 上一次的值 (变化前的值)
isFirstChange(): fn // 方法,用于判断是否是第一次变更。
}
}
```
**注意:``$onChanges`` 无法监控双向绑定属性,切记!**
**# 监控双向绑定**
由于 ``$onChanges`` 无法监控双向绑定属性,那么我们就必须另外想办法来进行监控,可以有以下几种方案:
*方案一:利用 ``$interval``*
既然是双向绑定,那么肯定变化是直接生效的,关键就在于我们无法监视到,这个时候我们可以利用 ``$interval`` 来实现定时监控。
```javascript
class XXXController{
constructor($interval){
this.$interval = $interval;
this.init();
}
init(){
let previousValue = null;
this.$interval(() => {
if(previousValue !== this.value){
previousValue = this.value;
console.log('value changed');
}
}, 200);
}
}
XXXController.$inject = ['$interval'];
angular.module('xxx').component('xxx', {
bindings: {
value: '='
},
controller: XXXController,
controllerAs: 'vm'
});
```
优点:
1. 易于理解
缺点:
1. 浪费资源
2. 需要自己书写逻辑
推荐指数: ☆
*方案二:利用 ``$scope.$watch(keyString)``*
组件也有独立的 ``$scope``,那么借助 ``$scope.$watch`` 也可以实现监听属性变化,代码如下:
```javascript
class XXXController{
constructor($scope){
this.$scope = $scope;
this.init();
}
init(){
this.$scope.$watch('vm.value', (newVal, oldVal) => {
console.log('value changed);
});
}
}
XXXController.$inject = ['$scope'];
angular.module('xxx').component('xxx', {
bindings: {
value: '=' // 双向绑定属性
},
controller: XXXController,
controllerAs: 'vm'
});
```
优点:
1. 使用简单
缺点:
1. 字符串形式的 ``$watch``,依赖 ``controllerAs``,不易理解
2. 实质仍然是定时器,只不过是使用的 ``angular`` 自身的 ``$diget`` 循环
推荐指数: ☆☆
*方案三:利用 ``$scope.$watch(fn)``*
``$scope.$watch`` 也接受函数类型的参数,相对于字符串形式,没有 ``controllerAs`` 的相关性,而且更灵活,代码如下:
```javascript
class XXXController{
constructor($scope){
this.$scope = $scope;
this.init();
}
init(){
this.$scope.$watch(() => this.value, (newVal, oldVal) => {
console.log('value changed);
});
}
}
XXXController.$inject = ['$scope'];
angular.module('xxx').component('xxx', {
bindings: {
value: '=' // 双向绑定属性
},
controller: XXXController,
controllerAs: 'vm'
});
```
优点:
1. 使用简单
缺点:
1. 实质仍然是定时器,只不过是使用的 ``angular`` 自身的 ``$diget`` 循环
推荐指数: ☆☆☆☆
*方案四:利用 ``getter & setter``*
因为我们使用了 ``ES6 Class``,那么 ``ES6`` 的 ``getter setter`` 特性,我们也是能够使用的,方式如下:
```javascript
class XXXController{
set value(val){
this._value = val;
console.log('value changed');
}
get value(){
return this._value;
}
}
XXXController.$inject = [];
angular.module('xxx').component('xxx', {
bindings: {
value: '=' // 双向绑定属性
},
controller: XXXController,
controllerAs: 'vm'
});
```
优点:
1. 没有额外的开销,性能高
缺点:
1. 使用相对较为复杂
推荐指数: ☆☆☆☆
================================================
FILE: AngularJS相关/Angular再回首(3)-我们来实现一个组件.md
================================================
---
title: Angular再回首(3)-我们来实现一个组件
date: 2017/02/21 14:47:10
---
## 0、前言
前两文写了 ``Component`` 的一些方面,但没有一个比较线性的串联关系,本文,就来从一个实例出发,来尝试概括一个组件的方方面面。
## 1、
## 2、组件实现
### 2.1、先整一个组件
```javascript
angular.module('app', [])
.component('finalComponent', {});
```
这个组件啥都不干,就提供了一个新的标签,显得毫无意义,但是我们可以从这里看到如何定义一个组件。
**注意:组件名称,请使用小驼峰命名法,在HTML中,请使用连字符+小写字母,这种实现是为了处理js和html大小写敏感的差异(js区分大小写,html不区分)**
**注意2:如果在组件标签中,嵌入有效的标签,是会显示出来的,如下:**
```html
Hello
```
会显示出大号的 “Hello”。
### 2.2、带模板的组件
接着,来实现一个有意义的组件,比如我要渲染一个特定的字符串,代码如下:
```javascript
angular.module('app', [])
.component('finalComponent', {
template: 'Hello World.
'
});
```
现在我们再使用:`ABC`,则会显示 "Hello World" 内容了。
**注意:当组件指定了模板属性后,其内部的标签,将不会生效(transclude除外,)**
### 2.3、复杂模板的组件
以上,我们已经实现了带模板的组件,可是我们的模板可能会比较复杂,这个时候直接写 `template` 就不太好用了,此时,我们会考虑把模板拆分到一个独立的 `.html` 文件中,代码如下:
```html
Hello World.
```
然后,使用 `templateUrl` 属性进行关联
```javascript
angular.module('app', [])
.component('finalComponent', {
templateUrl: '/app/template.html'
});
```
该代码可以达到 2.2 同样的效果,只是把模板内容拆分到独立文件中了。
**注意:模板路径可以是相对路径,也可以是绝对路径,需要注意路径的写法,否则会出现找不到模板**
**注意2:如果使用 `gulp` 构建,可以考虑使用 `gulp-angular-embed-templates` 将独立的模板文件,打包到组件中。**
### 2.4、组件属性绑定
之前实现的组件,感觉太死板了,我想改下文字,都不好实现(你非要用js强制操作dom,我拿你也没办法,不过后果自负),这个时候,我们迫切的需要能给组件传递参数。
`Angular` 组件中,有多个参数传递方式,如下:
* @ 单向绑定字符串(原值绑定) - 传什么就是什么,不做任何处理
* < 单向绑定变量(取scope的值绑定) - 传的值会先用 `$scope` 转换,把结果传递给组件
* = 双向绑定 - 组件内外变化都会通知另一方
#### 2.4.1 直接传递字符串参数
使用 `@` 进行单向字符串绑定
```javascript
angular.module('app', [])
.component('finalComponent', {
templateUrl: '/app/template.html'
bindings: {
name: '@'
}
});
```
```html
Hello {{$ctrl.name}}.
```
此时,将会显示“Hello Jay”,可以看到,设定的参数值会原样显示了。
**注意:在模板中,要使用变量,需要加$ctrl前缀,先这样用着,后面会提到**
#### 2.4.2 使用单向绑定变量
```javascript
class TestController{
constructor(){
this.name = 'Jay'
}
}
TestController.$inject = []; // 依赖
angular.module('app', [])
.component('finalComponent', {
templateUrl: '/app/template.html'
bindings: {
name: '<'
}
})
.controller('TestController', TestController);
```
```html
Hello {{$ctrl.name}}.
```
此时,也将会显示“Hello Jay”,可以看到,此时 `t.name` 会拿到 `$scope` 中进行解析。
**注意:推荐使用 `controller as` 写法**
#### 2.4.3 双向绑定
```javascript
class TestController{
constructor(){
this.name = 'Jay'
}
}
TestController.$inject = []; // 依赖
angular.module('app', [])
.component('finalComponent', {
templateUrl: '/app/template.html'
bindings: {
name: '='
}
})
.controller('TestController', TestController);
```
```html
Hello {{$ctrl.name}}.
{{t.name}}
```
此时,在文本框输入值之后,可以看到组件内外都会及时变更。
================================================
FILE: AngularJS相关/Angular开发Tips.md
================================================
---
title: Angular开发Tips
date: 2017/02/21 14:47:10
---
1、在使用$routeProvider的时候,需要让模块依赖ngRoute,否则会提示找不到服务,示例:
angular.module('module1', ['ngRoute'])
.config(['$routeProvider', function($routeProvider){
//do something...
}]);
2、在页面中需要绑定有风险的html的时候,可以使用 `ng-bind-html="html"(version>=1.3)`,如果遇到错误,控制器中可以使用`html = $sce.trustHtml(unsafeHtml)`。
3、 如何动态的向页面添加带指令的HTML?通入如下代码:
$compile(html)($scope);
4、如果阻止事件冒泡?示例如下:
//方式一,利用一个自定义指令实现
.directive('stopEventPropagation', function(){
return {
restrict: 'A',
link: function(scope, iElement, iAttrs){
//通过获取事件对象,来阻止调用
iElement.bind('click', function(e){
e.stopPropagation();
});
}
}
});
Click me
//方式二,直接引用$event对象
Click me
5、关于$route和$location的事件顺序,如下:
$routeChangeStart -> $locationChangeStart -> $locationChangeSuccess -> $routeChangeSuccess
6、有关select标签的使用,当options的来源是ajax时,那么如果指定选中项呢?如下:
//如上HTML代码,如果sysOptions来自ajax请求,而selectSystem又不是的话,往往会选中一个空值。
//可以使用如下方式避免:
```javascript
.controller('TestCtrl', ['$scope', '$http', function($scope, $http){
$http.get(url).success(function(data){
$scope.sysOptions = data;
//在异步回调函数中,对ng-model赋值。
$scope.selectSystem = 'Test';
});
}]);
```
7、在编写指令时,属性的匹配大小写需要注意:如果在html中使用 `showName="xx"`,那么在指令的iAttrs中,应该使用 `showname` 获取。如果要在指令中使用showName获取的话,那么必须在html中使用 `show-name="xx"`。
8、要生成安全链接时,需要修改配置,代码如下:
```javascript
需要将如下代码: ng-href="{{true: 'javascript:void(0);' : 'url'}}"
生成为: href="javascript:void(0);"
```
```javascript
.config(['$compileProvider', function($compileProvider){
$compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|file|javascript):/)
}]);
```
9、在ng-click等ng事件中,如果拿到事件源对象?如下:
```javascript
$scope.click = function($event){
var target = $event.target;
};
//注意,如果使用ng-click="click($event.target)",将会导致angular解析错误。
```
10、判断angular的模块是否存在,可以使用如下代码:
var isAngularModuleExists = function(moduleName){
try{
angular.module(moduleName)
}catch{
return false
}
return true;
};
11、在使用coffee编写使用provider方式编写服务时,当心写在最后的this.$get,coffee会将最后一句编译为return this.$get,而这刚好不符合provider的要求,所以应该在末尾手动加上return或者放置一个undefined在最后,放置编译出return this.$get这样的代码。
12、如果要动态控制是否启用非空验证,可以使用ng-required="true|false"指令。
13、当心ng-if指令,在使用ng-if指令时,会创建独立的作用域,如果要在$scope监视ng-if包含的变量,那么是无法成功的。如果一定要监视,可以考虑使用ng-show。
14、注意.value()与.constant的区别,前者只能注入和用于服务或者控制器中,后则可以被注入到配置(.config(['xx']))中。
================================================
FILE: AngularJS相关/Angular:指令、Controller数据共享.md
================================================
---
title: Angular:指令、Controller数据共享
date: 2015/3/13 11:24:39
---
## 1、Directive与Controller数据共享
在指令中,不仅仅需要指令配置信息,很多时候也需要获取$scope的相关数据。那么,如何在指令中拿到$scope的数据呢?
### 1.1、Directive和Controller使用同一个scope
Angular Demo
执行以上代码,页面显示Hi Jay,并在控制台打印
controller scope id = 2
directive scope id = 2
在指令中,默认会直接使用上级的scope,从控制台来看,先执行controller的scope,再执行directive的scope。因为id一致,所以是同一个scope。既然是同一个scope,那么共享数据自然就不是问题了。该方式,适合业务性质的directive,如果是公共的directive,不建议使用此方式,可能会导致scope杂乱。
### 1.2、在指令作用域中使用@,将当前属性作为字符串传递
Angular Demo
以上代码,主要修改了指令的scope,从输出来看,指令和controller各自是自己独有的作用域。
``scope = {name: '@'}``,等价于
link:function(scope, iElement, iAttrs){
scope.name = iAttrs.name;
}
Controller中的key的变化,会即时影响到Directive的变化,但是Directive的变化并不会反向影响到Controller,结果近似于单向绑定。
### 1.3、在指令的作用域中使用=,进行数据的双向绑定
Angular Demo
key = {{key}}
以上代码的变化在于,使用了scope: {name: '='},该代码将父作用域的属性和指令的属性进行双向绑定。所以指令中文本框的值的变化,将会同步影响controller中key的变化。
**注意:在使用指令的时候,html代码,并不是和示例1.1一致了,如果是双向绑定,那么应该使用
Angular Demo
key = {{key}}
点击指令生成的按钮,会执行controller的show方法,利用在scope: {showName: '&'},可以将父级作用域的方法绑定到指令中。
**注意,一定要注意属性命令,在html中书写showName,那么在iAttrs中对应showname,只有在html中书写show-name,在会在iAttrs中对应showName。**
## 2、在controller中,拿到directive的作用域
### 2.1、拿到scope的元素,调用isolateScope获取scope
Angular Demo
key = {{key}}
此代码中,利用$('#d1').isolateScope,拿到了该指令的scope,所以可以随时方式,该方式在多种指令中也有效。
**如果判断应该用isolateScope()还是scope()获取作用域?一个最简单的方式,用F12查看源码,找到该元素,然后查看class是ng-isolate-scope还是ng-scope**
## 3、 指令之间相互获取数据
### 3.1、通过directive依赖来共享数据
### 3.2、通过如2.1的方式获取数据
## 4、 其他Hacky的方式
1. 通过``$parent``访问父级作用域
2. 通过``$$prevSibling``访问该作用域的上一个兄弟作用域
3. 通过``$$nextSibling``访问该作用域的下一个兄弟作用域
4. 通过``$$childHead``访问儿子作用域的第一个
5. 通过``$$childTail``访问儿子作用域的最后一个
## 5、参考资料
1. [SHARING DATA BETWEEN CHILD AND PARENT DIRECTIVES AND SCOPES (IN ANGULARJS)](http://tech.blinemedical.com/sharing-data-between-child-and-parent-directives-and-scopes-in-angularjs/)
2. [directive和controller如何通信](http://www.cnblogs.com/bigdataZJ/p/AngularJS1.html)
================================================
FILE: AngularJS相关/[20140917]Angular:如何编写一个指令.md
================================================
---
title: Angular:如何编写一个指令
date: 2015/3/13 11:24:39
---
## Angular是什么?
AngularJS是一个用JavaScript编写的客户端MVC框架,它运行于Web浏览器,能够极大的帮助我们(开发者)编写模块化,单页面,Ajax风格的Web Applications。
PS:**AngularJS适合开发CRUD的SPA**
## Angular Directive是什么?
Angular Directive是构建在DOM元素(属性、标签名、注释和CSS类)上的标记,告诉AngularJS的HTML编译器($compile) 附加指定的行为到元素或者甚至变换这个元素和它的子集。
PS:**通过扩展HTML标签的方式提供可复用的web组件**
PS2:**指令的作用:提供语义化标签**
## 完整的Directive参数
var directiveModule=angular.module('Newkit.negHotkeys');
directiveModule.directive('negHotkeys',function(injectables){
var directiveDefineObject={
restrict:(string),
priority:(number),
template:(string),
templateUrl:(string),
replace:(bool),
transclude:(bool),
scope:(bool or object),
controller:(function),
require:(string),
link:(function)
compile:(function)
};
return directiveDefineObject;
});
##### 参数说明
- restrict:(string)指令的使用方式,可选值:元素[E]、属性[A]、样式类[C]、注释[M],并且可以采用组合的方式使用,示例:'AE'
- priority:(number)优先级,描述了多个指令时,指令的执行顺序。数字越大,优先级越高,默认值0。
- template:(string)文本模板
- templateUrl:(string)模板文件地址,如果设置了该属性,那么将会忽略template的配置。
- replace:(bool)指示是否替换元素,如果设置为true,则替换,否则(设置为false或不设置)追加到元素内部
- transclude:(bool)是否将指令的子节点移动到一个新模板内部,如果在模板中指定了ng-transclude,那么会将元素原本的内容移动到新的模板内部,具体看示例二
- scope:(bool or object)设置作用域,如果设置为false[默认值],则使用现有的作用域;如果设置为true,则创建一个新的作用域。设置为object时,设定作用域绑定策略
- controller:创建一个控制器,它会暴露一个API,实现在多个指令之间进行通信
- require:设置依赖的指令。不设置,则无依赖,示例:'?\^testDirective',其中,?表示该指令可选,^表示需要遍历DOM树查找指令
- link:链接函数,function(scope,iElement,iAttrs){},其中的i表示实例,所以在link中接收的是实例元素和实例元素属性
- compile:编译函数,function(tElement,tAttrs,transclude){},其中t表示模板,所以在compile中使用的是模板元素。在编译过程中,可以返回preLink(链接前),postLink(链接后)函数,compile函数只会调用一次,而link函数的调用次数等于things中的元素个数,所以多余共同的东西,那么最好放在compile函数中实现(出于效率考虑) **注:设置了compile属性之后,指令将忽略link属性,同时compile函数的返回值将作为link函数使用**
## Angular Directive 示例
### 示例一(简单指令)
```javascript
angular.module('app').directive('demo1',function(){
return {
restrict:'AE',/*标签或者属性*/
template:'Hello
',
replace:true
}
});
...
Hello World!
Hello World!
```
### 操作步骤分析
1. 定义一个模块app,并创建了一个指令demo1。
2. 设定该指令可采用元素的标签和属性申明,并设置了一个文本模板,同时设置了replace=true。
3. 在html中,采用标签如*<demo1>
angular.module('app.directive.demo2',[]).directive('demo2',function(){
return {
restrict:'E',
template:'',
transclude:true
}
});
原始的内容,
还会在这里。
This is Demo2
原始的内容,
还会在这里。
```
#### 分析
1. 通过在指令中设置transclude=true,同时在template中包含*<div ng-transclude>*,实现了将元素内部元素移动到了ng-transclude元素内部,并创建了新的作用域
### 示例三(link与compile)
```javascript
/*指令*/
angular.module('app.directive.demo3',[]).directive('demo3Link',function(){
return {
restrict:'E',
template:'This is Demo3Link
',
link:function(scope,iElement,iAttrs){
iElement.html('good link
');
}
}
}).directive('demo3Compile',function(){
return {
restrict:'E',
template:'This is Demo3Compile
',
compile:function(tElement,tAttrs,transclude){
tElement.html('test demo3 compile
');
return function(scope,iElement,iAttrs){
//iElement.html('good compile
');
};
}
}
});
/*使用*/
/*页面生成的HTML*/
good link
good link
test demo3 compile
```
#### 分析
compile用于在编译期处理模板内容,并能设置preLink和postLink函数,此时将不能设置link函数,代码如下:
```
compile:function(tElement,tAttrs,transclude){
tElement.html('test demo3 compile
');
return {
pre:function preLink(scope,iElement,iAttrs){
console.log('preLink');
},
post:function postLink(scope,iElement,iAttrs){
console.log('postLink');
}
};
}
```
link用于对替换后的元素进行操作,如果参数是iElement。
### 示例四(简单加法计算器)
```
/*代码在这里*/
angular.module('app.directive.demo4',[]).directive('demo4',function(){
return {
restrict:'E',
template:'',
replace:true,
link:function(scope,iElement,iAttrs){
scope.num1=0;
scope.num2=0;
scope.total=0;
scope.$watch('num1+num2',function(to,from){
scope.total=+scope.num1+(+scope.num2)
})
}
}
});
/*HTML在这里*/
、
/*效果请自行测试*/
```
#### 分析
可以利用指令完成特定的功能了。
### 示例五(negHotkeys指令代码)
[代码在这里](http://trgit/backend_framework/web_platform/blob/master/src/framework/js/directives/custom/negHotKeys.coffee)
## 总结
1. 指令依附于模块
2. 一个模块可以有多个指令,但是需要采用示例三的写法
3. 指令可以语义化标签,实现html组件化
4. 其他...
================================================
FILE: AngularJS相关/用AngularJS开发Web应用程序.md
================================================
---
title: 用AngularJS开发Web应用程序
date: 2015/3/13 11:24:39
---
##章节一:Angular 禅道##
###本章生词###
serve = 提供
take a brief = 先简要的
introduction = 介绍
concept = 概念
a lot of = 许多
material = 材料
cover = 概括
painless = 无痛的
plenty = 丰富、大量
unique = 独特的
doubt = 疑问
shape = 塑造
explain = 解释
expect = 预计
get familiar with = 熟悉
become aware = 察觉
sophisticated = 复杂
dependency injection = 依赖注入nuance
nuance = 细微之处
general = 一般
purpose = 目的
shines = 耀眼
recent = 最近
addition = 此外
mostly = 主要的
due = 由于
innovative = 创新
yet = 但
attract = 吸引
ease = 缓解
solid = 扎实
engineering = 工程
practice = 实践
indeed = 的确
respects = 方面
explicit = 明确的
capable = 能
figure out = 弄清楚
interesting = 有趣的
interpret = 解析
mistaken = 错误,谬
several = 几个,数个
typically = 通常
treasure = 宝藏
testability = 可测试性
built-in support 内置支持
thoroughly = 彻底的
relatively = 比较的
actor = 演员
personal = 个人的
turned out = 横空出世
---
这个章节介绍了AngularJS,包括框架和它背后的项目。首先,我们先简要的了解项目本身:谁become aware驱动了它,在哪儿可以找到源代码和文档,如何寻求帮助等等。
这个章节的大部分是介绍AngularJS框架,它的核心概念和编码模式。包含有许多概括(总结)性的材料,使得学习进程快速无障碍,同时也有丰富的代码示例。
AngularJS是一个独特的框架,毫无疑问的引领一个Web开发潮流。这也是为什么章节的最后部分解释了是什么让AngularJS如此特别,它和其它外部框架之间的差异和我们能在未来如何设想它。
这个章节包含了以下几个主题:
1. 如何用AngularJS书写一个简单的Hello World 程序。在做这个的过程中,你将了解到在哪儿找到框架源代码、文档以及社区。
2. 熟悉AngularJS应用程序的基本构造块:Templates、Directives、Scopes和Controllers。
3. 察觉AngularJS复杂的依赖注入系统以及它所有的细微之处。
4. 理解AngularJS与其他框架或库(特别是jQuery)之间的差异,是什么使得它如此特别。
###AngularJS简介###
AngularJS是一个用JavaScript编写的客户端MVC框架,它运行于Web浏览器,能够极大的帮助我们(开发者)编写模块化,单页面,Ajax风格的Web Applications。它是一个平常的框架,不过如果用于编写CRUD类型的web app,那么它将非常耀眼。
###熟悉框架###
AngularJS是最近的客户端mvc框架的例外,但是它吸引了许多注意力,主要是由于它创新的模板系统,减轻了开发,同时有很扎实的工程实践。的确,它的模板系统独特于许多方面:
1. 使用HTML作为模板语言
2. 不要求明确的DOM刷新,AngularJS 能跟踪用户操作、浏览器事件和模型变化,来选择何时和那个模板将被刷新
3. 它有非常有趣的和可扩展的组件子系统,它能教会浏览器如何解析新的HTML标签和属性
模板子系统可能是AngularJS中最常见的部分,但是不要错误的认为AngularJS是单页Web程序所需要的包含数个工具和常用服务的完整框架包。
AngularJS同样有一些隐藏的宝藏,依赖注入(DI=dependency injection)和可测试特性的强烈关注。DI的内置支持能够非常容易的访问从一个极小的、彻底的可测试服务创建的web app。
###项目发展路线###
AngularJS是客户端MVC框架中比较新的成员;它的1.0版本发布于2012年6月。实际上,这个框架作为谷歌雇员Misko Hevery的个人项目开始于2009年。最初的idea是如此的好,在写作本文的同时,这个项目已经被Google正式支持,并且有Google的完整团队全职维护这个框架。
AngularJS是托管在[GitHub](https://github.com/angular/angular.js)上的,基于MIT协议的开源项目
###社区###
================================================
FILE: AngularJS相关/详解angular之$q.md
================================================
---
title: 详解angular之$q
date: 2017/02/21 14:47:10
---
## 0、什么的Promise
Promise(承诺)是用于改善异步编程体验的一种编程模型,它提供了一些列的API的方法论,让你能更优雅的解决异步编程中出现的一些问题。
## 1、Promise的核心竞争力
在处理有依赖性的回调的时候,我们的代码是这样写的:
step1(function (value1) {
step2(value1, function(value2) {
step3(value2, function(value3) {
step4(value3, function(value4) {
// Do something with value4
});
});
});
});
这就是我们所谓的回调地狱。
如果用Promise的方式来实现,是怎么样呢?
step1().then(step2).then(step3).then(step4)
代码更简单逻辑也清晰,异步的回调嵌套变成了同步写法,孰优孰劣相信大家都一目了然。
## 2、Angular服务$q
在angular中,基于nodejs中流行的Q提供了一个简化版本的Q,对外的话提供一个service $q。
以下列举出angular中的$q提供的API
#### 1、Promise.then() 将回调变成链式调用,then可以接两个参数,successCallback, errorCallback,示例如下:
var deferred = $q.defer();
var promise = deferred.promise;
promise.then(successCallback, errorCallback);
#### 2、Promise.catch 捕获Promise异常,Promise.catch(errorCallback)等价于Promise.then(null, errorCallback)
#### 3、Promise.finally(callback, notifyCallback) promise结束后要做的事情和接收通知信息
#### 4、Deferred.resolve(val) 通知promise请求处理完毕,并将处理结果传给回调函数(successCallback),示例如下:
var deferred = $q.defer();
setTimeout(function(){
deferred.resolve('abc'); //会将abc传递给successCallback
}, 1000);
var promise = deferred.promise;
promise.then(successCallback, errorCallback);
#### 5、Deferred.reject(msg) 通知promise请求出现异常,将异常信息传给回调函数(errorCallback),示例如下:
var deferred = $q.defer();
setTimeout(function(){
deferred.reject('abc'); //会将abc传递给errorCallback
}, 1000);
var promise = deferred.promise;
promise.then(successCallback, errorCallback);
#### 6、Deferred.notify(value) 内部执行有变化时,对外发起通知。将会在Promise.finally中捕获到
var deferred = $q.defer();
setTimeout(function(){
deferred.reject('abc'); //会将abc传递给errorCallback
}, 1000);
var promise = deferred.promise;
promise.then(successCallback, errorCallback);
#### 7、$q.when(val/fn) 将任意对象/函数包装成promise,返回包装好的promise。
#### 8、$q.all(promises).then() 当所有的promise都成功解析后,流程才继续往下走。示例如下:
$q.all($http.get('xxx'), $http.post('xxx',{}))
.then(successCallback, errorCallback);
## 3、$q的使用
常规使用
//定义开关变量
var canSuccess = false;
//定义一个Promise
var buildPromise = ()=>{
var deferred = $q.defer();
setTimeout(()=>{
if(canSuccess){
deferred.resolve('promise执行成功!')
}else{
deferred.reject('promise执行失败!')
}
},5000);
return deferred.promise;
};
//使用它
var promise = buildPromise();
promise.then(()=>{
console.log('执行成功啦!');
}, ()=>{
console.log('执行失败了!');
})
使用$q.all
var p1 = $http.get('xxxx');
var p2 = $http.get('xxxx2');
$q.all(p1, p2).then(() =>{
console.log('两次请求都成功了!');
});
## 4、$q源码分解
//Deferred定义
function Deferred() {
this.promise = new Promise();
//Necessary to support unbound execution :/
this.resolve = simpleBind(this, this.resolve);
this.reject = simpleBind(this, this.reject);
this.notify = simpleBind(this, this.notify);
}
//函数柯里化
function simpleBind(context, fn) {
return function(value) {
fn.call(context, value);
};
}
通过这种方式,就能将resolve,reject和promise关联起来了。既然我们最终要返回promise,那我们来看已看Promise的实现:
function Promise() {
this.$$state = { status: 0 };
}
extend(Promise.prototype, {
then: function(onFulfilled, onRejected, progressBack) {
if (isUndefined(onFulfilled) && isUndefined(onRejected) && isUndefined(progressBack)) {
return this;
}
var result = new Deferred();
this.$$state.pending = this.$$state.pending || [];
this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]);
if (this.$$state.status > 0) scheduleProcessQueue(this.$$state);
return result.promise;
},
"catch": function(callback) {
return this.then(null, callback);
},
"finally": function(callback, progressBack) {
return this.then(function(value) {
return handleCallback(value, true, callback);
}, function(error) {
return handleCallback(error, false, callback);
}, progressBack);
}
});
从这里很明显可以看出,catch就是一个语法糖,调用的还是then。finally也是一个语法糖,就是不关成功,还是失败,都会调用callback。那这个时候,我们主要关注的方法就放到then这个方法的实现上。
为了实现链式调用,在then方法内部,又实例化了Deferred对象,并返回Defferrd.promise。
接下来就来看处理过程:
this.$$state.pending = this.$$state.pending || [];
this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]);
if (this.$$state.status > 0) scheduleProcessQueue(this.$$state);
extend(Deferred.prototype, {
resolve: function(val) {
if (this.promise.$$state.status) return;
if (val === this.promise) {
this.$$reject($qMinErr(
'qcycle',
"Expected promise to be resolved with value other than itself '{0}'",
val));
} else {
this.$$resolve(val);
}
},
$$resolve: function(val) {
var then, fns;
fns = callOnce(this, this.$$resolve, this.$$reject);
try {
if ((isObject(val) || isFunction(val))) then = val && val.then;
if (isFunction(then)) {
this.promise.$$state.status = -1;
then.call(val, fns[0], fns[1], this.notify);
} else {
this.promise.$$state.value = val;
this.promise.$$state.status = 1;
scheduleProcessQueue(this.promise.$$state);
}
} catch (e) {
fns[1](e);
exceptionHandler(e);
}
},
reject: function(reason) {
if (this.promise.$$state.status) return;
this.$$reject(reason);
},
$$reject: function(reason) {
this.promise.$$state.value = reason;
this.promise.$$state.status = 2;
scheduleProcessQueue(this.promise.$$state);
},
notify: function(progress) {
var callbacks = this.promise.$$state.pending;
if ((this.promise.$$state.status <= 0) && callbacks && callbacks.length) {
nextTick(function() {
var callback, result;
for (var i = 0, ii = callbacks.length; i < ii; i++) {
result = callbacks[i][0];
callback = callbacks[i][3];
try {
result.notify(isFunction(callback) ? callback(progress) : progress);
} catch (e) {
exceptionHandler(e);
}
}
});
}
}
});
在调用then的时候,就将锅中回调写到$$state的pending数组中,让defferred.resolve的时候就会调用Deferred的内部方法,调用我们传递的回调函数。
**源码分解实在是说不明白,后期再发一篇如何实现一个简易的Promise,希望能更简洁易懂**
## 5、 了解更多
[JavaScript Promise迷你书(中文版)](http://liubin.github.io/promises-book/)
[Angular $q api](https://docs.angularjs.org/api/ng/service/$q)
================================================
FILE: Angular系列/01_Angular2初体验.md
================================================
---
title: 01_Angular2初体验
date: 2017/02/21 14:47:10
---
## 0、关于Angular2
Angualr2是前端最流行的MV*框架AngularJS的革命性更新版本,官网:[https://angular.io/](https://angular.io/),号称一个框架统一移动版和桌面。
## 1、背景
将AngularJS升级为Angular2,是大势所趋。在之前,我们就必须要对Angular2有足够的了解。所以这一系列文章,希望从各个点将angular2分而破之。
另外,由于Angular2当前处于Beta阶段,所以代码的时效性不高。所以每篇都会注明相关版本。
Angular2推荐的开发语言是TypeScript [http://www.typescriptlang.org/](http://www.typescriptlang.org/),所以我们这一系列文章也使用TypeScript开发(实际是使用JavaScript,半天没弄成功,丧气ing...)。
不要害怕TypeScript,因为TypeScript是ES6的超集,我们完全可以使用ES6的方式来编写TypeScript代码。对我们来说,仅仅是文件名后缀变化了。
## 2、Angular2 Hello-World
Angular2并不仅仅只有一个JS文件,要想成功运行Angular2,需要包含如下内容:
1. systemjs --模块加载器
2. Rxjs --对Js的扩展,至今不知道它是做什么的
3. angular2
如果要支持IE,那么还需要
1. es6-shim
2. systemjs 中的system-polyfills.js文件
3. angular2中的shims_for_IE.js文件
接着就直接创建项目吧,结构如下:
```
components/
hello_world.html
hello_world.ts
bootstrap.ts
index.html
package.json
```
首先第一步,我们要通过npm安装我们的依赖项:
``npm install angular2 rxjs systemjs es6-shim typescript --save``
接着,实现我们的``index.html``内容:
```html
Angular2 Hello World
```
然后是我们的``hello_world.html``和``hello_world.ts``,内容如下:
```html
Hello Angular2
My name is:
Angular2: Hello, {{username}}
```
```javascript
import {Component} from 'angular2/core';
@Component({
selector: 'hello_world', //此处指明了组件的标记,我们就可以使用来使用这个组件了。
templateUrl: 'components/hello_world.html'
})
//export的意思是导出这个组件,在使用的地方,就可以使用import {xx} from 'xxx'来获取到了。
export class HelloWorldComponent{
constructor(){
}
}
```
最后,是我们的``bootstrap.ts``入口JS:
```javascript
import {bootstrap} from 'angular2/platform/browser';
import {HelloWorldComponent} from 'components/hello_world';
bootstrap(HelloWorldComponent);
```
通过``anywhere``启动静态服务器,就可以看到我们的页面了。
**But,理想很丰满,现实很骨感,为嘛不兼容IE11???**
错误提示如下:
```
"'Symbol' is undefined"
```
**坑你没商量*!最终发现是Rx的版本必须用angular2提供的那个版本**
地址是:[https://code.angularjs.org/2.0.0-beta.12/Rx.js](https://code.angularjs.org/2.0.0-beta.12/Rx.js)
所以把Rx.js文件替换下,就可以在IE11中跑起来了。
**另外,经测试,Angular2可兼容IE9及以上版本。**
## 3、结尾
[Demo源码](https://github.com/hstarorg/HstarDemoProject/tree/master/angular2_demo/04)
================================================
FILE: Angular系列/02_Angular2组件生命周期.md
================================================
---
title: 02_Angular2组件生命周期
date: 2017/02/21 14:47:10
---
## 0、Angular2 组件
Angular1并不是围绕组件的概念来实现的。所以,我们需要controller、$scope,同时也需要封装自定义指令。
在Angular2中,把之前的这些东西都丢弃了,使用了一种更面向对象的组件模型。
一个组件控制着我们称之为View的显示部分。组件同时也是自描述的。
**在Angular2中,指令也是存在的,组件只是指令的一种。**
## 1、定义一个组件
最基本的组件只需要提供一个selector和template就足够了。代码如下:
```javascript
import {Component} from 'angular2/core';
@Component({
selector: 'basic-info',
template: 'Basic Info
'
})
export class AboutComponent{
constructor() {
}
}
```
要实现输入和输出呢?
```javascript
import {Component, Input, Output, EventEmitter} from 'angular2/core';
@Component({
selector: 'basic-info',
template: `
Basic Info, {{abc}}
`
})
export class BasicInfo{
@Input('test') set value(val){
this.abc = val;
this.callback.next([val]);
}
@Output('callback') callback = new EventEmitter();
constructor(){
this.abc = 'aaaa';
}
}
```
如何使用?
```html
```
## 2、组件生命周期
Angular2会管理组件的整个生命周期,包括组件的创建、渲染、子组件的创建和渲染、数据绑定属性变化时的校验、从DOM移除之前的销毁等等。
那如果我们想在某个状态时,进行一些操作应该怎么办呢?Angular2提供了组件生命周期的钩子,供我们在这些时间点添加自定义的操作。
在``angular2/core``中提供了多个Lifecycle Hook接口,我们可以实现一个或多个接口,来设置自定义操作。每一个接口,都会有一个钩子方法,钩子方法的名称是接口的名称加上前缀ng。如OnInit的钩子方法如下:
```javascript
import {Component, Input, Output, EventEmitter} from 'angular2/core';
@Component({
selector: 'basic-info',
template: `
Basic Info, {{abc}}
`
})
export class BasicInfo{
@Input('test') set value(val){
this.abc = val;
this.callback.next([val]);
}
@Output('callback') callback = new EventEmitter();
constructor(){
this.abc = 'aaaa';
}
// 组件Init时创建
ngOnInit(){
console.log('basic info init.');
}
}
```
**生命周期钩子(组件和指令都有的)**
1. ngOnInit //组件初始化,在Angular初始化数据绑定输入属性之后
2. ngOnChanges //
3. ngDoCheck
4. ngOnDestroy
**生命周期钩子(组件特有的)**
1. ngAfterContentInit // Angular将外部内容放入视图后
2. ngAfterContentChecked // 在Angular检测放到视图内的外部内容的绑定后
3. ngAfterViewInit // Angular创建视图之后
4. ngAfterViewChecked //Angular检测了组件视图的绑定之后
**执行顺序**
1. ngOnChanges //绑定属性变化时
2. ngOnInit //在第一次ngOnChanges之后,初始化时
3. ngDoCheck //每次Angular变化检测时
4. ngAfterContentInit //组件内容初始化之后
5. ngAfterContentChecked //组件内容变化后
6. ngAfterViewInit //初始化组件视图和子视图之后
7. ngAfterViewChecked //在数组视图和子视图检查之后
8. ngOnDestroy
我们将组件设定上钩子函数如下:
```javascript
import {Component, Input, Output, EventEmitter} from 'angular2/core';
@Component({
selector: 'basic-info',
template: `
Basic Info, {{abc}}
`
})
export class BasicInfo{
@Input('test') set value(val){
this.abc = val;
this.callback.next([val]);
}
@Output('callback') callback = new EventEmitter();
constructor(){
this.abc = 'aaaa';
}
ngOnInit(){
console.log('basic info init.');
}
ngDoCheck(){
console.log('basic info do check.');
}
ngOnChanges(){
console.log('basic info changes.');
}
ngOnDestroy(){
console.log('basic info destroy');
}
ngAfterContentInit(){
console.log('basic info after content init');
}
ngAfterContentChecked(){
console.log('basic info after content checked');
}
ngAfterViewInit(){
console.log('basic info after view init');
}
ngAfterViewChecked(){
console.log('basic info after view checked');
}
}
```
控制台打印的结果是:
```html
basic info changes.
test.component.js:23 basic info init.
test.component.js:26 basic info do check.
test.component.js:35 basic info after content init
test.component.js:38 basic info after content checked
test.component.js:41 basic info after view init
test.component.js:44 basic info after view checked
test.component.js:26 basic info do check.
test.component.js:38 basic info after content checked
test.component.js:44 basic info after view checked
test.component.js:26 basic info do check.
test.component.js:38 basic info after content checked
test.component.js:44 basic info after view checked
test.component.js:26 basic info do check.
test.component.js:38 basic info after content checked
test.component.js:44 basic info after view checked
```
================================================
FILE: Angular系列/03_Angular2的那些Decorator.md
================================================
---
title: 03_Angular2的那些Decorator
date: 2017/02/21 14:47:10
---
## 0、Decorator
``Decorator`` 是ECMAScript中建议的标准,使得我们可以在设计时对类和属性进行注解和修改。
## 1、Angular2的Decorator
在Angular2的早期版本(使用AtScript)中,我们是使用Annotation(注解),它以一个声明的方式将元数据添加到代码中。
在后来迁移到TypeScript的时候,我们可以使用 Decorator 和 Annotation 。作为使用者来说,使用 Decorator 和 Annotation 几乎是一样的,唯一的区别是我们没有去控制 Annotation 如何将元数据添加到我们的代码中,而 Decorator 是对 这些 Annotation 的最终实现。
从长远看,我们更应该多关注 Decorator ,因为它才是真正的标准建议。
## 2、Angular2的那些Decorator
### 2.1、In angular2/core
#### 2.1.1、Component
``Component`` 用于声明可重用的UI构建模块(组件),每个 Angular component都要求有一个 ``@Component`` 注解,它指定了组件何时被实例化,哪些属性和 hostListeners 被绑定。
当组件实现(implements)了一些生命周期钩子(lifecycle-hooks),那么将在特定的时间点访问这些钩子的回调函数。
**如何使用**
```javascript
@Component({
selector: 'demo', // 配置选择器
inputs: [],
outputs: [],
properties: [],
events: [],
host: {},
providers: [], // 设定所依赖的Providers(ng1中的service,provider,factory)
exportAs: '',
moduleId: '', //设定模块ID
viewProviders: [],
queries: {},
//changeDetection
templateUrl : '', // 指定模板文件URL,和template冲突
template: 'Hello {{name}}!', //指定模板内容,和templateUrl冲突
styleUrls: [], // 设定组件依赖的样式表文件
styles : [], //设定组件依赖的样式
directives: [], //设定所依赖的Directives(ng1中的directives)
pipes: [] //设定所依赖的Pipes(ng1中的filter)
//encapsulation
})
export class Demo {
private name: string = 'World';
}
```
**注:从继承关系来看,Component extends Directive。**
#### 2.1.2、Directive
``Directive`` 允许你在DOM元素上附加行为。如果指令带有内嵌视图,那么就成为了组件。
指令同样也有生命周期钩子。使用方式和 Component 雷同。
指令允许多种注入方式来实例化:
1、无注入 -- 该指令没有外部依赖
```javascript
// 空构造,无注入
@Directive({ selector: '[my-directive]' })
class MyDirective {
constructor() {
}
}
```
2、组件级别的注入 -- 该指令依赖一些外部服务
```javascript
import {User} from 'xxx';
@Directive({ selector: '[my-directive]' })
class MyDirective {
constructor(user: User) { //依赖外部服务
}
}
```
3、注入当前元素的其它指令 --该指令依赖当前元素上的其他指令,搭配其他指令一起使用
```javascript
import {User} from 'xxx';
@Directive({ selector: '[my-directive]' })
class MyDirective {
constructor(depDirective: DepDirective) { //依赖当前元素上的其他指令
}
}
```
```html
```
4、注入当前元素、父元素、更上层的父元素上的指令 --该指令依赖上层元素的指令
```javascript
import {User} from 'xxx';
@Directive({ selector: '[my-directive]' })
class MyDirective {
//要使用 @Host()
constructor(@Host() depDirective: DepDirective) { //可以依赖父辈元素上的指令
}
}
```
```html
```
5、注入直接子集集合元素的的指令 --该指令依赖直接子元素的指令
```javascript
import {User} from 'xxx';
@Directive({ selector: '[my-directive]' })
class MyDirective {
//使用 @Query ,依赖直接子元素上的指令
constructor(@Query(DepDirective) depDirective: QueryList) {
}
}
```
```html
```
6、注入后代集合元素的指令 --该指令依赖后代元素的指令
```javascript
import {User} from 'xxx';
@Directive({ selector: '[my-directive]' })
class MyDirective {
//使用 @Query ,依赖直接子元素上的指令
constructor(@Query(DepDirective, {descendants: true}) depDirective: QueryList) {
}
}
```
```html
```
7、可选注入 --该指令的依赖是可选的。
```
@Directive({ selector: '[my-directive]' })
class MyDirective {
// 使用 @Optional 标记,依赖是可选的。
constructor(@Optional() depDirective:DepDirective) {
}
}
```
**注:以上多种注入方式也适用于 Component 。**
#### 2.1.3、Injectable
``Injectable`` 允许使用注入。在编写组件/指令时,如果有注入,那么就需要将指令/组件标记为可注入的。
#### 2.1.4、Pipe
``Pipe`` 允许我们定义管道方法,实现ng1中filter类似的功能。
如何编写一个Pipe?
```javascript
@Pipe({name: 'lowercase'})
class Lowercase {
transform(v: string, args: any[]) { return v.toLowerCase(); }
}
```
### 2.2、In angular2/router
#### 2.2.1、CanActivate
``CanActivate`` 允许我们在使用路由时,检查组件的权限,来确定是否可以使用。
```javascript
@Component({selector: 'control-panel-cmp', template: `Settings: ...
`})
@CanActivate(checkIfWeHavePermission)
class ControlPanelCmp {
}
```
#### 2.2.2、RouteConfig
``RouteConfig`` 用于我们配置路由。
使用如下:
```
@Component({
selector: 'dojo-app',
moduleId: module.id,
templateUrl: 'app.html',
styleUrls: ['app.css'],
directives: [ROUTER_DIRECTIVES, HeaderComponent]
})
@RouteConfig([
{path: '/', name: 'Home', component: HomeComponent},
{path: '/about', name: 'About', component: AboutComponent}
])
export class AppComponent{
constructor() {
}
}
```
## 3、参考
1. [https://angular.io/docs/ts/latest/api/index.html#!?apiType=Decorator](https://angular.io/docs/ts/latest/api/index.html#!?apiType=Decorator)
2. [https://angular.io/docs/ts/latest/api/index.html#!?apiFilter=metadata](https://angular.io/docs/ts/latest/api/index.html#!?apiFilter=metadata)
================================================
FILE: Angular系列/04_Angular2指令简析.md
================================================
---
title: 04_Angular2指令简析
date: 2017/02/21 14:47:10
---
## 0、Angular2指令
在Angular1中,就已经有了指令的概念。Angular1中的指令用于实现可复用UI部件,也用于操作dom元素。
那么在Angular2中的指令是一样的东西么?
Angular2中有组件的概念,指令这个东西就变得更加纯粹。
Angular2的指令有三种:
- 组件
- 属性指令
- 结构指令
组件是有模板的指令,是指令的中一个另类,因为它使用@Component来装饰,而不是@Directive。
属性指令用于改变现有元素的展现和行为,使用的时候它们看起来像是正常的HTML属性,所以称之为属性指令。如ngModel指令。
结构指令通过添加、删除和替换DOM树中的元素来改变布局,由于可以更改DOM结构,所以称之为结构指令。如ngIf,ngSwitch。
由于Angular2的API好不够稳定,书写该文时,采用的是Angular2 rc1(@angular rc.1)版本,其他版本请自行测试。
## 1、属性指令
接着,我们就一步步来实现一个属性指令 dynamicColor 。
首先,我们需要创建一个ts文件,然后把指令的骨架搭建起来。
```typescript
import {Directive} from '@angular/core';
@Directive({
selector: '[dynamicColor]'
})
export class DynamicColorDirective{
constructor(){
}
}
```
以上代码中,我们创建了一个dynamicColor指令。
接下来,我们来实现具体的功能,可以设置元素的背景色和前景色,并能实现事件通知。
要实现动态背景色和前景色,那我们需要额外附加两个属性bgColor和color。
要想在指令中获取这两个属性值,那么我们可以通过@Input方式或者是inputs属性,代码如下:
```typescript
import {Directive, Input} from '@angular/core';
@Directive({
selector: '[dynamicColor]'
})
export class DynamicColorDirective{
@Input()
private bgColor: string;
@Input()
private color: string;
constructor(){
}
}
```
或者是:
```typescript
import {Directive, Input} from '@angular/core';
@Directive({
selector: '[dynamicColor]',
//注意,在之前的版本中,使用properties属性,而且,当前还可以使用。
inputs: [
'bgColor: bgColor', //字符串以冒号隔开,前者是DynamicColorDirective的属性,后者的html元素的属性
'color: color'
]
})
export class DynamicColorDirective{
// @Input()
private bgColor: string;
// @Input()
private color: string;
constructor(){
}
}
```
那么html中又应该如何传递值给指令呢?
```html
Hi!
```
**注意:在html元素的属性上,我们可以有两种写法。一种是直接书写属性,此时会把属性值原样传递给指令。第二种是使用[属性],此时属性值应该是表达式(可以使用变量,判断等语句),传递给指令的是表达式的结果。**
我们又如何在后端查看这两个值呢?
直接在constructor中console?明确的说是不行的,因为constructor的代码会先于绑定执行。
这个时候,我们就需要借助指令的生命周期钩子。
指令的生命周期钩子有如下几个:
1. ngOnInit --初始化时
2. ngOnChanges -- 属性绑定之时(会有一次inputs属性绑定先于初始化)
3. ngDoCheck -- 执行属性检查时
4. ngOnDestroy -- 指令释放时
了解了生命周期钩子,我们就可以通过ngOnInit来查看绑定好的属性值了。
```typescript
import {Directive, Input, OnInit} from '@angular/core';
@Directive({
selector: '[dynamicColor]',
inputs: [
'bgColor: bgColor', //字符串以冒号隔开,前者是DynamicColorDirective的属性,后者的html元素的属性
'color: color'
]
})
export class DynamicColorDirective implements OnInit{
// @Input()
private bgColor: string;
// @Input()
private color: string;
constructor(){
}
ngOnInit(){
console.log('bgColor', this.bgColor);
console.log('color', this.color);
}
}
```
接下来,我们需要设置元素的background color和color样式,那么我们必须要拿到这个而元素的引用, 并在初始化之后进行绑定。
```typescript
import {Directive, Input, OnInit, ElementRef} from '@angular/core';
@Directive({
selector: '[dynamicColor]',
inputs: [
'bgColor: bgColor', //字符串以冒号隔开,前者是DynamicColorDirective的属性,后者的html元素的属性
'color: color'
]
})
export class DynamicColorDirective implements OnInit{
private nativeElement: any;
// @Input()
private bgColor: string;
// @Input()
private color: string;
constructor(el: ElementRef){
this.nativeElement = el.nativeElement;
}
private _setElementStyle(): void{
this.nativeElement.style.backgroundColor = this.bgColor;
this.nativeElement.style.color = this.color;
}
ngOnInit(){
console.log('bgColor', this.bgColor);
console.log('color', this.color);
this._setElementStyle();
}
}
```
当从元素上绑定的属性变化时,又应该从哪里获取到变更呢?这就需要借助生命周期里面的OnChanges函数,代码如下:
```typescript
import {Directive, Input, ElementRef, OnInit, OnChanges} from '@angular/core';
@Directive({
selector: '[dynamicColor]',
inputs: [
'bgColor: bgColor', //字符串以冒号隔开,前者是DynamicColorDirective的属性,后者的html元素的属性
'color: color'
]
})
export class DynamicColorDirective implements OnInit, OnChanges{
private nativeElement: any;
// @Input()
private bgColor: string;
// @Input()
private color: string;
constructor(el: ElementRef){
this.nativeElement = el.nativeElement;
}
private _setElementStyle(): void{
this.nativeElement.style.backgroundColor = this.bgColor;
this.nativeElement.style.color = this.color;
}
ngOnInit(){
console.log('bgColor', this.bgColor);
console.log('color', this.color);
this._setElementStyle();
}
ngOnChanges(){
console.log('bgColor-change', this.bgColor);
console.log('color-change', this.color);
this._setElementStyle();
}
}
```
由于每次变化都会触发OnChanges,那么为了提高性能,我们可以在这里加入一个节流函数。
```typescript
private _setElementStyle(): void {
clearTimeout(this.timeoutId);
this.timeoutId = setTimeout(() => {
this.nativeElement.style.backgroundColor = this.bgColor;
this.nativeElement.style.color = this.color;
}, 500);
}
```
有了节流函数,我们就不太确定到底执行了几次更新操作了。这个时候,我们可以加入事件通知。这就涉及到指令的@Output了。
```typescript
@Output()
private updated: EventEmitter = new EventEmitter();
```
也等同于:
```typescript
outputs: [
'updated: updated'
]
private updated: EventEmitter = new EventEmitter();
```
**注意:在之前的版本中,也可以用events属性来替代outputs,现在也还可以使用**
要对外发出通知,只需要使用如下代码:
```typescript
this.updated.emit('updated');
this.updated.next('updated2');
```
HTML标签使用时,代码如下:
```html
Hi!
```
至此,我们这个指令就已经完成了,所有代码如下:
```typescript
//dynamicColor.directive.ts
import {Directive, Input, Output, ElementRef, EventEmitter, OnInit, OnChanges} from '@angular/core';
@Directive({
selector: '[dynamicColor]',
inputs: [
'bgColor: bgColor', //字符串以冒号隔开,前者是DynamicColorDirective的属性,后者的html元素的属性
'color: color'
],
outputs: [
'updated: updated'
]
})
export class DynamicColorDirective implements OnInit, OnChanges {
private nativeElement: any;
private timeoutId: any;
// @Input()
private bgColor: string;
// @Input()
private color: string;
// @Output()
private updated: EventEmitter = new EventEmitter();
constructor(el: ElementRef) {
this.nativeElement = el.nativeElement;
}
private _setElementStyle(): void {
clearTimeout(this.timeoutId); //先清除已有的timeout
//保证只执行最后一次。
this.timeoutId = setTimeout(() => {
this.nativeElement.style.backgroundColor = this.bgColor;
this.nativeElement.style.color = this.color;
this.updated.emit('updated');
this.updated.next('updated2');
}, 500);
}
ngOnInit() {
console.log('bgColor', this.bgColor);
console.log('color', this.color);
this._setElementStyle();
}
ngOnChanges() {
console.log('bgColor-change', this.bgColor);
console.log('color-change', this.color);
this._setElementStyle();
}
}
```
```html
//test.html
Dynamic Color Directive
Hi!
```
```typescript
//test.component.ts
export class TestComponent{
private testBgColor: string = 'blue';
private testColor: string = 'red';
constructor(){
}
private test(data){
console.log('data', 'my', data);
}
private notify(data){
console.log('notify = ', data);
}
}
```
*思考一下?我们还有没有更简单的方式实现以上的效果呢?*
```typescript
import {Directive, EventEmitter} from '@angular/core';
@Directive({
selector: '[dynamicColor]',
inputs: [
'bgColor: bgColor', //字符串以冒号隔开,前者是DynamicColorDirective的属性,后者的html元素的属性
'color: color'
],
outputs: [
'updated: updated'
],
host: {
'[style.backgroundColor]': 'bgColor',
'[style.color]': 'color'
}
})
export class DynamicColorDirective {
private bgColor: string;
private color: string;
private updated: EventEmitter = new EventEmitter();
constructor() {
}
}
```
通过host直接在元素上添加绑定。
## 2、结构指令
结构指令帮助我们修改dom结构,我们就简单实现一个templateInclude指令。
```typescript
import {Directive, Input, Output, ElementRef, EventEmitter, OnChanges} from '@angular/core';
import {Http} from '@angular/http';
@Directive({
selector: '[templateInclude]'
})
export class TemplateIncludeDirective implements OnChanges {
private nativeElement: any;
@Input('templateInclude')
private templateUrl: string;
@Output()
private loaded: EventEmitter = new EventEmitter();
constructor(el: ElementRef, private http: Http) {
this.nativeElement = el.nativeElement;
}
private _setTemplate() {
this.http.get(this.templateUrl)
.subscribe(res => {
this.nativeElement.innerHTML = res.text();
this.loaded.next(`${this.templateUrl} loaded`);
});
}
ngOnChanges() {
console.log(this.templateUrl);
this._setTemplate();
}
}
```
## 3、总结
**1、指令的元数据有很多属性可以使用**
```typescript
class DirectiveMetadata {
selector : string //指令使用的标记(选择器)
inputs : string[] //输入参数绑定
properties : string[] //属性绑定(过期,请使用inputs)
outputs : string[] //输出参数绑定
events : string[] //事件绑定(过期,请使用outputs)
host : {[key: string]: string} //宿主元素属性设置
providers : any[] //服务绑定
bindings : any[] //服务绑定(过期,请使用providers)
exportAs : string //导出名称
queries : {[key: string]: any} //用于指令依赖关系
}
```
**2、在使用指令(不仅仅是指令)进行绑定的时候,[]表示输入属性,()表示输出属性和事件**
**3、尽量使用统一的做法,用装饰器优于在属性上做绑定**
**4、在编写指令(不仅限于指令)时,将class中内容按照特定顺序进行排列,推荐顺序如下(个人建议,仅供参考):**
1. 私有变量
2. 共有变量
3. @Input变量
4. @Output变量
5. 构造函数
6. 私有方法(建议下划线开头)
7. 公有方法
8. 生命周期钩子方法
================================================
FILE: Angular系列/05_Angular2组件简析.md
================================================
---
title: 05_Angular2组件简析
date: 2017/02/21 14:47:10
---
## 0、Angular2组件
**注:由于Angular2的API好不够稳定,书写该文时,采用的是Angular2 rc1(@angular rc.1)版本,其他版本请自行测试。**
在上篇中,我们已经讲到了指令,这篇呢,我们一起来看看Angular2组件是怎么一回事。
首先,组件也是指令,组件是一种有模板(内嵌视图)的特殊指令。
从元数据[指令源代码](https://github.com/angular/angular/blob/2.0.0-rc.1/modules/%40angular/core/src/metadata/directives.ts)中也可以看出组件与指令的关系:
```typescript
export class ComponentMetadata extends DirectiveMetadata {
}
```
相比 ``Directive``, ``Component`` 新增了一些属性,如下:
```typescript
{
changeDetection : ChangeDetectionStrategy; 定义变化检测类型
viewProviders: any[]; 用于在组件中注入特定的class。一般是实体类
moduleId: string; 定义主键的ID
templateUrl: string; 如ng1,外部模板地址
template: string; 如ng1,内嵌模板内容
styleUrls: string[]; 外部样式表文件
styles: string[]; 内嵌样式
directives: Array; 使用到的指令
pipes: Array; 使用到的管道
encapsulation: ViewEncapsulation 封装视图的类型
}
```
## 1、组件生命周期
既然组件也是指令,那么指令所拥有的四大阶段组件也同样拥有。
而且,由于组件带有视图,还多了几个和视图相关的生命周期阶段。如下:
1. ngAfterContentInit 组件内容渲染到页面之后触发
2. ngAfterContentChecked 检查组件内容绑定数据后触发
3. ngAfterViewInit 创建组件视图之后触发
4. ngAfterViewChecked 检查组件视图绑定数据后触发
它们的执行顺序也和以上顺序一致。
## 2、整一个组件试试?
接下来,我们就简单实现一个组件 ``TodoList`` 来实验一下以上的知识点。
首先,搭建好一个简单的架子,如下:
```typescript
import {Component} from '@angular/core';
@Component({
selector: 'todo-list'
})
export class TodoListComponent{
constructor(){
}
}
```
使用组件装饰器 ``Component`` 来定义一个组件,注意其中 ``selector`` 属性和 ``Directive`` 中的写法不一样了。
组件必须以标签的方式存在,所以 selector 属性值仅仅只需要写标签名就可以了,不再需要其他特别的符号了。
组件和指令最大的差别就在于模板,所以我们接下来添加上模板代码:
```typescript
import {Component} from '@angular/core';
@Component({
selector: 'todo-list',
template: `
`
})
export class TodoListComponent{
constructor(){
}
}
```
如上,一个简单的模板就搞好了。这里需要注意 ``templateUrl`` 和 ``template`` 是互斥的两个属性。一般来说只选择一个赋值,如果两者都存在,那么会采用 ``tempalte`` 的值。
模板有了,我们就来点业务逻辑:
先假设Todo有三个状态:
```typescript
enum TodoStatus {
Open,
Processing,
Closed
}
```
在来定义Todo的实体类:
```typescript
class Todo {
private name: string;
private description: string;
private status: TodoStatus;
constructor(name: string, status: TodoStatus, description?: string) {
this.name = name;
this.status = status;
this.description = description || '';
}
}
```
接着来实现一个组件:
```typescript
@Component({
selector: 'todo-list',
template: require('./todo-list.component.html')
})
export class TodoList {
private todos: Array;
private todo: { name: string, desc: string } = { name: '', desc: '' };
constructor() {
this.todos = [];
}
addTodo() {
this.todos.push(new Todo(this.todo.name, TodoStatus.Open, this.todo.desc));
}
}
```
这个时候,HTML页面内容如下:
```html
No todos.
-
{{todo.name}} - {{todo.description}}
Name:
Description:
```
功能做好了,我们得给它来点样式美化。
此时我们仅仅需要实现一点样式:
```css
.todo-list{
margin: 0;
padding: 0;
}
.todo-list li{
list-style: none;
border: 1px solid red;
}
```
然后在组件装饰器中申明就可以了:
```typescript
@Component({
selector: 'todo-list',
template: require('./todo-list.component.html'),
styles : [require('./todo-list.component.css')]
})
```
至此,一个简单的可以添加todo的todo-list就已经完成了。
**注意:注入@Input,@Output之类的和Directive都是一样的,此处就不再演示了。**
================================================
FILE: Angular系列/06_Angular2管道(Pipe)简析.md
================================================
---
title: 06_Angular2管道(Pipe)简析
date: 2017/02/21 14:47:10
---
## 0、Angular2 Pipe
**注:由于Angular2的API好不够稳定,书写该文时,采用的是Angular2 rc1(@angular rc.1)版本,其他版本请自行测试。**
对于 ``Pipe``,其实我们并不陌生。在angular1中,它被称之为 ``filter``。
``Pipe``用于对数据进行格式化处理,就好比管道,一个进一头出,中间过程就是管道的处理逻辑。
Angular2中的 ``Pipe`` 本质上是包含特定方法的类。
## 1、编写一个简单Pipe
``Pipe`` 相对于指令和组件来说,非常简单,我们仅仅需要编写一个类:
```typescript
class EmptyToZero{
}
```
实现特定的方法:
```typescript
exprt class EmptyToZero{
transform(v: any, args: any[]){
if(v === undefined || v === null || v === ''){
return 0;
}
return v;
}
}
```
使用 ``Pipe`` 装饰,请设定名称:
```typescript
import {Pipe} from '@angular/core';
@Pipe({ name: 'empty2zero' })
export class EmptyToZero {
transform(v: any, args: any[]) {
if (v === undefined || v === null || v === '') {
return 0;
}
return v;
}
}
```
如何使用?
```html
{{ value | empty2zero}}
```
当然前提是要在Component中申明要使用的Pipe:
```typescript
@Component({
selector: 'todo-list',
template: require('./todo-list.component.html'),
styles : [require('./todo-list.component.css')],
pipes:[EmptyToZero]
})
```
## 2、有状态的Pipe
在使用 ``Pipe`` 装饰器的时候,我们可以提供两个参数:
```typescript
@Pipe({
name: 'empty2zero', //string类型,必填项,指定pipe的名称
pure: true //boolean类型,可选项,默认为true,设定为true时,表示无状态管道。无论是输入或者是什么参数的改变都会触发重新计算结果。
})
```
有状态的 ``Pipe`` 可以收到一个Promise对象或者检测输入和自动订阅输入,最终返回一个可触发的值。
要使用有状态的管道,必须将pure属性设置为false。
比较典型的有状态异步管道如下:
```typescript
import {Pipe} from 'angular2/core';
@Pipe({
name: 'fetch',
pure: false
})
export class FetchJsonPipe {
private fetchedValue:any;
private fetchPromise:Promise;
transform(value:string, args:string[]):any {
if (!this.fetchPromise) {
this.fetchPromise = window.fetch(value)
.then((result:any) => result.json())
.then((json:any) => this.fetchedValue = json);
}
return this.fetchedValue;
}
}
```
================================================
FILE: Angular系列/07_Angular2使用路由.md
================================================
---
title: 07_Angular2使用路由
date: 2017/02/21 14:47:10
---
## 0、关于路由
此处所说的路由是指URL路由(也许叫URL Rewrite)。其实是把网址(URL)映射到相关Controller、Component的这个功能。
Angular2的路由其实也就是URL路由,在Angular2中,有两个模块提供了路由功能,``@angular/router-deprecated`` 和 ``@angular/router``。
``@angular/router-deprecated`` 从名称也可以看出,它是一个过时的模块(beta版本中它的名字是 ``angular2/router``,在rc版本被更名)。但疑惑的是,它一直存在于 ``@angular`` 包中。在这里,我主要使用 ``@angular/router`` 来实现路由功能。
## 1、使用Angular2路由
建议在根组件中配置路由。要使用路由,必须先依赖 ``ROUTER_PROVIDERS``;如果要使用路由指令,必须先依赖 ``ROUTER_DIRECTIVES``。
大概结构如下:
```typescript
//bootstrap.ts
import {provide} from '@angular/core';
import {bootstrap} from '@angular/platform-browser-dynamic';
import {LocationStrategy, HashLocationStrategy} from '@angular/common';
import {AppComponent} from './app/app.component';
bootstrap(AppComponent, [
provide(LocationStrategy, { useClass: HashLocationStrategy })
]);
```
```typescript
//app.component.ts
import {Component, provide} from '@angular/core';
import {Routes, ROUTER_DIRECTIVES, ROUTER_PROVIDERS} from '@angular/router';
import {HomeComponent} from './../home/home.component';
import {AboutComponent} from './../about/about.component';
@Component({
selector: 'demo-app',
template: `
Angular2 Router Test
Home
About
`,
directives: [ROUTER_DIRECTIVES],
providers: [ROUTER_PROVIDERS]
})
@Routes([
{ path: '/home', component: HomeComponent},
{ path: '/about', component: AboutComponent }
])
export class AppComponent {
constructor() {
console.log('app init');
}
}
```
通过 ``@Routes``,我们可以配置路由对应的组件。当然,要求这些组件必须已经存在。在配置路由节点的时候,我们仅仅需要提供 ``path`` 和 ``component`` 参数。
在模板中使用 ``[routerLink]`` 可以配置连接,它的值是一个数组,第一个元素是要导向的url,第二个参数是路由参数。
除了使用 ``[routerLink]`` 实现路由跳转,还可以使用特定服务来跳转,代码如下:
```typescript
this.router.navigate(['/about']);
this.router.navigateByUrl('/about');
```
当URL比较复杂,如/home/test/index时,使用navigate方式如下:
```typescript
this.router.navigate(['home', 'test' ,'index']);
```
## 2、路由参数
有时候,我们需要给路由传递一些参数,这个时候就需要在配置路由的时候,指定参数。使用 ``[routerLink]`` 的方式如下:
```html
About
```
那么如何获取这个参数呢?就需要在 ``AboutComponent`` 组件中通过 ``RouteSegment`` 来获取,代码如下:
```typescript
export class AboutComponent {
constructor(private routeSegment: RouteSegment) {
console.log('about init', 'params:', routeSegment.parameters);
}
}
```
## 3、嵌套路由(子路由)
一般来说,我们写一个中大型的应用程序,一个一级路由根本就不够使用。这个时候,就可以使用嵌套路由来把应用程序拆分成很多小的模块。
在这种情况下,就需要我们的子组件也需要使用 ``@Routes`` 来申明自己的路由体系。
```typescript
//about.component.ts
import {Component} from '@angular/core';
import {ROUTER_DIRECTIVES, Routes, RouteSegment} from '@angular/router';
import {AboutUserComponent} from './about-user.component';
import {AboutMeComponent} from './about-me.component';
@Component({
selector: 'demo-about',
template: `
About
Go to Home
Go to About User
Go to About Me
`,
directives: [ROUTER_DIRECTIVES]
})
@Routes([
{ path: '/', component: AboutUserComponent },
{ path: '/user/:id', component: AboutUserComponent },
{ path: '/me', component: AboutMeComponent }
])
export class AboutComponent {
private id: number = 1;
constructor(private routeSegment: RouteSegment) {
console.log('about init', 'params:', routeSegment.parameters);
}
}
```
在这段代码里,我们需要注意,``[routerLink]``的值,有些是 ``['/home']``, 也有 ``['./me]`` 这种,它们有什么区别呢?
其实直接 '/home' 是指从根路径开始计算,也就是跳转到父路由,真实路径就是 /home。如果是使用的 './me' 这种形式,那么是相对路径,所以点击这个链接,跳转到的实际上是 /about/me 这个地址。
## 4、生命周期钩子(拦截器)
在路由中,我们可以通过实现 ``CanDeactivate`` 来控制路由是否可以被解除;还可以通过实现 ``OnActivate`` 来控制路由激活后的操作。
当我们在 ``HomeComponent`` 中编写如下语句时:
```typescript
routerCanDeactivate(curTree: RouteTree, futureTree: RouteTree) {
console.log('abc');
return new Promise((resolve, reject) => {
resolve(false);
});
}
```
当进入Home页之后,我们就已经无法跳出了。
当我们在 ``HomeComponent`` 中编写如下语句时:
```typescript
routerOnActivate(currSegment: RouteSegment, prev: RouteSegment, currTree: RouteTree, prevTree: RouteTree){
console.log('succeed');
}
```
每一次跳转到Home组件,我们都能在控制台看到输出 succeed 。
**很遗憾的是,暂时没有发现有全局的路由钩子,这也就意味着,我们没法在一个地方控制所有的路由是否允许被解除。**
## 5、后记
以上就是Angular2路由的简单使用了。相关Demo,请点击 [这里](https://github.com/hstarorg/HstarDemoProject/tree/master/angular2_demo)
在Angular2中,还有一个路由是 ``@angular/router-deprecated``,它是之前的路由方式,由于已经被标注为过期,这里就不在说明,如果想了解一下,可以查看 [Demo](https://github.com/hstarorg/HstarDemoProject/tree/master/angular2_demo/angular2-router-deprecated-test)
至于更复杂的动态路由,动态加载组件等等,未完待续...
================================================
FILE: Angular系列/08_Angular2动态加载组件.md
================================================
---
title: 08_Angular2动态加载组件
date: 2017/02/21 14:47:10
---
## 0、为什么需要动态加载
在一个比较大的应用程序中,我们不可能将所有的业务逻辑一次性加载出来,比较浪费资源,因为单个用户一般用不到所有的功能,这个时候,就需要部分组件动态加载了。
## 1、Angular2如何动态加载组件
在Angular2,有一个服务是 ``DynamicComponentLoader``,我们就可以通过它来进行动态加载组件。
首先要使用它的话,我们必须要在providers中指定它。
另外,它还有一些必须的依赖,是 ``injector``,当引入了这些元素之后,我们就可以实现一个加载组件的方法:
```javascript
class AppComponent {
constructor(dynamicComponentLoader, viewContainerRef, injector) {
this.dynamicComponentLoader = dynamicComponentLoader;
this.viewContainerRef = viewContainerRef;
this.injector = injector;
}
//动态加载组件的方法,需要传入一个组件
loadComponent(component) {
this.viewContainerRef.clear();
this.dynamicComponentLoader.loadAsRoot(component, '#component-container', this.injector)
.then((componentRef) => {
//必须要这样来编写,否则会导致双向绑定表达式获取不到值。
componentRef.changeDetectorRef.detectChanges();
componentRef.onDestroy(() => {
componentRef.changeDetectorRef.detach();
});
return componentRef;
});
}
}
```
在以上代码中,核心就是 ``dynamicComponentLoader`` 的 ``loadAsRoot`` 方法,这个方法里面有个参数是 ``'#component-container'``,实际上指定将动态加载的组件放置的容器,
```html
```
通过这样的方式,我们就能够动态的把组件加载到页面上了。
**注:``dynamicComponentLoader`` 还有一个加载方法是 ``loadNextToLocation(component, viewContainerRef)``,只需要提供要动态加载的组件和一个容器引用,就可以将组件加载到容器中了。**
================================================
FILE: Angular系列/09_Angular2使用ui-router-ng2.md
================================================
---
title: 09_Angular2使用ui-router-ng2
date: 2017/02/21 14:47:10
---
## 0、导言
Angular2的路由组件从beta到rc经历了多次变更,知道rc.4都没有完全稳定下来。其次它的功能也并不强大,全局钩子,动态加载,状态控制都不支持。
如果是使用Angular1,那这个时候我们一般会选择 ``ui-router`` 这一个强大的基于状态的路由。
其实,``ui-router`` 也提供了一个Angular2的版本,那就是 ``ui-router-ng2``。
我们就来简单的尝试下它的使用,和利用它来实现动态加载一批组件(结合webpack)。
## 1、引入 ``ui-router-ng2``
要使用 ``ui-router-ng2``,我们必须要先通过 ``npm install ui-router-ng2`` 来安装该包。
安装成功之后,我们需要实现一个 ``UIRouterConfig`` 的实例,代码如下:
```typescript
import { Injectable } from '@angular/core';
import { UIRouter, UIRouterConfig } from 'ui-router-ng2';
import {AboutComponent} from './about.component';
@Injectable()
export class AppRouterConfig implements UIRouterConfig {
constructor() {
}
configure(uiRouter: UIRouter) {
uiRouter.stateRegistry.register({
name: 'about',
component: AboutComponent,
url: '/about'
});
}
}
```
一般做法,我们需要在 ``configure`` 方法中,注册路由状态对象。
当实现了 ``UIRouterConfig`` 之后,我们就可以在应用启动时来使用它了,具体代码如下:
```typescript
import { enableProdMode, provide, PLATFORM_DIRECTIVES } from '@angular/core';
import { APP_BASE_HREF, LocationStrategy, HashLocationStrategy, PlatformLocation } from '@angular/common';
import { BrowserPlatformLocation } from '@angular/platform-browser';
import { bootstrap } from '@angular/platform-browser-dynamic';
import { UIROUTER_PROVIDERS, UIRouterConfig, UIROUTER_DIRECTIVES } from 'ui-router-ng2';
import { RootComponent } from './../shell';
import { AppRouterConfig } from './routes';
enableProdMode();
bootstrap(RootComponent, [
provide(APP_BASE_HREF, { useValue: '/' }),
provide(LocationStrategy, { useClass: HashLocationStrategy }),
provide(PlatformLocation, { useClass: BrowserPlatformLocation }),
...UIROUTER_PROVIDERS,
provide(UIRouterConfig, { useClass: AppRouterConfig }),
provide(PLATFORM_DIRECTIVES, { useValue: UIROUTER_DIRECTIVES, multi: true })
])
.then(x => {
console.log('app started...');
})
.catch(error => console.log(error));
```
其中最关键是和路由相关的代码是:
```typescript
...UIROUTER_PROVIDERS, //依赖路由提供者
provide(UIRouterConfig, { useClass: AppRouterConfig }), //指定RouterConfig
provide(PLATFORM_DIRECTIVES, { useValue: UIROUTER_DIRECTIVES, multi: true }) //依赖路由指令
```
在经过这些步骤之后,我们的项目就已经可以使用 ``ui-router-ng2`` 来进行路由管理了。
## 2、路由钩子
``ui-router-ng2`` 提供了很强大的路由钩子函数,可以让我们很方便的对路由的各个阶段进行控制。
使用方式如下:
```typescript
import {Injectable, Inject} from '@angular/core';
import {UIRouter, UIRouterConfig} from 'ui-router-ng2';
import {AboutComponent} from './about.component';
@Injectable()
export class AppRouterConfig implements UIRouterConfig {
constructor() {
}
configure(uiRouter: UIRouter) {
uiRouter.stateRegistry.register({
name: 'about',
component: AboutComponent,
url: '/about'
});
// 以下t均为Transition实例
uiRouter.transitionService.onBefore({}, t => {
console.log('onBefore', t); //路由跳转之前
});
uiRouter.transitionService.onStart({}, t => {
console.log('onStart', t); //路由跳转开始
});
uiRouter.transitionService.onExit({}, t => {
console.log('onExit', t); //路由跳出时
});
uiRouter.transitionService.onRetain({}, t => {
console.log('onRetain', t); //...
});
uiRouter.transitionService.onEnter({}, t => {
console.log('onEnter', t); //路由进入时
});
uiRouter.transitionService.onFinish({}, t => {
console.log('onFinish', t); //路由跳转完成
});
uiRouter.transitionService.onSuccess({}, t => {
console.log('onSuccess', t); //路由跳转成功
});
uiRouter.transitionService.onError({}, t => {
console.log('onError', t); //路由跳转出错
});
}
}
```
通过以上的各个阶段,我们可以灵活控制跳转是否继续,每个钩子函数都接受 ``boolean`` 和 ``Promise``来让我们确定是否跳转。
## 3、自定义回调处理invalidState
通过以上的方式,我们实现了状态路由,也实现了路由跳转的控制。接下来,我们另辟蹊径来实现动态加载。
由于当我们请求不合法的state时,uiRouter都会执行到 ``invalidCallbacks`` 这个函数,我这里就通过它加载动态模块。
实现代码如下:
```typescript
configure(uiRouter: UIRouter) {
... //省略不相关代码
uiRouter.stateProvider.invalidCallbacks = [($from$, $to$) => {
return new Promise((resolve, reject) => {
let toStateName = $to$.name();
let moduleName = this._getModuleName(toStateName);
if (!moduleName) {
return;
}
this.moduleLoader.load(moduleName).then(_ => {
let state = uiRouter.stateService.target(toStateName);
resolve(state);
});
});
}];
}
```
接着,再来看看moduleLoader的代码:
```typescript
import { Injectable, Inject } from '@angular/core';
import {Http} from '@angular/http';
import {UIRouter} from 'ui-router-ng2';
@Injectable()
export class ModuleLoader {
private uiRouter: UIRouter;
private loadedModules: Set;
constructor(private http: Http) {
this.loadedModules = new Set();
}
setRouter(uiRouter) {
this.uiRouter = uiRouter;
}
load(moduleName): Promise {
if (this.loadedModules.has(moduleName)) {
return Promise.resolve();
}
return new Promise((resolve, reject) => {
this.http.get(`../dist/assets/js/${moduleName}.js`)
.toPromise()
.then(res => {
let mod = eval(res.text());
mod.MODULE_STATES.forEach(state => {
this.uiRouter.stateRegistry.register(state);
});
this.loadedModules.add(moduleName);
resolve();
}).catch(err => reject(err));
});
}
}
```
通过请求指定的文件,然后利用eval执行出具体的state数组,通过register方法,动态注入到我们的ui-router中。
**注意invalidCallbacks回调中的参数($from$, $to$),必须使用这两个名字,通过分析源代码发现它是用参数名做了匹配的,如果换成其他名称,会提示注入错误。**
**invalidCallbacks回调中的逻辑非常关键,演示了如何获取原始要跳转的state,也演示了如何恢复继续跳转。**
## 4、更多待探索
当前对 ``ui-router-ng2`` 的探索还不够多,另外它本身也还在beta版本,该处理方式应该还有优化空间。
================================================
FILE: Angular系列/Angular2踩坑大全.md
================================================
---
title: Angular2踩坑大全
date: 2017/02/21 14:47:10
---
## Angular2的那些坑
1、同样的代码,引用Rxjs库的版本不对,就会导致在IE11下无法运行。(特定版本下重现)
正确的版本:[https://code.angularjs.org/2.0.0-beta.12/Rx.js](https://code.angularjs.org/2.0.0-beta.12/Rx.js)
2、在使用TypeScript编写Angular2代码时,一定要将注意 ``tsconfig.json``,其中 ``experimentalDecorators`` 和 ``emitDecoratorMetadata`` 必须要设置为true,否则无法使用依赖注入。
3、<router-outlet> 不能放在带有 *ngIf的容器内,否则会出现初始化时无法找到。
================================================
FILE: Angular系列/利用Angular实现多团队模块化SPA开发框架.md
================================================
---
title: 利用Angular实现多团队模块化SPA开发框架
date: 2017-11-23 11:11:11
---
# 0、前言
当一个公司有多个开发团队时,我们可能会遇到这样一些问题:
1. 技术选项杂乱,大家各玩各
2. 业务重复度高,各种通用api,登录注销,权限管理都需要重复实现(甚至一个团队都需要重复实现)
3. 业务壁垒,业务之间的互通变得比较麻烦
4. 部署方式复杂,多个域名(或IP地址)访问,给用户造成较大的记忆难度
5. 多套系统,风格难以统一
6. 等等...
当然,解决方式有不少。以下就来讲解下我们这边的一种解决方案。
# 1、思路
**Angualr**
`Angular`(注:非AngularJS) 是流行的前端 `MVVM` 框架之一,配合 `TypeScript`,非常适合用来做后台管理系统。由于我们曾今的一套 `Angularjs` 开发框架,我们继续选择 `Angular` 来进行实现,并尽可能的兼容 `AngularJS` 的模块。
**SPA**
选 `SPA` 还是多页?多余 `Mvvm` 来说,多页并不是标配。而且多页开发中,我们势必会关注更多的内容,包括通用header,footer,而不仅仅是页面的核心内容。
**模块化**
为什么要模块化呢?当有多个团队开发时(或者项目较大时),我们希望各个团队开发出来的东西都是 `模块`(不仅限于JS模块),这样可以让我们独立发布、更新、删除模块,也能让我们的关注点集中在特定模块下,提高开发效率和可维护性。
**平台化**
我们需要有一个运行平台(Website站点),允许在里面运行指定的模块。这样就可以实现单一入口,也容易实现通用逻辑,模块共享机制等等。
**兼容 AngularJS 模块**
在考虑将框架切换到 `Angular` 时,我们无可避免的会遇到如何兼容当前已有模块的问题。大致可选的方案如下:
1. 参考 `AngualrJS -> Angular` 官方升级指南,一步步将模块切换为 `Angular` 的实现。(工作量大,需要开发团队调整很多东西)
2. `iframe嵌入`,会有一定的体验差异,但对开发团队来说,基本无缝升级,也不需要做什么改动。(无疑,我们选择了这套方案)
**模块打包**
我们需要将单个的模块打包为资源包,进行更新。这样才能做到模块独立发布,及时生效。
**CSS冲突**
在大型 `SPA` 中,CSS冲突是很大的一个问题。我们期望通过技术手段,能够根据当前使用的模块,加载和卸载CSS。
**跨页面共享数据**
由于涉及到iframe兼容旧有模块,我们无可避免,需要考虑跨窗口的页面共享。
**公共模块**
当一个团队的模块较多时,就会有一些公共的东西被抽取出来,这个过程,框架是无法知道的,所以这个时候,我们就需要考虑支持公共模块。(模块之间也有依赖关系)
# 3、实现
基于以上的一些思考,我们首先需要实现一个基础的平台网站,这个没什么难度,直接用 `Angular` 实现即可。有了这一套东西,我们的登录注销,基本的菜单权限管理,也就实现了。
在这个基础之上,我们也能实现公共服务、公共组件了(封装一系列常用的玩意)。
## 如何模块化?如何打包?
**注意:此模块并非Angular本身的模块。** 我们通过约定,在 `modules/` 下的每一个目录都是一个业务模块。一个业务模块一般会包含,静态资源、CSS以及JS。根据这个思路,我们的打包策略就是:遍历 `modules/` 的所有目录,对每一个目录进行单独打包(webpack多entry打包+CSS抽取),另外使用 `gulp` 来处理相关的静态资源(在我看来,gulp才是构建工具,webpack是打包工具,所以混合使用,物尽其用)。
一般来说,`webpack` 会把所有相关依赖打包在一起,A、B 模块都依赖了 `@angular/core` 识别会重复打包,而且框架中,也已经打包了 `@angular` 相关组件。这个时候,常规的打包配置就不太合适了。那该如何做呢?
考虑到 `Angular` 也提供了 `CDN` 版本,所以我们将 `Angular` 的组件通过文件合并,作为全局全量访问,如 `ng.core`、`ng.common` 等。
既然这样,那我们打包的时候,就可以利用 `webpack` 的 `externals` 功能,把相关依赖替换为全局变量。
```js
externals: [{
'rxjs': 'Rx',
'@angular/common': 'ng.common',
'@angular/compiler': 'ng.compiler',
'@angular/core': 'ng.core',
'@angular/http': 'ng.http',
'@angular/platform-browser': 'ng.platformBrowser',
'@angular/platform-browser-dynamic': 'ng.platformBrowserDynamic',
'@angular/router': 'ng.router',
'@angular/forms': 'ng.forms',
'@angular/animations': 'ng.animations'
}
```
这样处理之后,我们打包后的文件,也就不会有 `Angular` 框架代码了。
**注:这个对引入资源的方式也有一定要求,就不能直接引入内层资源了。**
## 如何动态加载模块
打包完成之后,这个时候就要考虑平台如何加载这些模块了(发布过程就不说了,放到指定位置即可)。
什么时候决定加载模块呢?其实是访问特定路由的时候,所以我们的顶级路由,会使用Promise方法来实现,如下:
```js
const loadModule = (moduleName) => {
return () => {
return ModuleLoaderService.load(moduleName);
};
};
const dynamicRoutes = [];
modules.forEach(item => {
dynamicRoutes.push({
path: item.path,
canActivate: [AuthGuard],
canActivateChild: [AuthGuard],
loadChildren: loadModule(item.module)
});
});
const appRoutes: Routes = [{
path: 'login', component: LoginComponent
}, {
path: 'logout', component: LogoutComponent
}, {
path: '', component: LayoutComponent, canActivate: [AuthGuard],
children: [
{ path: '', component: HomeComponent },
...dynamicRoutes,
{ path: '**', component: NotFoundComponent },
]
}];
```
我们把每个模块,按照 `umd` 的格式进行打包。然后再需要使用该模块的时候,使用动态构建 `script` 来运行脚本。
```js
load(moduleName, isDepModule = false): Promise {
let module = window['xxx'][moduleName];
if (module) {
return Promise.resolve(module);
}
return new Promise((resolve, reject) => {
let path = `${root}${moduleName}/app.js?rnd=${Math.random()}`;
this._loadCss(moduleName);
this.http.get(path)
.toPromise()
.then(res => {
let code = res.text();
this._DomEval(code);
return window['xxx'][moduleName];
})
.then(mod => {
window['xxx'][moduleName] = mod;
let AppModule = mod.AppModule;
// route change will call useModuleStyles function.
// this.useModuleStyles(moduleName, isDepModule);
resolve(AppModule);
})
.catch(err => {
console.error('Load module failed: ', err);
resolve(EmptyModule);
});
});
}
// 取自jQuery
_DomEval(code, doc?) {
doc = doc || document;
let script = doc.createElement('script');
script.text = code;
doc.head.appendChild(script).parentNode.removeChild(script);
}
```
CSS的动态加载相对比较简单,代码如下:
```js
_loadCss(moduleName: string): void {
let cssPath = `${root}${moduleName}/app.css?rnd=${Math.random()}`;
let link = document.createElement('link');
link.setAttribute('rel', 'stylesheet');
link.setAttribute('href', cssPath);
link.setAttribute('class', `xxx-module-style ${moduleName}`);
document.querySelector('head').appendChild(link);
}
```
为了能够在模块切换时卸载,还需要提供一个方法,供路由切换时使用:
```js
useModuleStyles(moduleName: string): void {
let xxxModuleStyles = [].slice.apply(document.querySelectorAll('.xxx-module-style'));
let moduleDeps = this._getModuleAndDeps(moduleName);
moduleDeps.push(moduleName);
xxxModuleStyles.forEach(link => {
let disabled = true;
for (let i = moduleDeps.length - 1; i >= 0; i--) {
if (link.className.indexOf(moduleDeps[i]) >= 0) {
disabled = false;
moduleDeps.splice(i, 1);
break;
}
}
link.disabled = disabled;
});
}
```
## 公共模块依赖
为了处理模块依赖,我们可以借鉴 AMD规范 以及使用 `requirejs` 作为加载器。当前在我的实现里,是自定义了一套加载器,后期应该会切换到 AMD 规范上去。
## 如何兼容 `AngularJS` 模块?
为了兼容 `AngularJS` 的模块,我们引入了 iframe, iframe会先加载一套曾今的 `AngularJS` 宿主,然后再这个宿主中,运行 `AngularJS` 模块。为了实现通信,我们需要两套平台程序中,都引入一个基于 `postMessage` 实现的跨窗口通信库(因为默认跨域,所以用postMessage实现),有了它之后,我们就可以很方便的两边通信了。
## AOT编译
按照 `Angular` 官方的 `Aot` 编译流程即可。
## 多Tab页
在后台系统中,多Tab页是比较常用了。但是多Tab页,在单页中使用,会有一定的性能风险,这个依据实际的情况,进行使用。实现多Tab页的核心就是如何动态加载组件以及如何获取到要加载的组件。
多Tab页面,实际就是一个 `Tabset` 组件,只是在 `tab-item` 的实现稍显特别一些,相关动态加载的源码:
```js
@ViewChild('dynamicComponentContainer', { read: ViewContainerRef }) dynamicComponentContainer: ViewContainerRef;
constructor(
private elementRef: ElementRef,
private renderer: Renderer2,
private tabset: TabsetComponent,
private resolver: ComponentFactoryResolver,
private parentContexts: ChildrenOutletContexts
) {
}
public destroy() {
let el = this.elementRef.nativeElement as HTMLElement;
// tslint:disable-next-line:no-unused-expression
el.parentNode && (el.parentNode.removeChild(el));
}
private loadComponent(component: any) {
let context = this.parentContexts.getContext(PRIMARY_OUTLET);
let injector = ReflectiveInjector.fromResolvedProviders([], this.dynamicComponentContainer.injector);
const resolver = context.resolver || this.resolver;
let factory = resolver.resolveComponentFactory(component);
// let componentIns = factory.create(injector);
// this.dynamicComponentContainer.insert(componentIns.hostView);
this.dynamicComponentContainer.createComponent(factory);
}
```
**注意:要考虑组件卸载方法,如 destroy()**
为了获取到当前要渲染的组件,我们可以借用路由来抓取:
```js
this.router.events.subscribe(evt => {
if (evt instanceof NavigationEnd) {
let pageComponent;
let pageName;
try {
let nextRoute = this.route.children[0].children[0];
pageName = this.location.path();
pageComponent = nextRoute.component;
} catch (e) {
pageName = '$$notfound';
pageComponent = NotFoundComponent;
}
let idx = this.pageList.length + 1;
if (!this.pageList.find(x => x.name === pageName)) {
this.pageList.push({
header: `页面${idx}`,
comp: pageComponent,
name: pageName,
closable: true
});
}
setTimeout(() => {
this.selectedPage = pageName;
});
}
});
```
# 3、总结
以上就是大概的实现思路以及部分相关的细节。其他细节就需要根据实际的情况,进行酌情处理。
该思路并不仅限于 `Angular` 框架,使用 `Vue、React` 也可以做到类似的效果。同时,这套东西也比较适合中小企业的后台平台(不一定非要多团队,一个团队按模块开发也是不错的)。
如需要了解更多细节,可以参考:[ngx-modular-platform](https://github.com/hstarorg/ngx-modular-platform),能给个 `star` 就更好了。
在此抛砖引玉,希望能集思广益,提炼出更好的方案。欢迎讨论和 `提Issue`, `发PR`。
================================================
FILE: Angular系列/跟我学Angular2(1-初体验).md
================================================
---
title: 跟我学Angular2(1-初体验)
date: 2017/02/21 14:47:10
---
## 0、导言
Angular1作为最流行的前端MV*框架,给前端开发带来了极大的便利性。但是,仍然有许多不好的地方已经很难再改变了。Angular团队根据WEB发展的趋势和Angular1中积累的经验来开发了一个全新的Angular,也就是Angular2。
## 1、优势
Angular2做了很激进的变化,带来的成果也是显而易见的。
1. 极大的提高了性能
2. 更强大的模块化
3. 改进的依赖注入
4. 对Web Component友好
5. 原生移动支持 - iOS 和 Android
6. 服务端渲染,搜索引擎优化
## 2、工具链
由于Angular2面向未来,使用了太多还不被当前主流浏览器支持的技术,跑起来还真不是一个容易的事情,所以我们需要一个工具链:

systemjs - 通用模块加载器,支持AMD、CommonJS、ES6等各种格式的JS模块加载
es6-module-loader - ES6模块加载器,systemjs会自动加载这个模块
traceur - ES6转码器,将ES6代码转换为当前浏览器支持的ES5代码。systemjs会自动加载 这个模块。
## 3、Angular2 Hello world
### Step1、下载angular2
[https://angular.io/](https://angular.io/)是angular2的官网,我们需要通过npm进行下载angular2: npm install angular2 [https://www.npmjs.com/package/angular2](https://www.npmjs.com/package/angular2)。
### Step2、引入angular2
### Step3、 Hello angular
================================================
FILE: CSS3学习之路/CSS3入门之文本与字体.md
================================================
---
title: CSS3入门之文本与字体
date: 2017/02/21 14:47:10
---
## 1、CSS3文本效果
### 1.1、text-shadow文本阴影
语法:``text-shadow: h-shadow v-shadow blur color;``(<水平阴影>,<垂直阴影>,[模糊距离],[阴影颜色])
示例:
我是文本阴影
我是文本阴影
我是文本阴影
我是文本阴影
我是文本阴影
我是文本阴影
**该属性兼容IE10+以及所有现代浏览器**
### 1.2、word-break文本换行
语法: ``word-break: normal|break-all|keep-all;``
normal:默认换行;break-all:允许在单词内换行;keep-all:只能在半角空格或连字符处换行
示例:
Nice to meet you. good mor-ning.
Nice to meet you. good mor-ning.
Nice to meet you. good mor-ning.
Nice to meet you. good mor-ning.
### 1.3、text-overflow修剪文本
语法:``text-overflow: clip|ellipsis|string;``
示例:
Nice to meet you. good mor-ning.
Nice to meet you. good mor-ning.
Nice to meet you. good mor-ning.
Nice to meet you. good mor-ning.
**注意:使用text-overflow的时候,需要与overflow:hidden;white-space:nowrap;协同使用**
## 2、CSS3字体
在CSS3之前,必须使用已经在用户计算机上安装好的字体,给Web设计带来很大的局限性。现在,通过CSS3,Web设计师可以使用他们喜欢的任意字体。
### 2.1、@font-face引入网络字体
Firefox、Chrome、Safari 以及 Opera 支持 .ttf (True Type Fonts) 和 .otf (OpenType Fonts) 类型的字体。
Internet Explorer 9+ 支持新的 @font-face 规则,但是仅支持 .eot 类型的字体 (Embedded OpenType)。
不兼容IE8,IE8-。
示例:
自定义字体演示
自定义字体演示
自定义字体演示
自定义字体演示
除此之外,在@font-face中,还可以设置多种字体描述符,如:
| 描述符 |
值 |
描述 |
| font-family |
name |
必需。规定字体的名称。 |
| src |
URL |
必需。定义字体文件的 URL。 |
| font-stretch |
- normal
- condensed
- ultra-condensed
- extra-condensed
- semi-condensed
- expanded
- semi-expanded
- extra-expanded
- ultra-expanded
|
可选。定义如何拉伸字体。默认是 "normal"。 |
| font-style |
|
可选。定义字体的样式。默认是 "normal"。 |
| font-weight |
- normal
- bold
- 100
- 200
- 300
- 400
- 500
- 600
- 700
- 800
- 900
|
可选。定义字体的粗细。默认是 "normal"。 |
| unicode-range |
unicode-range |
可选。定义字体支持的 UNICODE 字符范围。默认是 "U+0-10FFFF"。 |
================================================
FILE: CSS3学习之路/CSS3入门之转换.md
================================================
---
title: CSS3入门之转换
date: 2017/02/21 14:47:10
---
## 1、CSS3 转换
### 1.1、转换是什么,能实现哪些效果?
转换是使元素改变形状、尺寸和位置的一种效果,主要能实现的效果如下:
1. 移动
2. 缩放
3. 转动
4. 拉长
5. 拉伸
### 1.2、浏览器兼容
CSS3的转换属性为 ``transform`` ,IE10+,Firefox,Chrome,Opera,Safari等现代浏览器支持transform属性,IE9需要-ms-前缀。
## 2、 2D 转换
准备工作:
### 2.1、translate() -- 移动
translate(/\*x坐标移动位移\*/ left, /\*y坐标移动位移\*/ top)
右移20px
下移20px
左移20px,下移20px
右移20px
下移20px
左移20px,下移20px
### 2.2、rotate() -- 旋转
rotate(/\*旋转角度\*/ deg)
旋转135度
旋转135度
### 2.3、scale() -- 缩放
scale(/\*宽度缩放比例\*/ widthScale, /\*高度缩放比例\*/ heightScale)
缩放到0.5倍
宽度缩放到1.5倍,高度缩放到0.25倍
缩放到0.5倍
宽度缩放到1.5倍,高度缩放到0.25倍
### 2.4、skew() -- 倾斜
skew(/\*X轴倾斜角度\*/ xDeg, /\*Y轴倾斜角度\*/ yDeg)
X轴翻转30度
X轴翻转30度,Y轴翻转10度
X轴翻转30度
X轴翻转30度,Y轴翻转10度
### 2.5、matrix() --矩阵
旋转30度
旋转30度
### 2.6 Transform方法
| 函数 |
描述 |
| matrix(n,n,n,n,n,n) |
定义 2D 转换,使用六个值的矩阵。 |
| translate(x,y) |
定义 2D 转换,沿着 X 和 Y 轴移动元素。 |
| translateX(n) |
定义 2D 转换,沿着 X 轴移动元素。 |
| translateY(n) |
定义 2D 转换,沿着 Y 轴移动元素。 |
| scale(x,y) |
定义 2D 缩放转换,改变元素的宽度和高度。 |
| scaleX(n) |
定义 2D 缩放转换,改变元素的宽度。 |
| scaleY(n) |
定义 2D 缩放转换,改变元素的高度。 |
| rotate(angle) |
定义 2D 旋转,在参数中规定角度。 |
| skew(x-angle,y-angle) |
定义 2D 倾斜转换,沿着 X 和 Y 轴。 |
| skewX(angle) |
定义 2D 倾斜转换,沿着 X 轴。 |
| skewY(angle) |
定义 2D 倾斜转换,沿着 Y 轴。 |
## 3、3D 转换
### 3.1、rotateX、rotateY
### 3.2、Transform方法
| 函数 |
描述 |
matrix3d(n,n,n,n,n,n, n,n,n,n,n,n,n,n,n,n) |
定义 3D 转换,使用 16 个值的 4x4 矩阵。 |
| translate3d(x,y,z) |
定义 3D 转化。 |
| translateX(x) |
定义 3D 转化,仅使用用于 X 轴的值。 |
| translateY(y) |
定义 3D 转化,仅使用用于 Y 轴的值。 |
| translateZ(z) |
定义 3D 转化,仅使用用于 Z 轴的值。 |
| scale3d(x,y,z) |
定义 3D 缩放转换。 |
| scaleX(x) |
定义 3D 缩放转换,通过给定一个 X 轴的值。 |
| scaleY(y) |
定义 3D 缩放转换,通过给定一个 Y 轴的值。 |
| scaleZ(z) |
定义 3D 缩放转换,通过给定一个 Z 轴的值。 |
| rotate3d(x,y,z,angle) |
定义 3D 旋转。 |
| rotateX(angle) |
定义沿 X 轴的 3D 旋转。 |
| rotateY(angle) |
定义沿 Y 轴的 3D 旋转。 |
| rotateZ(angle) |
定义沿 Z 轴的 3D 旋转。 |
| perspective(n) |
定义 3D 转换元素的透视视图。 |
================================================
FILE: CSS3学习之路/CSS3入门之边框与背景.md
================================================
---
title: CSS3入门之边框与背景
date: 2017/02/21 14:47:10
---
## 1、前言
CSS3作为CSS的最新版本,在展示效果上有非常大的提升,接下来,我们就一起领略一下CSS3的风采吧。
## 2、CSS3边框
```
```
### 2.1、border-radius(用于设置圆角边框)
在CSS2时代,要想实现圆角边框,是一件非常麻烦的事情。一种实现方式是使用一个背景图片,为了实现伸缩效果,还需要至少3张图片拼凑,相当麻烦。另外一种实现方式是使用多个div重叠来实现圆角。
在CSS3中,有一个非常简单的属性,那就是border-radius。
语法: ``border-radius: 1-4 length|% / 1-4 length|%;``
```css
border-radius: 10px;
//等价于
border-top-left-radius:10px;
border-top-right-radius:10px;
border-bottom-right-radius:10px;
border-bottom-left-radius:10px;
```
演示圆角边框
演示圆角边框
**兼容性说明:** IE9+,Chrome,FF,Safari,Oprea
```
div
{
border:2px solid;
border-radius:25px;
-moz-border-radius:25px; /* Old Firefox */
}
```
### 2.2、box-shadow(用于添加边框阴影)
语法: ``box-shadow: h-shadow v-shadow blur spread color inset;``,其中h-shadow和v-shadow是必须设置,允许负值。【参数说明:水平阴影的位置,垂直阴影的位置,模糊距离,阴影的尺寸,阴影的颜色,外部引用(outset)改为内部阴影】
```html
演示圆角边框
简单阴影
简单阴影
简单阴影
带模糊效果的阴影
带模糊效果的阴影
带模糊效果指定尺寸的阴影
带模糊效果指定尺寸的阴影
内部阴影
内部阴影
```
**兼容性说明:** IE9+,Chrome,FF,Safari,Oprea
### 2.3、border-image(CSS3边框图片)
border-image是简写属性,全部是:
```css
border-image-source //背景图片源
border-image-slice //图片边框内偏移
border-image-width //图片边框的宽度
border-image-outset //边框图像区域超出边框的量
border-image-repeat //边框是否适应平铺(repeated)、铺满(rounded)、拉伸(stretched)
```
```html
简单图片边框
简单图片边框
完全设置的图片边框
完全设置的图片边框
```
**兼容性说明:** Chrome,FF,Safari,Oprea
```
div
{
border-image:url(border.png) 30 30 round;
-moz-border-image:url(border.png) 30 30 round; /* 老的 Firefox */
-webkit-border-image:url(border.png) 30 30 round; /* Safari 和 Chrome */
-o-border-image:url(border.png) 30 30 round; /* Opera */
}
```
## 3、CSS3背景
### 整体兼容性
以下CSS背景的特性,全部支持IE9+,FF,Chrome,Safari,Oprea
### 3.1、background-size(用于规定背景图片的尺寸)
在以前的CSS中,背景图片的大小,是由图片本身的大小决定的。在CSS3中,有一个简单的CSS样式可以设置背景图片的大小,允许我们在不同的环境中重复使用背景图片。可以以像素或百分比规定尺寸。
```html
简单设置背景图大小
```
### 3.2、background-origin(规定背景图片的定位区域)
盒子模型示意图:
background-origin属性则可以设置背景图片放置于哪个区域上(content-box,padding-box,border-box)
```html
```
### 3.3、多重背景
可以针对标签设置多个背景,用法如下:
```css
body
{
background-image:url(bg_flower.gif),url(bg_flower_2.gif);
}
```
================================================
FILE: Canvas学习札记/01_初识Canvas,绘制简单图形.md
================================================
---
title: 01_初识Canvas,绘制简单图形
date: 2017/02/21 14:47:10
---
## 0、关于Canvas
``
);
```
>**警告:**
>
>由于 JSX 更趋近于 JavaScript 而不是 HTML,React DOM 使用 `camelCase`(小驼峰) 属性命名约定而不是HTML的属性名称。
>
>例如:`class` 在JSX中是 [`className`](https://developer.mozilla.org/en-US/docs/Web/API/Element/className),`tabindex` 在JSX中是 [`tabIndex`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/tabIndex)。
### JSX 防止注入攻击
在 `JSX` 中嵌入用户输入是安全的:
```js
const title = response.potentiallyMaliciousInput;
// 安全的:
const element = {title}
;
```
默认情况下,React DOM 会在渲染前使用 [escapes](http://stackoverflow.com/questions/7381974/which-characters-need-to-be-escaped-on-html) 编码所有嵌入 `JSX` 的值。 因此它能确保您永远不会注入任何未明确写入应用程序的内容。所有内容都将在呈现前转换为字符串。这有助于防御 [XSS (cross-site-scripting)](https://en.wikipedia.org/wiki/Cross-site_scripting) 攻击。
### JSX 代表对象
`Babel` 将 `JSX` 编译成 `React.createElement()` 调用。
这两个例子是等同的:
```js
const element = (
Hello, world!
);
```
```js
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
```
`React.createElement()` 会执行一些检查来帮助您编写无误的代码,但基本上,它是创建如下的对象:
```js
// 注意:以下是简单结构
const element = {
type: 'h1',
props: {
className: 'greeting',
children: 'Hello, world'
}
};
```
这些对象称之为 "React elements". 你可以将它们视为您想要在屏幕上看到的内容。`React` 会读取这些对象,并使用它们来构造DOM且保持为最新状态。
下一节我们将探索如何渲染 `React elements` 到DOM中。
>**提示:**
>
>我们建议为编辑器选择 `Babel` 语法支持插件,以便 `ES6` 和 `JSX` 都能被高亮显示。
================================================
FILE: React面面观/【译】快速起步-事件处理.md
================================================
---
title: 快速起步-事件处理
date: 2017-4-18 16:58:04
react version: 15.5.0
---
# 事件处理
使用React元素处理事件和DOM元素上的事件非常相似。但还是有一些语法上的不同:
* React 的事件是小驼峰命名的,不是全小写命名的。
* 可以在JSX中传递函数作为事件处理器,而不是字符串。
例如:
```html
```
在React中略微不同:
```js{1}
```
另一个不同是,不能通过 `return false` 来阻止默认行为。必须要明确使用 `preventDefault` 。例如,在HTML中,要想阻止a标签的默认行为,可以如下写:
```html
Click me
```
但是在React中,你必须这样写:
```js{2-5,8}
function ActionLink() {
function handleClick(e) {
e.preventDefault();
console.log('The link was clicked.');
}
return (
Click me
);
}
```
在这里, `e` 是一个合成事件。`React` 根据 [W3C 标准](https://www.w3.org/TR/DOM-Level-3-Events/) 定义了这些合成事件,所以你不需要担心浏览器兼容性问题。查看 [SyntheticEvent](https://facebook.github.io/react/docs/events.html) 了解更多。
在使用 `React` 的时候,通常不需要调用 `addEventListener` 来给DOM元素添加事件。而是在元素最初渲染时提供一个监听器。
当你在使用 [ES6 class](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Classes) 定义组件时,一个常见的模式是将事件处理程序定义为类上的一个原型方法。例如,`Toggle` 渲染了一个可以在 "ON" 和 "OFF" 之间切换的按钮:
```js{6,7,10-14,18}
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// 必要的绑定,让 `this` 在回调中可用。
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
);
}
}
ReactDOM.render(
,
document.getElementById('root')
);
```
[CodePen Demo](http://codepen.io/gaearon/pen/xEmzGg?editors=0010)
您必须要注意JSX回调中 `this` 的含义。在JavaScript中,默认情况下,类方法不是 [bound](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_objects/Function/bind) 的。如果你传递处理函数给 `onClick` 时忘记了绑定 `this` 给 `this.handleClick`,那么当函数被调用时,`this` 将会是 `undefined`。
这不是React特定的行为,它是 [JavaScript中函数如何工作](https://www.smashingmagazine.com/2014/01/understanding-javascript-function-prototype-bind/) 的一部分。通常,如果你引用了没有 `()` 的方法(方法引用),比如 `onClick={this.handleClick}`,那么你需要先 `bind(this)`。
如果调用 `bind` 让你觉得很繁琐,还有两种方法可以解决这个问题。如果你在使用实现性质的语法 [属性初始化](https://babeljs.io/docs/plugins/transform-class-properties/),你可以使用属性初始化来正确的绑定回调的 `this`:
```js{2-6}
class LoggingButton extends React.Component {
// This syntax ensures `this` is bound within handleClick.
// Warning: this is *experimental* syntax.
handleClick = () => {
console.log('this is:', this);
}
render() {
return (
);
}
}
```
在 [Create React App](https://github.com/facebookincubator/create-react-app) 中,这个语法是默认启用的。
如果你不想使用属性初始化语法,你还可以在回调中使用 [箭头函数](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/Arrow_functions):
```js{7-9}
class LoggingButton extends React.Component {
handleClick() {
console.log('this is:', this);
}
render() {
// 这个语法确保 `this` 在 handleClick中指向正确
return (
);
}
}
```
这个语法的问题是,每次 `LoggingButton` 渲染时,都会创建一个不同的回调函数。大多数情况下,这都没啥问题。但是,如果这个回调函数会传递给层次更低的组件,那么可能会导致这些组件进行额外的渲染。我们通常建议在构造函数中使用绑定,或者使用属性初始化语法来避免这个问题。
================================================
FILE: React面面观/【译】快速起步-列表与KEY.md
================================================
---
title: 快速起步-数组与KEY
date: 2017-4-20 13:34:37
react version: 15.5.0
---
# 数组与KEY
首先,我们来看看如何在 `JavaScript` 中转换列表。
如下代码中,我们使用 `map()` 方法来让我们的数字数组值翻倍。我们通过 `map()` 方法得到了一个新数组,并打印:
```javascript{2}
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((number) => number * 2);
console.log(doubled);
```
代码将会在控制台上打印:`[2, 4, 6, 8, 10]`。
在 `React` 中,将数组转换为 [elements](【译】快速起步-渲染元素.md) 列表,几乎和上面的代码一样。
### 渲染多个组件
你可以构建元素集合,并使用 `{}` 将它们包含在 `JSX` 中。
下面,我们使用 [`map()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) 来循环一个数字数组。并对每个项目都返回一个 `` 元素。最终将结果赋值给 `listItems`:
```javascript{2-4}
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
{number}
);
```
我们将整个 `listItems` 包含在 `` 元素中,并渲染到DOM中:
```javascript{2}
ReactDOM.render(
,
document.getElementById('root')
);
```
[Try it on CodePen.](https://codepen.io/gaearon/pen/GjPyQr?editors=0011)
这个代码显示了 1-5 的数字列表。
### 基础列表组件
通常你会在 [组件](【译】快速起步-组件与属性.md) 中显示列表。
我们将上一个示例重构到一个组件中,这个组件接收一个数字数组,并输出一个无序的元素列表。
```javascript{3-5,7,13}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
- {number}
);
return (
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
,
document.getElementById('root')
);
```
当你运行这段代码时,你会得到 `a key should be provided for list items(应该为列表提供一个key)` 这样的警告。"Key" 是创建元素列表时,需要包含的特殊字符串属性。我们接下来将会讨论为什么 “Key” 对于数组是非常重要的。
我们可以在 `numbers.map()` 方法中给列表项传递一个 `key` 属性,这样可以修复该问题。
```javascript{4}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
-
{number}
);
return (
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
,
document.getElementById('root')
);
```
[Try it on CodePen.](https://codepen.io/gaearon/pen/jrXYRR?editors=0011)
## Keys
Keys 会帮助 React 识别哪些项是修改、新增或者是移除掉的。Keys 会给数组元素一个固定的标识:
```js{3}
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
-
{number}
);
```
最好的选择 Key 的方式是使用一个唯一字符串来标识列表项。最常用的做法是使用数据项的ID作为 Key:
```js{2}
const todoItems = todos.map((todo) =>
-
{todo.text}
);
```
当需要渲染的项没有固定的ID时,你可以把项的索引当成 key:
```js{2,3}
const todoItems = todos.map((todo, index) =>
// Only do this if items have no stable IDs
-
{todo.text}
);
```
我们并不建议使用索引来作为 key,这可能会很慢。如果有兴趣,可以阅读 [深入理解Key的必要性](https://facebook.github.io/react/docs/reconciliation.html#recursing-on-children)。
### 带Key的组件拆分
Keys 仅仅在数组上下文才有意义。
例如,如果你拆分出一个 `ListItem` 组件,你需要将 Key 设置在 `` 元素上,而不是放在 `ListItem` 组件本身的 `- ` 上。(PS:设置在循环的ITEM上)
**示例: 不正确的Key用法**
```javascript{4,5,14,15}
function ListItem(props) {
const value = props.value;
return (
// 错误,这里并不需要指定key:
-
{value}
);
}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// 错误,这个需要指定key:
);
return (
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
,
document.getElementById('root')
);
```
**示例: 正确的 Key 用法**
```javascript{2,3,9,10}
function ListItem(props) {
// 正确,此处不需要指定key:
return - {props.value}
;
}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// 正确,应该在这里指定key:
);
return (
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
,
document.getElementById('root')
);
```
[Try it on CodePen.](https://codepen.io/rthor/pen/QKzJKG?editors=0010)
一个很好的经验法则就是 `map()` 方法内的元素需要key。
### Keys 必须在兄弟节点中唯一
数组元素中指定的 key 应该在当前列表中是独立无二的,但不需要全局唯一。我们可以在不同的数组中,使用相同的 key:
```js{2,5,11,12,19,21}
function Blog(props) {
const sidebar = (
{props.posts.map((post) =>
-
{post.title}
)}
);
const content = props.posts.map((post) =>
{post.title}
{post.content}
);
return (
{sidebar}
{content}
);
}
const posts = [
{id: 1, title: 'Hello World', content: 'Welcome to learning React!'},
{id: 2, title: 'Installation', content: 'You can install React from npm.'}
];
ReactDOM.render(
,
document.getElementById('root')
);
```
[Try it on CodePen.](https://codepen.io/gaearon/pen/NRZYGN?editors=0010)
Keys 为 React 提供了一些隐藏信息,但不会传递给您的组件。如果你的组件中需要相同的值,请通过其他不同的属性名称进行传递:
```js{3,4}
const content = posts.map((post) =>
);
```
以上这个例子,`Post` 组件能够读取到 `props.id`,但读取不到 `props.key`。
### 在 JSX 中嵌入 `map()`
在上面的例子中,我们定义了一个独立的 `listItems` 变量,并在 JSX 中进行了使用:
```js{3-6}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
);
return (
);
}
```
JSX 允许在 `{}` 中嵌入表达式,所以我们内联 `map()` 的结果:
```js{5-8}
function NumberList(props) {
const numbers = props.numbers;
return (
{numbers.map((number) =>
)}
);
}
```
[Try it on CodePen.](https://codepen.io/gaearon/pen/BLvYrB?editors=0010)
有时这会让代码更清晰,但这种风格也可能被滥用。像JavaScript一样,是否值得提取为变量是由你决定的。请记住,如果 `map()` 嵌套太多,那么是时候进行组件拆分了。
================================================
FILE: React面面观/【译】快速起步-条件渲染.md
================================================
---
title: 快速起步-条件渲染
date: 2017-4-20 15:38:23
react version: 15.5.0
---
# 条件渲染
在 `React` 中,你可以创建不同的组件来封装你需要的行为。然后,你可以通过应用程序的状态来决定渲染它们中的一部分。
`React` 中的条件渲染和 `JavaScript` 的条件表达式一致。使用像 [`if`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/if...else) 或者是 [条件运算符](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Conditional_Operator) 来创建表示当前状态的元素,并使用 `React` 更新UI。
思考下面两个组件:
```js
function UserGreeting(props) {
return Welcome back!
;
}
function GuestGreeting(props) {
return Please sign up.
;
}
```
我们创建一个 `Greeting` 组件,它将根据用户的登录状态,显示以上两个组件中的一个:
```javascript{3-7,11,12}
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn) {
return ;
}
return ;
}
ReactDOM.render(
// 尝试设置 isLoggedIn={true}:
,
document.getElementById('root')
);
```
[Try it on CodePen.](https://codepen.io/gaearon/pen/ZpVxNq?editors=0011)
这个例子将根据 `isLoggedIn` 属性的值来渲染不同的问候语。
### 元素变量
你可以使用变量来存储元素。这可以帮助您有条件的渲染组件的一部分,而其他的部分则不会更改。
思考下面两个表示注销和登录按钮的新组件:
```js
function LoginButton(props) {
return (
);
}
function LogoutButton(props) {
return (
);
}
```
在上面的例子中,我们将会创建一个名为 `LoginControl` 的有状态组件。
根据它自身的状态,它会渲染 `` 或者 `` 中的一个。它也会渲染上一个例子中的 ``:
```javascript{20-25,29,30}
class LoginControl extends React.Component {
constructor(props) {
super(props);
this.handleLoginClick = this.handleLoginClick.bind(this);
this.handleLogoutClick = this.handleLogoutClick.bind(this);
this.state = {isLoggedIn: false};
}
handleLoginClick() {
this.setState({isLoggedIn: true});
}
handleLogoutClick() {
this.setState({isLoggedIn: false});
}
render() {
const isLoggedIn = this.state.isLoggedIn;
let button = null;
if (isLoggedIn) {
button = ;
} else {
button = ;
}
return (
{button}
);
}
}
ReactDOM.render(
,
document.getElementById('root')
);
```
[Try it on CodePen.](https://codepen.io/gaearon/pen/QKzAgB?editors=0010)
虽然,申明一个变量,并使用 `if` 语句是进行条件渲染的好办法,但有时你可能想使用更短的语法。在 `JSX` 中有几种内联条件的写法,如下。
### 逻辑 `&&` 操作符实现内联if
`JSX` 允许在 `{}` 中嵌入表达式。这包含JS中的 `&&` 操作符。它有助于条件渲染一个元素:
```js{6-10}
function Mailbox(props) {
const unreadMessages = props.unreadMessages;
return (
Hello!
{unreadMessages.length > 0 &&
You have {unreadMessages.length} unread messages.
}
);
}
const messages = ['React', 'Re: React', 'Re:Re: React'];
ReactDOM.render(
,
document.getElementById('root')
);
```
[Try it on CodePen.](https://codepen.io/gaearon/pen/ozJddz?editors=0010)
它可以正常工作是因为在 `JavaScript`中,`true && expression` 总是执行 `expression`,且 `false && expression` 总是执行 `false`。(PS:短路表达式)
因此,如果条件是 `true`, `&&` 右侧的元素将会被渲染。如果为 `false`, `React` 将会跳过右侧的元素。
### 三目表达式实现内联if...else
另一个内联条件渲染的方法是使用 `JavaScript` 中的三目运算符 [`condition ? true : false`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Conditional_Operator)。
在下面的例子中,我们使用三目运算来条件渲染一小段文本。
```javascript{5}
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
The user is {isLoggedIn ? 'currently' : 'not'} logged in.
);
}
```
它也可以用于更大的表达式,虽然不够清晰:
```js{5,7,9}
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
{isLoggedIn ? (
) : (
)}
);
}
```
就像在 `JavaScript` 中一样,你可以根据您和您的团队的规范来选择更合适的风格。还需要记住,当条件变得越来越复杂的时候,那就应该进行组件拆分了。
### 阻止组件渲染
在极少数情况下,您可能希望组件隐藏自身,即使它是由另一个组件呈现的。为此,我们可以在 `render()` 中返回 `null` 来替代返回渲染元素。
在下面的示例中,`` 依赖 `warn` 属性的值来进行渲染。如果属性值是 `false`, 组件将不会被渲染:
```javascript{2-4,29}
function WarningBanner(props) {
if (!props.warn) {
return null;
}
return (
Warning!
);
}
class Page extends React.Component {
constructor(props) {
super(props);
this.state = {showWarning: true}
this.handleToggleClick = this.handleToggleClick.bind(this);
}
handleToggleClick() {
this.setState(prevState => ({
showWarning: !prevState.showWarning
}));
}
render() {
return (
);
}
}
ReactDOM.render(
,
document.getElementById('root')
);
```
[Try it on CodePen.](https://codepen.io/gaearon/pen/Xjoqwm?editors=0010)
从组件的 `render` 方法中返回 `null`,不会影响组件生命周期方法的触发。例如,`componentWillUpdate` 和 `componentDidUpdate` 仍然会被调用。
================================================
FILE: React面面观/【译】快速起步-渲染元素.md
================================================
---
title: 快速起步-渲染元素
date: 2017-4-14 14:10:50
react version: 15.5.0
---
# 渲染元素
`Elements` 是 `React` 应用中的最小单元。
一个元素定义了你会在屏幕上看到什么:
```js
const element = Hello, world
;
```
React 元素是很容易创建的纯对象,不同于浏览器的DOM元素。React DOM 负责从 React元素来更新DOM。
>**注意:**
>
>人们可能会将元素与更广为人知的组件混淆。我们将在 [下一节](【译】快速起步-组件与属性.md) 讨论组件。组件是由元素构成的,请不要跳过阅读本章节。
## 渲染元素到DOM中
在您的HTML文件中有一个 `` :
```html
```
我们称它为根节点,因为它的内容都将被React DOM管理。
使用 `React` 构建的应用程序通常拥有单个根节点。如果你是整合 `React` 到已经存在的应用中,你可以以你喜欢的方式使用多个独立的根节点。
通过 `ReactDOM.render()` 来将 `React 元素` 渲染到 根节点中:
```js
const element =
Hello, world
;
ReactDOM.render(
element,
document.getElementById('root')
);
```
[CodePen Demo](http://codepen.io/gaearon/pen/rrpgNB?editors=1010)
它会在页面上显示 "Hello, world"。
## 更新渲染完成的元素
React的元素是 [不可变的](https://en.wikipedia.org/wiki/Immutable_object). 当你创建好一个元素后,你就不能改变它的子集和它的属性。一个元素就代表了某个时间点的UI,就像电影中的帧一样。
到目前我们所知,更新UI的唯一方式就是创建一个新元素,并传递给 `ReactDOM.render()`。
以滴答作响的时钟为例:
```js{8-11}
function tick() {
const element = (
Hello, world!
It is {new Date().toLocaleTimeString()}.
);
ReactDOM.render(
element,
document.getElementById('root')
);
}
setInterval(tick, 1000);
```
[CodePen Demo](http://codepen.io/gaearon/pen/gwoJZk?editors=0010)
通过 [`setInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval) 的回调函数,每一秒都会调用 `ReactDOM.render()`。
>**注意:**
>
>在实际运用中,许多React应用都仅仅只会调用一次 `ReactDOM.render()`。在下一节,我们将会学习如何将这些代码封装为 [有状态组件](【译】快速起步-状态和生命周期.md)。
>
>我们建议不要跳过某些主题,因为它们彼此之间具有联系。
## React 只更新有必要的部分
React DOM将元素及其子元素与上一个进行比较,然后仅将变化了的内容更新到指定DOM上。
可以通过浏览器DOM检查工具来验证 [Demo](http://codepen.io/gaearon/pen/gwoJZk?editors=0010) 的局部更新,。
即使我们每时每刻都创建一个表示整个UI树的元素,也只有内容有变化的文本节点才会被React DOM更新。
以我们的经验,考虑UI在某个时刻的展现而不是考虑如何随时间去改变UI,可能会避免很多bug。
================================================
FILE: React面面观/【译】快速起步-状态和生命周期.md
================================================
---
title: 快速起步-状态和生命周期
date: 2017-4-13 16:43:28
version: 15.4.2
---
# 状态和生命周期
到目前为止,我们仅学到了一种更新UI的方法。
我们通过调用 `ReactDOM.render()` 来改变渲染输出:
```js{8-11}
function tick() {
const element = (
Hello, world!
It is {new Date().toLocaleTimeString()}.
);
ReactDOM.render(
element,
document.getElementById('root')
);
}
setInterval(tick, 1000);
```
[CodePen地址](http://codepen.io/gaearon/pen/gwoJZk?editors=0010)
在本节中,我们将学习如何使得 `Clock` 组件是可重用的和封装的。 它将创建自己的计时器并每秒更新一次。
我们可以用如下方式进行封装:
```js{3-6,12}
function Clock(props) {
return (
Hello, world!
It is {props.date.toLocaleTimeString()}.
);
}
function tick() {
ReactDOM.render(
,
document.getElementById('root')
);
}
setInterval(tick, 1000);
```
[CodePen地址](http://codepen.io/gaearon/pen/dpdoYR?editors=0010)
然而,它缺失了一个关键要求:在 `Clock` 组件的具体实现中,需要能创建它自己的定时器,并能每秒更新UI。
理想情况下,我们只需要写一次即可实现自更新的 `Clock`:
```js{2}
ReactDOM.render(
,
document.getElementById('root')
);
```
想要实现这个目标,我们需要给 `Clock` 组件添加 `state`。
`state` 和 `props` 类似,但它是组件内部管理的的私有变量。
我们 [之前提到](https://facebook.github.io/react/docs/components-and-props.html#functional-and-class-components),当使用 `Class` 定义组件时,可以使用一些附加的特性。本地状态就是这样的特性,仅在用 `Class` 定义组件时可用。
## 将 `Function` 转换为 `Class`
使用以下五步,可以将组件 `Clock` 从函数定义方式转换到类定义方式:
1. 创建一个同名的继承自 `React.Component` 的 [ES6 class](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Classes)。
2. 在类中添加空的 `render()`方法。
3. 将函数体内的代码复制到 `render()` 方法中。
4. 在 `render()` 方法中使用 `this.props` 来替代 `props`。
5. 删除多余的空函数定义。
```js
class Clock extends React.Component {
render() {
return (
Hello, world!
It is {this.props.date.toLocaleTimeString()}.
);
}
}
```
[CodePen地址](http://codepen.io/gaearon/pen/zKRGpo?editors=0010)
现在 `Clock` 组件是用类定义的,而不是函数定义的。
这让我们可以使用一些附加特性,如本地状态和生命周期钩子。
## 添加本地状态到类
通过以下三个步骤,我们将会把 `date` 从 `props` 移动到 `state` 中:
1) 在 `render()` 方法中,使用 `this.state.date` 来替代 `this.props.date` :
```js{6}
class Clock extends React.Component {
render() {
return (
Hello, world!
It is {this.state.date.toLocaleTimeString()}.
);
}
}
```
2) 添加 [class constructor](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Classes#Constructor) 并初始化 `this.state`:
```js{4}
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
render() {
return (
Hello, world!
It is {this.state.date.toLocaleTimeString()}.
);
}
}
```
注意我们是如何传递 `props` 到基类构造函数的:
```js{2}
constructor(props) {
super(props);
this.state = {date: new Date()};
}
```
类组件应始终传递 `props` 并调用基类构造函数。
3) 从 `
` 元素中移除 `date` 属性:
```js{2}
ReactDOM.render(
,
document.getElementById('root')
);
```
稍后,我们将定时器代码块添加到组件本身上。
代码看起来如下:
```js{2-5,11,18}
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
render() {
return (
Hello, world!
It is {this.state.date.toLocaleTimeString()}.
);
}
}
ReactDOM.render(
,
document.getElementById('root')
);
```
[CodePen地址](http://codepen.io/gaearon/pen/KgQpJd?editors=0010)
接下来,我们将使 `Clock` 启动自己的定时器,并每秒更新一次。
## 将生命周期方法添加到类
在包含多个组件的应用程序中,在组件释放时,销毁(清理)组件所占用的资源是非常重要的。
我们希望当 `Clock` 渲染到DOM时,第一时间 [启动定时器](https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval)。这个阶段在 `React` 中称之为 `mounting`。
我们也希望当 `Clock` 产生的DOM被移除时 [清除定时器](https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval) 。这个阶段在 `React` 中称之为 `unmounting`。
在组件的 `mounting` 和 `unmounting` 阶段,我们可以定义特定的方法来运行我们的代码:
```js{7-9,11-13}
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
}
componentWillUnmount() {
}
render() {
return (
Hello, world!
It is {this.state.date.toLocaleTimeString()}.
);
}
}
```
这些方法就被称之为生命周期钩子(lifecycle hooks)。
`componentDidMount()` 钩子在组件渲染到DOM之后运行,这是启动定时器的好地方:
```js{2-5}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
```
**注意我们是如何在 `this` 上保存定时器ID的**
`this.props` 是由 `React` 自身管理的,`this.state` 也具有特定的意义。当您需要存储与渲染无关的内容时,也可以手动的在类中添加其他字段。
与 `render()` 无关的内容,没必要存储在 `state` 中。
我们将会在 `componentWillUnmount` 生命周期钩子中清除定时器:
```js{2}
componentWillUnmount() {
clearInterval(this.timerID);
}
```
最后,我们将实现每秒运行的 `tick()` 方法。
它将使用 `this.setState()` 来实现组件本地状态的更新:
```js{18-22}
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date()
});
}
render() {
return (
Hello, world!
It is {this.state.date.toLocaleTimeString()}.
);
}
}
ReactDOM.render(
,
document.getElementById('root')
);
```
[CodePen地址](http://codepen.io/gaearon/pen/amqdNA?editors=0010)
现在 `Clock` 会每秒跳动。
我们来简单回顾下发生了什么以及调用方法的顺序:
1) 当 `
` 传递给 `ReactDOM.render()` 时,`React` 调用了 `Clock` 组件的构造函数。由于 `Clock` 需要显示当前时间,我们通过一个包含当前时间的对象来初始化 `this.state`。稍后,我们将更新这个 `state`。
2) 接下来,`React` 调用 `Clock` 组件的 `render()` 方法。这决定了 `React` 将会在屏幕上显示什么。接着,React 将组件的输出更新到DOM上。
3) 当 `Clock` 组件被添加到DOM之后,React会调用 `componentDidMount()` 钩子函数。 在它的内部, `Clock` 组件启动浏览器定时器,并每秒执行一次 `tick()`。
4) 每一秒,浏览器都会调用 `tick()` 方法。在它的内部,`Clock` 组件将包含当前时间的对象传递给 `this.setState()`,并以此来调度UI变更。当调用 `setState()` 方法,`React` 知道了 `state` 变化,然后就调用 `render()` 来确定要在屏幕上渲染的内容。这个时候, `render()` 中的 `this.state.date` 将和之前不同,会产生包含当前时间的新的输出。于是,React则对应的更新DOM。
5) 当 `Clock` 组件从DOM移除时,`React` 将调用 `componentWillUnmount()` 来停止定时器。
## 正确的使用 `state`
关于 `setState()`,您需要知道以下三点:
### 不要直接修改 `state`
如下代码将不会重新渲染组件:
```js
// Wrong
this.state.comment = 'Hello';
```
请使用 `setState()` 替代:
```js
// Correct
this.setState({comment: 'Hello'});
```
唯一可以给 `this.state` 赋值的地方是构造函数。
### 状态更新可能是异步的
为了提高性能,React可能会在单个更新中批量处理多个 `setState()`。
由于 `this.props` 和 `this.state` 可能是异步更新的,您不应该依靠它们的值来计算下一个状态。
以下代码可能不会正确更新计数器:
```js
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
```
要解决这个问题,可以 `setState()` 的第二种形式,接受一个函数。这个函数的第一个参数是上一个状态,第二个参数是当前属性:
```js
// Correct
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
```
以上代码我们使用了 [arrow function](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/Arrow_functions) ,但常规函数也是可以正常使用的:
```js
// Correct
this.setState(function(prevState, props) {
return {
counter: prevState.counter + props.increment
};
});
```
### 状态是合并更新
当你调用 `setState()` 时,React 会将您传递的对象合并到当前状态。
您的状态可能包含几个独立变量,如下:
```js{4,5}
constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}
```
您可以通过 `setState()` 更新其中一个:
```js{4,10}
componentDidMount() {
fetchPosts().then(response => {
this.setState({
posts: response.posts
});
});
fetchComments().then(response => {
this.setState({
comments: response.comments
});
});
}
```
这个合并是浅复制,所以 `this.setState({comments})` 完全不影响 `this.state.posts` ,但会整个替换 `this.state.comments`。
## 数据流向
父组件和子组件都不必要知道某个组件是有状态的还是无状态的,并且它们不应该关心它是否被定义函数或类。
这也是为什么 `state` 通常被称为本地状态或封装状态。因为除了组件本身之外,其他组件都是不能直接访问组件状态的。
父组件可以选择将状态通过子组件的属性进行传递:
```js
It is {this.state.date.toLocaleTimeString()}.
```
This also works for user-defined components:
```js
```
`FormattedDate` 组件将通过它自身的 `props` 接收到 `date`,不需要知道 `date` 是来自哪里(`Clock` 组件的状态或属性,乃至手动输入):
```js
function FormattedDate(props) {
return
It is {props.date.toLocaleTimeString()}.
;
}
```
[CodePen地址](http://codepen.io/gaearon/pen/zKRqNB?editors=0010)
这通常被称作 `从上到下` 或 `单向` 数据流。任何 `state` 始终由某个特定组件拥有,并且从该 `state` 导出的任何数据或UI都只会影响子集。
如果把组件树想象为瀑布,每个组件的状态都是一个额外的水源,它可以从任一点汇入,但都向下流动。
为了表明组件都是相互隔离的,我们可以创建一个包含三个 `Clock` 组件的 `App` 组件:
```js{4-6}
function App() {
return (
);
}
ReactDOM.render(
,
document.getElementById('root')
);
```
[CodePen地址](http://codepen.io/gaearon/pen/vXdGmd?editors=0010)
每个 `React` 有自己的定时器,并独立更新(并不会相互影响)。
在 `React` 应用中,组件有无状态被认为是可变的,它是组件的具体实现细节。你可以在有状态组件中使用无状态组件,反之亦然。
================================================
FILE: React面面观/【译】快速起步-状态提升.md
================================================
---
title: 快速起步-状态提升
date: 2017-4-25 17:27:42
version: 15.5.0
---
# 状态提升
通常,多个组件会响应同样的变化数据。我们建议将这种共享的状态提升到最近的共同的祖先。让我们看看它是如何工作的。
在这一节中,我们会创建一个温度计算器用来计算水是否在给定的温度下沸腾。
我们先创建一个 `BoilingVerdict`(沸腾判断)组件。它有一个叫 `celsius` (摄氏温度)的属性,并会打印水是否沸腾:
```js{3,5}
function BoilingVerdict(props) {
if (props.celsius >= 100) {
return
The water would boil.
;
}
return
The water would not boil.
;
}
```
下一步,我们创建一个叫 `Calculator`(计算器)的组件。它会渲染一个可让你输入摄氏温度的输入框,并将该输入框的值绑定到 `this.state.temperature` 上。
另外,它也会通过输入框的值渲染 `BoilingVerdict` 组件。
```js{5,9,13,17-21}
class Calculator extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {temperature: ''};
}
handleChange(e) {
this.setState({temperature: e.target.value});
}
render() {
const temperature = this.state.temperature;
return (
);
}
}
```
[Try it on CodePen.](http://codepen.io/valscion/pen/VpZJRZ?editors=0010)
## 添加第二个输入
我们的新需求是,我们不仅要提供摄氏温度输入,还需要提供华氏温度输入,并要自动进行转换。
我们从 `Calculator` 提取出一个叫 `TemperatureInput` 的组件,并运行传入 `"c"` 或者 `"f"` 给它的属性 `scale`:
```js{1-4,19,22}
const scaleNames = {
c: 'Celsius',
f: 'Fahrenheit'
};
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {temperature: ''};
}
handleChange(e) {
this.setState({temperature: e.target.value});
}
render() {
const temperature = this.state.temperature;
const scale = this.props.scale;
return (
);
}
}
```
现在我们来更新 `Calculator`,让它渲染两个独立的温度输入组件:
```js{5,6}
class Calculator extends React.Component {
render() {
return (
);
}
}
```
[Try it on CodePen.](http://codepen.io/valscion/pen/GWKbao?editors=0010)
现在,我们有两个输入框了,但是当我们在其中一个输入值时,另外一个并不会更新。这违背了我们的需求,我们想让它们保持同步。
此时,我们也不能从 `Calculator` 中展示 `BoilingVerdict`。 因为 `Calculator` 并不知道当前的温度,温度是隐藏在 `TemperatureInput` 组件内部的。
## 编写转换功能
首先,我们先编写两个函数来对华氏温度和摄氏温度进行转换:
```js
function toCelsius(fahrenheit) {
return (fahrenheit - 32) * 5 / 9;
}
function toFahrenheit(celsius) {
return (celsius * 9 / 5) + 32;
}
```
这两个函数将会转换不同的温度。我们还需要编写另一个函数,它将字符串 `temperature`(温度)和一个转换器作为参数,并返回一个字符串。我们将使用它来根据一个输入计算另外一个输入。
当 `temperature`(温度)不合法时,它会返回一个空字符串,它也会四舍五入到小数点后第三位:
```js
function tryConvert(temperature, convert) {
const input = parseFloat(temperature);
if (Number.isNaN(input)) {
return '';
}
const output = convert(input);
const rounded = Math.round(output * 1000) / 1000;
return rounded.toString();
}
```
比如,`tryConvert('abc', toCelsius)` 返回空字符串,`tryConvert('10.22', toFahrenheit)` 返回 `'50.396'`。
## 状态提升
目前,所有的 `TemperatureInput` 组件都将值作为本地状态保存在组件内部:
```js{5,9,13}
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {temperature: ''};
}
handleChange(e) {
this.setState({temperature: e.target.value});
}
render() {
const temperature = this.state.temperature;
```
然而,我们希望它们能够相互同步这个值。当我们更新摄氏温度时,华氏温度也会自动显示,反之亦然。
在 `React` 中,共享状态是通过将状态移动的最近的共同祖先来实现的。这被称之为 “状态提升”。我们将从 `TemperatureInput` 组件中移除本地状态,并在 `Calculator` 组件中保存状态。
如果 `Calculator` 拥有共享状态,它就成为了多个温度输入组件的的 “source of truth”(译者理解:唯一来源)。它可以让输入组件具有彼此一致的值。自从将 `TemperatureInput` 组件的属性提取到共同的父组件 `Calculator` 后,它们的输入值会总是同步的。
我们看看看它是如何一步步开始工作的。
首先,我们会在 `TemperatureInput` 组件中,使用 `this.props.temperature` 来替换 `this.state.temperature`。现在,先假设 `this.props.temperature` 总是存在的。将来,我们会在 `Calculator` 组件中传递给它:
```js{3}
render() {
// Before: const temperature = this.state.temperature;
const temperature = this.props.temperature;
```
我们知道 [属性是只读的](【译】快速起步-组件与属性.md)。当 `temperature` 是本地状态时,`TemperatureInput` 组件通过 `this.setState()` 来改变它。然而,`temperature` 成为了父组件的属性,`TemperatureInput` 组件就没有该属性的控制权了。
在 `React` 中,通常让组件 `controlled` 来解决该问题。就像DOM中的 `
` 接收一个 `value` 和 `onChange` 属性,自定义的 `TemperatureInput` 会从 `Calculator`(父组件)中接收 `temperature` 和 `onTemperatureChange`。
现在,当 `TemperatureInput` 想更新它的温度,可以调用 `this.props.onTemperatureChange`:
```js{3}
handleChange(e) {
// Before: this.setState({temperature: e.target.value});
this.props.onTemperatureChange(e.target.value);
```
注意自定义组件中的 `temperature` 和 `onTemperatureChange` 没有特殊的含义。我们也可以用其他名称代替,比如 `value` 和 `onChange` 这种常用的惯例。
组件中的 `onTemperatureChange` 属性将会通知父组件 `Calculator` 温度变化。父组件将通过修改自己的本地状态来处理更新,并为两个输入组件提供新的值。我们将很快看到新的 `Calculator` 实现。
在更新 `Calculator` 之前,我们先更新 `TemperatureInput`。先移除它的本地状态,使用 `this.props.temperature` 来替代`this.state.temperature`。同时,我们调用 `Calculator` 提供的 `this.props.onTemperatureChange()` 来替代自身的 `this.setState()`:
```js{8,12}
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.props.onTemperatureChange(e.target.value);
}
render() {
const temperature = this.props.temperature;
const scale = this.props.scale;
return (
);
}
}
```
现在,我们切换到 `Calculator` 组件。
我们将当前输入组件的 `temperature` 和 `scale` 存储到本地状态中。这是从输入组件中提升的状态,并为输入组件提供真实的值。为了呈现两个输入,我们需要知道所有数据的最小表示。
例如,我们在摄氏温度中输入了 37 ,`Calculator` 组件的状态将会是:
```js
{
temperature: '37',
scale: 'c'
}
```
如果我们之后在华氏温度中输入了 212,`Calculator` 组件的状态将会是:
```js
{
temperature: '212',
scale: 'f'
}
```
我们可以存储两个输入的值,但实际上是不必要的。存储最近更改的输入值,以及它所代表的比例就足够了。我们可以基于当前的温度(temperature)和温度类别(scale)来推断另一个输入的值。
这将是输入保持同步,因为它们的值是从相同的状态计算出来的:
```js{6,10,14,18-21,27-28,31-32,34}
class Calculator extends React.Component {
constructor(props) {
super(props);
this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
this.state = {temperature: '', scale: 'c'};
}
handleCelsiusChange(temperature) {
this.setState({scale: 'c', temperature});
}
handleFahrenheitChange(temperature) {
this.setState({scale: 'f', temperature});
}
render() {
const scale = this.state.scale;
const temperature = this.state.temperature;
const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;
return (
);
}
}
```
[Try it on CodePen.](http://codepen.io/valscion/pen/jBNjja?editors=0010)
现在,无论你是在哪个温度输入组件中输入,`Calculator` 组件中的 `this.state.temperature` 和 `this.state.scale` 都会更新。其中一个输入值,另一个的输入值总是基于它重新计算,所以随便输入哪个,值都会生效。
让我们回顾一下编辑输入框时会发生什么:
* React会调用DOM元素 `
` 上的 `onChange` 函数。在我们的示例中, 是 `TemperatureInput` 组件的 `handleChange` 方法。
* `TemperatureInput` 组件的 `handleChange` 方法会调用 `this.props.onTemperatureChange()` 并传入新的期望值。这是由父组件 `Calculator` 传递来的属性。
* 在渲染呈现之前,如果在 `TemperatureInput` 组件中输入摄氏温度,`onTemperatureChange` 将会调用父组件的 `handleCelsiusChange` 方法,如果是输入的华氏温度,`onTemperatureChange` 将会调用父组件的 `handleFahrenheitChange` 方法。 因此,会根据具体的输入,调用这两个方法中的一个。
* 在这些方法内部,`Calculator` 组件通过 `this.setState()` 通知 `React` 使用最新的输入值来重新渲染它自己。
* React 调用 `Calculator` 组件的 `render` 方法来确定需要展示的UI。所有输入框的值,都将通过当前温度和温度类型来重新计算。温度就在这个阶段进行转换的。
* 当 `Calculator` 传递给 `TemperatureInput` 的属性发生变化时,React调用 `TemperatureInput` 组件的 `render()` 方法来确定如何渲染。
* 最终,React DOM 使用期望的输入值,来更新DOM。在我们更新一个的时候,另一个输入框也就同步更新。
每个更新都会执行相同的步骤,以便输入保持同步。
## 经验教训
对于在 `React` 中更改的任何数据,应该有一个唯一的来源。通常,状态首先会被添加到使用它的组件中。然后,当其他组件也需要它的时候,可以将它提升到最近的公共祖先上,而不是尝试同步这些状态。我们应该使用 [单向数据流](【译】快速起步-状态和生命周期.md)。
提升状态会比双向绑定编写更多的样板代码,但它有一个好处,不容易制造bug。提升状态到单一组件后,仅有该组件可以修改它,这导致错误的可能性大大降低。此外,您可以实现任何自定义的逻辑来拒绝或者转换用户的输入。
如果一些东西可以通过 `props` 或者 `state` 得到,那么他可能并不需要放在共享状态中。例如,我们并没有存储 `celsiusValue` 和 `fahrenheitValue`,而是存储最后编辑的 `temperature` 和 `scale`。其他的值总是可以在 `render()` 中被计算出来。这使得我们可以清理或者四舍五入到其他字段,而不会在用户输入中丢失精度。
当你在UI中看到错误时,您可以使用 [React Developer Tools](https://github.com/facebook/react-devtools) 来检查属性,并向上查找组件树直至到负责更新的组件上。这可以让你跟踪这些错误来源:

================================================
FILE: React面面观/【译】快速起步-组件与属性.md
================================================
---
title: 快速起步-组件与属性
date: 2017-4-14 16:01:04
version: 15.5.0
---
# 组件与属性
组件允许您将UI拆分为多个独立的,可重用的部分。
概念上,组件就类似于 `JavaScript` 函数。它们接收任意的输入(`props`),返回用于描述屏幕显示内容的 `React elements`。
## 函数式组件和类组件
最简单定义组件的方式,是编写一个 `JavaScript` 函数:
```js
function Welcome(props) {
return
Hello, {props.name}
;
}
```
这个函数是一个合法的组件,因为它接收包含数据的单个 `props` 对象参数,返回了一个 `React elements`。我们称这种用JavaScript函数定义的组件为函数式组件。
你也可以使用 [ES6 class](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Classes) 来定义组件:
```js
class Welcome extends React.Component {
render() {
return
Hello, {this.props.name}
;
}
}
```
上述两个组件在视图展示上是等同的。
在 [下一章节](【译】快速起步-状态和生命周期.md) 我们会讨论类组件的一些附加特性。在这之前,我们先使用函数式组件来简化演示。
## 渲染组件
之前,我们仅仅遇到表示DOM标签的 `React elements`:
```js
const element =
;
```
然而,元素也可以表示用户定义的组件:
```js
const element =
;
```
当 `React` 发现元素表示一个用户定义的组件时,它将 `JSX` 属性作为单个对象传递给组件。我们称之为 `props`。
例如,以下代码在页面上渲染 "Hello, Sara":
```js{1,5}
function Welcome(props) {
return
Hello, {props.name}
;
}
const element =
;
ReactDOM.render(
element,
document.getElementById('root')
);
```
[CodePen Demo](http://codepen.io/gaearon/pen/YGYmEG?editors=0010)
我们回顾下这个例子中发生了什么:
1. 使用 `
` 元素调用 `ReactDOM.render()`。
2. React 将 `{name: 'Sara'}` 作为属性调用 `Welcome` 组件。
3. `Welcome` 组件将 `
Hello, Sara
` 元素作为结果返回。
4. React DOM 使用 `
Hello, Sara
` 来更新DOM。
>**警告:**
>
>总是用大写字母开头命名自定义组件
>
>例如, `
` 表示一个DOM标签,但是 `
` 表示一个组件,且需要作用域中存在 `Welcome` 变量。
## 复合组件
组件可以在其输出中引用其他组件。这使我们可以对不同级别的实现抽象为相同的组件。在 `React` 中,按钮、表单、对话框,屏幕都通常被抽象为组件。
例如,我们可以创建 `App` 组件来渲染多个 `Welcome` 组件:
```js{8-10}
function Welcome(props) {
return
Hello, {props.name}
;
}
function App() {
return (
);
}
ReactDOM.render(
,
document.getElementById('root')
);
```
[CodePen Demo](http://codepen.io/gaearon/pen/KgQKPr?editors=0010)
通常,新的React应用程序在最顶层都会有一个 `App` 组件。然而,如果你集成 `React` 到一个现有的应用程序,你可以在内部先使用像 `Button` 这样的小部件,然后逐步的替换到最顶层。
>**警告:**
>
>组件必须返回单个根元素。这是为什么我们用 `
` 来包含多个 `
` 组件。
## 提取组件
不要害怕将组件分成更小的组件。
例如,思考这个 `Comment` 组件:
```js
function Comment(props) {
return (
{props.author.name}
{props.text}
{formatDate(props.date)}
);
}
```
[CodePen Demo](http://codepen.io/gaearon/pen/VKQwEo?editors=0010)
它接收 `author`(对象类型),`text`(字符串)和 `date`(日期) 作为属性,然后在社交网站上呈现一个评论。
由于多层嵌套,这个组件很难变化,且很难重用它其中的各个部分。我们可以从中提取几个组件。
首先,我们提取 `Avatar` 组件:
```js{3-6}
function Avatar(props) {
return (

);
}
```
`Avatar` 不需要知道谁会渲染它。这也是为什么我们使用 `user` 这个更通用的属性名称来替代 `author`。
我们建议从组件自身的角度来命名属性,而不是它的使用者。
我们现在已经稍微简化了一下 `Comment` 组件:
```js{5}
function Comment(props) {
return (
{props.text}
{formatDate(props.date)}
);
}
```
下一步,我们将提取 `UserInfo` 组件,用于在用户名旁边显示 `Avator`:
```js{3-8}
function UserInfo(props) {
return (
);
}
```
这让我们进一步简化了 `Comment` 组件:
```js{4}
function Comment(props) {
return (
{props.text}
{formatDate(props.date)}
);
}
```
[CodePen Demo](http://codepen.io/gaearon/pen/rrJNJY?editors=0010)
提取组件可能是看起来很繁琐的工作,但是在较大的应用程序中,可重用的组件能够付出更小的代价。
如果您的UI的一部分使用了几次(按钮,面板,头像)或者它自身就很复杂(App, Comment),它们则是拆分出可重用组件的最佳候选人,这是一个相当实用的经验法则。
## 属性是只读的
当你定义一个组件时,它不应该修改自己的 `props`。思考下面的 `sum` 函数。
```js
function sum(a, b) {
return a + b;
}
```
这些不会修改输入参数,且当输入参数相同时,总是返回相同结果的函数被称之为 [纯函数](https://en.wikipedia.org/wiki/Pure_function)。
相比之下,以下的函数是不纯的,因为它改变了自己的输入:
```js
function withdraw(account, amount) {
account.total -= amount;
}
```
`React` 非常灵活,但是也有一个严格的规则:
**所有的React组件必须像纯函数那样使用 `props`(也就是不要在组件内部修改 `props`)**
当然,应用程序的UI是动态的,可以随时间的推移而变化。在 [下一节](【译】快速起步-状态和生命周期.md) 中,我们将介绍新的概念 `state`。`state` 允许React组件根据用户操作,网络响应以及其他随时间推移产生的变化来修改组件输出,而不会违背该规则。
================================================
FILE: React面面观/【译】快速起步-组合与继承.md
================================================
---
title: 快速起步-组合与继承
date: 2017-4-26 13:19:51
version: 15.5.0
---
# 组合 VS. 继承
`React` 具有很强大的组合模型,我们推荐使用组合而不是继承来重用组件之间的代码。
在这一章节中,我们将思考一些React新手通常遇到的问题,并展示如何使用组合来解决它们。
## 容器
一些组件并不能提前知道它们的子集。这在通用盒子组件,如 `Sidebar` 或者 `Dialog` 上尤其常见。
我们推荐这类组件使用特定的 `children` 属性,将子元素传递到其输出中:
```js{4}
function FancyBorder(props) {
return (
{props.children}
);
}
```
这允许其他组件通过嵌套JSX传递任意的子集到组件内部:
```js{4-9}
function WelcomeDialog() {
return (
Welcome
Thank you for visiting our spacecraft!
);
}
```
[Try it on CodePen.](http://codepen.io/gaearon/pen/ozqNOV?editors=0010)
任何JSX标签 `
` 内部的内容,都会作为 `children` 属性传递给 `FancyBorder` 组件。由于 `FancyBorder` 会在 `` 中渲染 `{props.children}`,传递的元素将会出现在最终输出中。
虽然这不太常见,但有时您可能需要在组件中设置多个“孔”(holes,译者注:插槽)。在这种情况下,您可以采用您自己的约定,而不是使用 `children`:
```js{5,8,18,21}
function SplitPane(props) {
return (
{props.left}
{props.right}
);
}
function App() {
return (
}
right={
} />
);
}
```
[Try it on CodePen.](http://codepen.io/gaearon/pen/gwZOJp?editors=0010)
像 `
` 和 `
` 这样的React元素只是对象,所以你可以像传递其他数据一样将其作为属性传递。
## 具象化
有时我们认为某些组件是其他组件的“特殊情况”。比如,我们认为 `WelcomeDialog` 是一个特殊的 `Dialog`。
在React中,这也可以通过组合来实现,其中更“特殊”的组件会渲染更“通用”的组件,并且使用属性来配置通用组件:
```js{5,8,16-18}
function Dialog(props) {
return (
{props.title}
{props.message}
);
}
function WelcomeDialog() {
return (
);
}
```
[Try it on CodePen.](http://codepen.io/gaearon/pen/kkEaOZ?editors=0010)
组合对应定义为类的组件同样适用:
```js{10,27-31}
function Dialog(props) {
return (
{props.title}
{props.message}
{props.children}
);
}
class SignUpDialog extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleSignUp = this.handleSignUp.bind(this);
this.state = {login: ''};
}
render() {
return (
);
}
handleChange(e) {
this.setState({login: e.target.value});
}
handleSignUp() {
alert(`Welcome aboard, ${this.state.login}!`);
}
}
```
[Try it on CodePen.](http://codepen.io/gaearon/pen/gwZbYa?editors=0010)
## 那么继承呢?
在Facebook,我们在数以千计的组件中使用React,并且我们没有发现任何用例会要求我们创建组件继承层次结构。
当你需要用安全的方式定制组件的外观和形式时,属性和组合给了你所有的灵活性。 请记住,组件接受任意props,包括原始值、React元素或函数。
如果你想在组件之中重用非UI的功能,我们建议您将其提取到单独的 `JavaScript` 模块中。组件可以导入它,并在不扩展它的情况下,使用哪些函数,对象或者是类。
================================================
FILE: React面面观/【译】快速起步-表单.md
================================================
---
title: 快速起步-表单
date: 2017-4-28 08:35:09
version: 15.5.0
---
# 表单
HTML的表单元素和React中的表单元素有所不同,因为表单元素会保持一些内部状态。例如,以下是一个接收单个name的纯HTML表单:
```html
```
当用户提交表单时,该表单具有打开新页面的默认行为。这种用法放在React中也是可用的。但是,大多数时候,我们希望使用JavaScript函数处理表单提交并能方便的访问用户输入的表单数据。实现这一点的标准方式是使用一种称之为受控组件(controlled components)的技术。
## 受控组件
在HTML中,诸如 `
`, `
Page2
Page2
Page1
Page1
Page2
```
从浏览器打开 ``http://localhost:8001``,然后点击iframe中的按钮,会出现一个如下提示:
```
(index):15 Uncaught SecurityError: Blocked a frame with origin "http://localhost:8003" from accessing a frame with origin "http://10.16.85.170:8001". Protocols, domains, and ports must match.
```
简而言之,就是不允许跨域访问。
## 2、方式1 - 通过修改domain来实现跨域访问
该方式适合主域相同,而子域不同的场景。此时可以在多个iframe中,通过修改document.domain = '主域' 的方式来实现跨域。
**特定场合适用,不推荐**
## 3、方式2 - 通过 ``window.name`` 跨域访问
该方式原理是通过先请求其他的域的页面,把要传输的值赋值给 ``window.name`` 属性,然后把该iframe的src地址,修改为不跨域的一个页面。此时,由于是同一个iframe,所以name还是之前的数据,通过这样的方式变相的来获取其他域的数据。
示例如下:
在子页面中,仅仅需要把数据赋值给 ``window.name``
```html
// localhost:8003/index.html
...
...
```
父页面中,需要修改iframe为不跨域,然后获取数据
```html
// localhost:8001/index.html
...
...
```
这种方式实现起来,比较别扭,另外只能获取单次数据,并不友好,**不推荐使用**。
## 4、方式3 - 通过 ``navigator`` 对象来跨域(已过期,不能使用了)
该方式利用多个iframe窗口,访问的 ``navigator`` 对象都是同一个,而且并没有跨域问题这个原理;通过在该对象上注册和发送事件的方式来跨域访问。
## 5、通过 ``window.postMessage`` 传递消息
这是IE8+之后正统的iframe跨域解决方案,全称“跨文档消息”,是一个HTML5的新特性。
```javascript
otherWindow.postMessage(message, targetOrigin, [transfer]);
```
其中第一个参数是消息对象,允许JS数据类型,第二个是要发送到的域,可以设置为 ``*`` 表示不限制。
使用如下:
```html
// localhost:8001/index.html
...
...
```
```html
// localhost:8003/index.html
...
...
```
打开 ``http://localhost:8001`` 就可以看到父页面已经收到子页面发过来的消息了。
evt对象有几个重要的属性需要我们去了解一下:
1. data // 具体发送的数据
2. origin // 发送者origin(http://localhost:8003)
3. source // 发送者(window对象)
**该方式是当前最合适的跨文档通信方式,如果没有兼容IE6、7的需求,建议全部使用该方式。**
================================================
FILE: 前端相关/JSONP详解.md
================================================
---
title: JSONP详解
date: 2017/02/21 14:47:11
---
## 0、关于JSONP
### 什么的JSONP
JSONP(JSON with Padding)是资料格式 JSON 的一种“使用模式”,可以让网页从别的网域要资料。另一个解决这个问题的新方法是跨来源资源共享。(参考:[https://zh.wikipedia.org/wiki/JSONP](https://zh.wikipedia.org/wiki/JSONP))
### JSONP的起源
1. 曾经的Ajax不能跨域请求(现在的也不能,不过有cors)
2. Web上使用script调用js文件不存在跨域问题(实际上,只要拥有src属性的标签都允许跨域,比如script,img,iframe)
3. 那个时候,想要通过web端跨域访问数据,只可以在服务器端设法把数据装进js,然后客户端调用
4. 刚好这个时候JSON大行其道
5. 所以,解决方案就出来,web端像调用脚本一样来跨域请求服务器上动态生成的js文件
6. 为了便于客户端使用数据,逐渐形成了一种非正式传输协议,人们把它称作JSONP。
### JSONP用来做什么
通过JSONP的起源,我们大概也知道了JSONP就是为了跨域资源访问的。
## 1、JSONP实现原理
我们知道,在script标签中请求的js代码,到客户端之后,是能被自动执行的。
我们先构造一个后端(采用node实现):
var http = require('http');
var server = http.createServer((req, res) => {
var sendObj = {
url: req.url,
name: 'test'
};
res.write(`callback(${JSON.stringify(sendObj)})`);
res.end();
});
server.listen(9999, () => {
console.log('started.')
});
我们要使用这个这个数据呢?可以用Ajax,可能会产生跨域问题
另外,可以用如下写法: