学习Vue过程中,记下与Angular相比较的笔记
VS Code
Win-10 v1903
NodeJs: v10.14.1
npm: 6.4.1
angular/cli: 8.3.20
rxjs: 6.4.0
Angular的起源这里只简单的介绍一下,有兴趣的可以自行搜索。
AngularJs诞生于2009年,起先也是类似于现在VueJs一样的,只需要html页面中导入相应的script库,即可使用,有兴趣的可以稍微看看我的AngularJS-1.6.6-Tutorial稍作了解。
从Angular2开始,Angular变成了一个完整的前端框架且主要使用TypeScript。之后为了与之前的AngularJs区分并且对Angular本身进行了很大的改变,跨过了Angular3,直接到了Angular4,并且计划每半年发布一次新版本。至今已是Angular8 (Angular9照计划应该已经推出,但是每次都会晚一些)。
- 安装Angular需要首先安装NodeJs和npm,搜索一下nodejs即可找到安装包,windows下npm也会随着nodejs安装,安装完成后使用
node --version
和npm --version
检查是否安装成功。 - 打开终端(cmd,powershell,windows terminal都可以),运行
npm install -g @angular/cli
,等待安装结束 - 运行
ng --version
查看是否安装成功
只有一步,导入<script src="https://cdn.jsdelivr.net/npm/vue"></script>
即可。
由于本人之前只用过Angular,vue的经验不多,具目前的使用来看大概分为几个部分,我将一一对比Angular和vuejs的代码。所有的代码我都会放在stackblitz: vuejs和angular
index.html
<body>
<div id="app">
{{message}}
</div>
</body>
index.js
// Import stylesheets
import './style.css';
import Vue from 'vue'
// Write Javascript code!
var vm = new Vue({
el:'#app', //绑定到id为app的元素,不可使用body
data:function(){ //插入值变量
return{
message:'hello world'
}
}
})
即可得到
在stackblitz里可以直接创建一个空的angular项目,也可以本地使用ng new app 创建一个本地项目,这里推荐在stackblitz因为既快又方便。
app.component.ts //等价于index.js,使用ts编写
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
message = 'hello world';
}
app.component.html
{{message}}
这里可能会奇怪为什么没有完整的body和其他的tag,原因在于angular已经帮你绑定好了,你会看到项目内存在一个index.html的文件,如果打开你会看到一行代码<my-app>loading</my-app>
。这里的my-app
与app.component.ts
里selector: 'my-app'
是一致的,而my-app
就相当于vue中的根元素<div id="app"> </div>
。
如果运行程序可以看到相同的结果
在mvvm中,双向绑定是一个非常方便且很有必要的功能,在纯js中,如果我们有两个input让用户来输入他的邮箱和密码,再点击登录按钮登录,需要使用类似的代码
var email = document.getElementById('email').val();
var password = document.getElementById('password').val();
//code to send email & password
而在angular和vue中,如果我们使用了双向绑定,变量会自动得到当前的值而非手动
首先,我们需要在index.js中的data设一个变量,如果没有默认值可以设为空值email:'x@zx.com'
第二步,在index.html中添加如下代码
<!-- 双向绑定 -->
<hr/>
<div>
<h4>双向绑定</h4>
<input type="" v-model="email"/>
<div>最新的email为:{{email}}</div>
</div>
在angular中,第一次双向绑定会比vue多一步,而之后则是一样的先设变量再绑定。这是因为angular中的模块较多,需要手动添加需要的模块。
- 在app.module.ts中导入
import { FormsModule } from '@angular/forms';
,再在@NgModule里的imports中添加FormsModule
- 添加email变量
- 使用[(ngModel)]绑定
修改后的app.component.html代码如下
<hr />
<div>
<h4>双向绑定</h4>
<input type="" [(ngModel)]="email"/>
<div>最新的email为:{{email}}</div>
</div>
可以发现代码几乎完全一样,唯一的却别在于[(ngModel)]="email"
和v-model="email"
。
与双相绑定一样,angular和vue的事件绑定也非常类似。
- 在Vue对象中添加新的field-methods,在methods中定义方法。修改后的js如下:
var vm = new Vue({ el:'#app', data:function(){ return{ message:'hello world', email:'x@zx.com', count: 0 } }, methods:{ addOne:function(){ this.count+=1; } } })
- 添加对应的html并使用
v-on:click="addOne"
将按钮与事件绑定<!-- 事件 --> <hr/> <div> <h4>事件</h4> <button v-on:click="addOne">加1</button> <div>最新的count为:{{count}}</div> </div>
效果如下:
- 在html文件中添加如下代码
<!-- 事件 --> <hr/> <div> <h4>事件</h4> <button (click)="addOne()">加1</button> <div>最新的count为:{{count}}</div> </div>
- 在ts文件中添加count变量并添加函数,修改后的
AppComponent
class如下:export class AppComponent { message = 'hello world'; email:string = 'x@zx.com'; count:number = 0; addOne(){ this.count++; } }
即可得到相同的效果,主要区别在于vue使用v-on:click="addOne"
而angular使用(click)="addOne()"
,注意angular中的函数括号不可省略
首先来看条件,从前面的插值、双向绑定和事件,能看出来vue和angular是非常相似的,这一点在条件和循环调用也是一样的。
- 在html文件中添加如下代码:
<!-- 条件 --> <hr/> <div> <h4>条件</h4> <div v-if="showMsg">这是一条信息</div> <div v-else>这是另一条信息</div> <button @click="changeShowMsg">改变showMsg</button> </div>
- 在Vue实例中添加showMsg变量和changeShowMsg函数如下
showMsg: true
changeShowMsg:function(){ this.showMsg = !this.showMsg; }
注意当v-if的变量表达式为false时,dom中是看不到这个元素的。效果如下:
- 在html中添加如下代码:
<!-- 条件 --> <hr/> <div> <h4>条件</h4> <div *ngIf="showMsg;else anotherMsg">这是一条信息</div> <ng-template #anotherMsg> <div >这是另一条信息</div> </ng-template> <button (click)="changeShowMsg()">改变showMsg</button> </div>
- 在ts文件中加入变量和方法如下:
showMsg:boolean = true;
changeShowMsg(){ this.showMsg = !this.showMsg; }
这里我们发现,angular的else是和if放在一起的*ngIf="showMsg;else anotherMsg"
。而在else的模板中需要使用指令赋予一个名字#anotherMsg
,这样angular才会知道else去渲染什么模板。并且这个模板必须使用ng-template
,在angular中ng-template
并不会真正的生产一个tag,当渲染时,会自动去除。
这里可能会认为angular这里会复杂一些,但是根据我的理解,事实并非如此,我们对vue中的条件稍作修饰如下:
<!-- 条件2 -->
<hr/>
<div>
<h4>条件2</h4>
<template v-if="showMsg2">
<div>这是一条信息</div>
<div>这是一条信息</div>
</template>
<template v-else>
<div>这是另一条信息</div>
<div>这是另一条信息</div>
</template>
<button @click="changeShowMsg2">改变showMsg</button>
</div>
当我们的if-else模板中的tag多余一个,我们也必须使用template
,这样就和angular基本一致了。唯一的区别在于angular的else是需要放在*ngIf
语句中,而vue是分开的。
接下来是另一个重要的指令-循环
假设我们有一个包含n个Person
对象的数组People
,如果不使用任何框架(包括mvc),那么n个对象以为着我们要重复的写n遍html来显示所有对象的字段,而如果用循环,只需要写一次并放入循环即可。
-
在data中添加一个数组如下:
people:[ {name: 'Jack', phone: '123-123-3456', addr: 'NYC'}, {name: 'Tom', phone: '123-123-1234', addr: 'CT'}, {name: 'Bell', phone: '123-123-5678', addr: 'CL'} ]
-
在css中添加table样式如下:
td,th{ border:1px solid; padding: 5px; } table{ border-collapse:collapse; text-align:center; }
-
在html添加如下代码:
<!-- 循环 --> <hr/> <h4>循环</h4> <div> <table> <thead> <th>Name</th> <th>Phone</th> <th>Address</th> </thead> <tbody> <tr v-for="person in people"> <td>{{person.name}}</td> <td>{{person.phone}}</td> <td>{{person.addr}}</td> </tr> </tbody> </table> </div>
注意v-for="person in people"
这里的people是data
里的person
,名字要保持一致,而person
可以当成我们赋予的本地变量,就像c#
中的for(var item in people)
一样
无论数组中有多少个item,都会自动渲染出来,效果图如下:
angular的代码和vue并不会有太大的区别,但是因为angular是用ts的,我们可以指定Person
和People
的类型,也可以不指定而使用js风格,代码如下:
ts
修改后的app.component.ts
代码如下
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
message = 'hello world';
email: string = 'x@zx.com';
count: number = 0;
showMsg: boolean = true;
people:Person[] = [
new Person('Jack','123-123-3456','NYC'),
new Person('Tom','123-123-1234','CT'),
new Person('Bell','123-123-5678','CL'),
];
addOne() {
this.count++;
}
changeShowMsg() {
this.showMsg = !this.showMsg;
}
}
export class Person{
constructor(public name:string, public phone:string, public addr:string){}
}
若不想声明一个Person类,下面的代码等效:
people = [
{name: 'Jack', phone: '123-123-3456', addr: 'NYC'},
{name: 'Tom', phone: '123-123-1234', addr: 'CT'},
{name: 'Bell', phone: '123-123-5678', addr: 'CL'}
]
html:
<!-- 循环 -->
<hr />
<h4>循环</h4>
<div>
<table>
<thead>
<th>Name</th>
<th>Phone</th>
<th>Address</th>
</thead>
<tbody>
<tr *ngFor="let person of people">
<td>{{person.name}}</td>
<td>{{person.phone}}</td>
<td>{{person.addr}}</td>
</tr>
</tbody>
</table>
</div>
可以发现html中唯一的区别在于是:
vue: v-for="person in people"
和 angular: *ngFor="let person of people"
。
从前面的几个部分可以看出来,如果你会写vue,那么angular其实也会了,反之亦然。
在vue的官方文档中,vue实例有一个叫props的字段,可以定义传入的变量名,这样可以在调用自定义组件时绑定相应。假设我们有一个自定义的子组件,组件接受一个数组并在表格中显示所有的数据。就拿之前的一步做例子,我们新建并注册一个vue的component。
var personComponent = Vue.component('personComponent',{
props:['people'],
template:`
<div>
<table>
<thead>
<th>Name</th>
<th>Phone</th>
<th>Address</th>
</thead>
<tbody>
<tr v-for="person in people">
<td>{{person.name}}</td>
<td>{{person.phone}}</td>
<td>{{person.addr}}</td>
</tr>
</tbody>
</table>
</div>
`
})
注册component必须放在vue的实例之前
这里我们把前一步的table和循环放在了一个子组件里,这样我们可以在任意的地方调用这个子组件并传入相应的值。当我们需要在不同的地方使用同一块html的时候,组件会非常有用。
调用:
<!-- 子组件传入 -->
<hr/>
<h4>子组件传入</h4>
<person-component v-bind:people="people"></person-component>
这里v-bind:people="people"
中v-bind:people
里的people
是指的子组件里的props的值,而="people"
是指的父组件(调用组件域)中的data里的people
变量。
注意: 可能会疑惑为什么我们注册的component的名字为personComponent
而调用的时候是使用<person-component>
,这里就不多赘述,详情请看vue官方文档
组件化或者模块化可以把复杂、大量的html分成简洁明了的代码,这在维护一个非常大、复杂的页面时是非常有用的。
在一个angular项目中添加一个component后需要主动导入到根module中,这是因为避免加载过多的未使用的component而导致项目体积过大。*如果你使用angular cli,则使用ng g component <component-name>
会自动导入。在stackblitz中由于无法使用angular cli
,则必须手动添加component和导入,步骤如下:
-
在app目录下新建一个文件夹名为
person-component
-
在
person-component
文件下添加person-component.component.ts
,person-component.component.html
,person-component.component.css
三个文件 -
在ts文件中添加如下代码:
import { Component, Input } from '@angular/core'; @Component({ selector: 'person-component', templateUrl: './person-component.component.html', styleUrls: ['./person-component.component.css'] }) export class PersonComponent { @Input() people:any; }
-
在html文件中添加如下代码:
<div> <table> <thead> <th>Name</th> <th>Phone</th> <th>Address</th> </thead> <tbody> <tr *ngFor="let person of people"> <td>{{person.name}}</td> <td>{{person.phone}}</td> <td>{{person.addr}}</td> </tr> </tbody> </table> </div>
-
打开
app.module.ts
文件并在导入部分添加import { PersonComponent } from './person-component/person-component.component';
,再在declarations
数组中添加PersonComponent
,导入完成。 -
在
app.component.html
中添加如下代码:<!-- 子组件传入 --> <hr /> <h4>子组件传入</h4> <person-component [people]="people"></person-component>
即可得到相同的效果,效果图如下:
解析:
vue中的props
等价于angular中@Input()
vue中v-bind:people="people"
等价于angular中的[people]="people"
。
有传入就有传出,当我们需要把子组件的事件上浮到父组件中处理时,就需要把事件上浮。还是首先来看vue
-
在子组件
personComponent
的模板中的<tbody>
末尾添加, 点击按扭emit一个名为change-person
的事件,需要在父组件中绑定,并传出参数person<td> <button @click="$emit('change-person',person)">Change</button> </td>
-
在html中添加代码,监听子组件中的事件名称并绑定方法:
注意这里的
v-on:change-person="changePerson"
中的change-person与模板中的$emit一致,而changePerson是父组件中的方法名称<!-- 子组件事件上浮 --> <hr/> <h4>子组件事件上浮</h4> <person-component v-bind:people="people" v-on:change-person="changePerson"></person-component>
-
在父组件中添加方法如下,这个方法可以将子组件传出的值打印在控制台以方便我们观察:
changePerson:function(event){ console.log(event); }
最终的效果如下:
接下来是angular,在angular中的传入是Input,那么你可能已经猜到了上浮有可能是Output,答对了!
-
首先修改子组件的ts文件,添加一个Output的事件变量并导入
EventEmitter
和Output
的类,最后再添加事件上浮的方法:import { Component, Input, Output, EventEmitter } from '@angular/core';
@Output() selectedPerson = new EventEmitter(); changePerson(e){ this.selectedPerson.emit(e); }
-
修改子组件html文件,添加按钮和事件,angular的话子组件里只需要设置参数即可:
<td> <button (click)="changePerson(person)">Change</button> </td>
-
在父组件
app.component.html
中添加子组件并绑定本地方法,这里的selectedPerson
与子组件中Output变量名一致,也可以设置其他名字,具体步骤可以搜索angular的官方文档:<!-- 事件上浮 --> <hr /> <h4>事件上浮</h4> <person-component [people]="people" (selectedPerson)="changePerson($event)"></person-component>
app.component.ts:
changePerson(e){ console.log(e); }
对比angular和vue可以发现他们的流程都是一样的,几个步骤如下:
- 在子组件中emit一个事件
- 在父组件中设定一个变量和方法与上浮的事件进行绑定
- 调用方法