angularJs component与directive的不同

半兽人 发表于: 2020-05-02   最后更新时间: 2020-05-02  

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)
        }
    }
})

screenshot

或许你想问能绑定在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更强大,没有谁好谁坏,只是根据需求来决定。



您需要解锁本帖隐藏内容请: 点击这里
本帖隐藏的内容




上一条: AngularJs之ngModel中$parsers和$formatters
下一条: 已经是最后了!