component(组件)类似删减版的directive(指令),用法上大同小异,但两者在使用角度仍然存在不少差异,那么本文将详细对比两者,加深大家的认知,那么本文开始。
指令和组件的区别
Directive | Component | |
---|---|---|
bindings(用于父传值子) | NO | YES(绑定至控制器) |
bindToController | YES | NO |
compile function(编译函数) | YES | NO |
controller | YES | YES |
controllerAs | YES(默认flase) | YES(默认$ctrl) |
link functions(链接函数) | YSE | NO |
multiElement | YES | NO |
priority(组件优先权) | YES | NO |
replace | YES | NO |
require | YES | YES |
restrict | YES | NO(仅支持元素) |
scope | YES(绑定至scope) | NO(作用域总是隔离) |
template | YES | YES |
templateNamespace | YES | NO |
templateUrl | YES | NO |
terminal(优先权低的组件是否执行) | YES | NO |
transclude | YES(默认false) | YES(默认false) |
1.创建与使用方式不同
在创建上,directive在指令名后是一个回调函数,函数内返回一个包含指令配置的对象,而component在组件名后紧接一个包含组件配置的对象。
在使用上,directive支持EMAC,即元素注释属性与类名,而component仅支持元素,因此component没有restrict,terminal,replace此类属性。
<!-- 指令 -->
<!-- directive:directive-name -->
<directive-name></directive-name>
<div directive-name></div>
<div class="directive-name"></div>
<!-- 组件 -->
<component-name></component-name>
angular.module('myApp', [])
.controller('myCtrl', function ($scope) {})
.directive('directiveName', function () {
return {
//定义属性配置
}
})
.component('componentName', {
//定义属性配置
});
2.模板使用不同
指令directive在使用模板,不管是template或者templateUrl,都要求模板代码用一个根元素进行包裹,但component并没有这个要求。
angular.module('myApp', [])
.controller('myCtrl', function ($scope) {})
.directive('directiveName', function () {
return {
template: '<span>1</span><span>2<span>', //错误
template: '<div><span>1</span><span>2<span></div>' //正确
}
})
.component('componentName', {
//定义属性配置
template: '<span>1</span><span>2<span>', //不会报错
});
3.父子传值表现不同
我们知道指令directivescope传值directive绑在scope上,component绑在this上,所以component要使用钩子函数。当然directive可以使用bindTocontroller让传值也绑定在this上。
我们知道component自带隔离作用域,而directive是否隔离由scope属性决定,false不创建作用域,true创建作用域但不隔离,{}创建隔离作用域。
当拥有隔离作用域时,父子互不相关,所以子无法继承父作用域中的任何数据,component得使用bindings传值,而directive得使用scope:{}传值:
<!DOCTYPE html>
<html lang="en" ng-app="app">
<head>
<meta charset="UTF-8">
<title>parser_uppercase</title>
<script src="https://www.orchome.com/user/js/angular-1.5.8.min.js"></script>
</head>
<body>
<div ng-controller="MyCtrl as vm">
<echo1 name="name" age="vm.age"></echo1>
<echo2 name="name" age="vm.age"></echo2>
</div>
<script type="text/javascript">
var app = angular.module('app',[]);
app.controller('MyCtrl', function($scope){
$scope.name = 'OrcHome';
this.age = 5;
});
app.directive('echo1', function(){
return {
restrict: 'AE',
replace: true,
scope: {
name: '<',
age: '<'
},
template: '<div>{{name}}{{age}}</div>',
controller:function($scope) {
console.log($scope,this)
}
}
});
app.component('echo2', {
bindings: {
name: '<',
age: '<'
},
template: '<div>{{$ctrl.name}}{{$ctrl.age}}</div>',
controller:function($scope) {
console.log($scope,this);
}
});
</script>
</body>
</html>
在这个例子中,我们在父级控制器中分别在scope以及this上绑定了两条数据,并分别传递给指令echo1与组件echo2,可以看到在两者的模板中使用是有差异的,指令使用传递过来的数据更像$scope
的写法,而组件更像构造器。
这是因为directive传值默认是绑定在scope上的,而component传值默认绑定在控制器上,我们可以分别打印两者的scope与this,首先是directive:
m {$id: 3, $$childTail: null, $$childHead: null, $$prevSibling: null, $$nextSibling: null, …}
$$childHead: null
$$childTail: null
$$destroyed: false
$$isolateBindings: {name: {…}, age: {…}}
$$listenerCount: {$destroy: 2}
$$listeners: {$destroy: Array(2)}
$$nextSibling: m {$id: 4, $$childTail: null, $$childHead: null, $$prevSibling: m, $$nextSibling: null, …}
$$phase: null
$$prevSibling: null
$$watchers: (2) [{…}, {…}]
$$watchersCount: 2
$id: 3
$parent: b {$$childTail: m, $$childHead: m, $$nextSibling: null, $$watchers: Array(4), $$listeners: {…}, …}
$root: m {$id: 1, $$childTail: b, $$childHead: b, $$prevSibling: null, $$nextSibling: null, …}
age: 5
name: "OrcHome"
可以看到数据传递过来是直接绑定在scope中的,所以用起来与绑在$scope上一样,再来看看component:
m {$id: 4, $$childTail: null, $$childHead: null, $$prevSibling: m, $$nextSibling: null, …}
$$childHead: null
$$childTail: null
$$destroyed: false
$$isolateBindings: {}
$$listenerCount: {$destroy: 1}
$$listeners: {$destroy: Array(1)}
$$nextSibling: null
$$phase: null
$$prevSibling: m {$id: 3, $$childTail: null, $$childHead: null, $$prevSibling: null, $$nextSibling: m, …}
$$watchers: (2) [{…}, {…}]
$$watchersCount: 2
$ctrl: controller {name: "OrcHome", age: 5}
$id: 4
$parent: b {$$childTail: m, $$childHead: m, $$nextSibling: null, $$watchers: Array(4), $$listeners: {…}, …}
$root: m {$id: 1, $$childTail: b, $$childHead: b, $$prevSibling: null, $$nextSibling: null, …}
__proto__: Object
可以看到传递过来的数据不是绑定在scope上,而是组件的控制器上,由于我们没有设置controllerAs,所以这里默认可以通过$ctrl访问。
别忘了directive有一个bindToController属性,作用就是将传递过来的值绑定在控制器上,我们修改代码,为directive添加此bindToController:true,再次输出可以看到scope与this发生了变化。
.directive('echo1', function () {
return {
restrict: 'AE',
replace: true,
scope: {
name: '<',
age: '<'
},
bindToController: true,
controllerAs: 'ctrl',
template: '<div>{{ctrl.name}}{{ctrl.age}}</div>',
controller: function ($scope) {
console.log($scope, this)
}
}
})
或许你想问能绑定在scope上干嘛要绑定在控制器上呢?别忘了directive与component都有一个require属性,通过此属性我们能注入其它组价或指令的控制器,也就是说你能用其它指令中定义的属性方法,前提是这些属性方法得绑定在this上,来看个例子:
<div ng-controller="myCtrl as vm">
<echo1>
<echo2></echo2>
</echo1>
</div>
angular.module('myApp', [])
.controller('myCtrl', function ($scope) {})
.directive('echo1', function () {
return {
restrict: 'AE',
replace: true,
controller: function ($scope) {
$scope.gender = 'male';
this.name = 'OrcHome';
this.sayName = function(){
console.log(this.name);
}
}
}
})
.component('echo2', {
template: '<div><button ng-click="$ctrl.echoCtrl.sayName()">点我</button></div>',
require:{
echoCtrl:'?^echo1'
},
controller: function ($scope) {
this.$onInit = function () {
console.log(this.echoCtrl);
}
}
});
我在指令echo1的controller中分别在scope以及this上绑定了一些属性,然后在组件echo2中通过require注入echo1的控制器,可以看到我们能通过此做法复用已经定义过的属性方法。同时我们打印注入的控制器,可以看到只有绑定在echo1 this上的属性,scope上的属性并没有传递过来。
4.component的controller中一般结合钩子函数使用,directive不需要
不管是通过scope/bindings传递父作用域数据过来,还是require注入其它指令组件控制器上的属性方法,在模板上直接使用都是没问题的,只是一个在scope上一个在控制器上的区别,前面也有例子展示。
但如果我们要在directive和component的controller中操作传递过来的数据component得使用钩子函数中才能获取到,否则就是undefined,看个例子:
<div ng-controller="myCtrl">
<echo1 person="person"></echo1>
<echo2 person="person"></echo2>
</div>
angular.module('myApp', [])
.controller('myCtrl', function ($scope) {
$scope.person = {
name: 'OrcHome',
age: '5'
}
})
.directive('echo1', function () {
return {
restrict: 'AE',
replace: true,
scope: {
person: '<',
},
template: '<div>{{name}}</div>',
controller: function ($scope) {
//传递过来的person绑定在scope上
$scope.name = $scope.person.name;
}
}
})
.component('echo2', {
bindings: {
person: '<',
},
template: '<div>{{name}}</div>',
controller: function ($scope) {
//只有在钩子函数中才能获取到
this.$onInit = function (){
//传递过来的person绑定在控制器上
$scope.name = this.person.name
}
}
});
这个例子中我们传递过来的是一个对象,但是我们只需要在视图上渲染对象中的一条属性,所以在controller中做了一次数据加工。directive直接加工没问题,但是component必须在钩子函数中才能取到this.person对象,大家可以尝试注释掉外面的$onInit方法看看区别,这点在使用component的controller处理传递过来的数据一定得注意。
5.require使用不同
前面已经提到directive与component都能使用require注入其它指令组件的控制器,以达到使用控制器中的数据,不过directive与component在require使用上有一点点区别,看个例子:
<div ng-controller="myCtrl">
<echo>
<echo1></echo1>
<echo2></echo2>
</echo>
</div>
angular.module('myApp', [])
.controller('myCtrl', function ($scope) {})
.directive('echo', function () {
return {
restrict: 'AE',
replace: true,
controller: function ($scope) {
this.name = 'OrcHome';
}
}
})
.directive('echo1', function () {
return {
restrict: 'AE',
replace: true,
require: '?^echo',
template: '<div>{{name}}</div>',
link: function (scope, ele, attrs, ctrls) {
console.log(ctrls);
scope.name = ctrls.name;
}
}
})
.component('echo2', {
require: {
echoCtrl: '?^echo'
},
template: '<div>{{name}}</div>',
controller: function ($scope) {
//只有在钩子函数中才能获取到
this.$onInit = function () {
console.log(this.echoCtrl);
$scope.name = this.echoCtrl.name;
}
}
});
在directive中require的值是一个字符串或者一个数组(注入多个时),并且注入的指令/组件控制器将成为link函数的第四个参数,注意是link函数不是controller。
而component的require值一直是个对象,被注入的指令/组件的控制器需要作为自定义key的value,在controller中通过this.key访问,注意,使用同样需要钩子函数。
选择
我们在上文中介绍了directive与component使用时存在的部分差异,那么实际开发中该如何抉择呢,其实在angular官网就已经给出了答案。
在AngularJS中,组件是一种特殊的指令,它使用更简单的配置,在属性默认值和属性配置实用角度上component有更大的优势,例如require key-value
形式相比directive的数组更便于使用,controllerAs自带了默认值等。
当然directive也有component无法取代的一面,当我们需要在编译和预链接函数中执行操作时,或者同一元素拥有多个指令需要定义优先级时,directive会比component更强大,没有谁好谁坏,只是根据需求来决定。