翻译:深入理解Angular 1.5 中的生命周期钩子
文章翻译自:https://toddmotto.com/angular-1-5-lifecycle-hooks 生命周期钩子是一些简单的函数,这些函数会在Angular应用组件特定生命周期被调用。生命周期钩子在Angular 1.5版本被引入,通常与.component()方法一起使用,并在接下来的几个版本中演变,并包含了更多有用的钩子函数(受Angular 2的启发)。让我们深入研究这些钩子函数并实际使用它们吧。这些钩子函数所带来的作用以及为什么我们需要使用它们,对于我们深入理解通过组件架构的应用具有重要的意义。 在Angular v1.3.0+版本,我自己实现了 $onInit什么是 var myComponent = { bindings: {},controller: function () { this.$onInit = function() { }; } }; angular .module('app') .component('myComponent',myComponent); Using $onInit
var myComponent = { ... controller: function () { this.foo = 'bar'; this.bar = 'foo'; this.fooBar = function () { }; } }; 注意上面的代码,我们把所有的属性直接赋值到了this上面,它们就像“浮在”控制器的各个角落。现在,让我们通过 var myComponent = { ... controller: function () { this.$onInit = function () { this.foo = 'bar'; this.bar = 'foo'; }; this.fooBar = function () { console.log(this.foo); // 'bar' }; } }; 上面的数据明显地通过硬编码写入的,但是在实际的应用中,我们通常是通过 对于 $onInit + “require”因为这些生命周期钩子定义得如此优雅(不同的生命周期钩子都在组件的不同生命周期被调用),一个组件也可以从另外的组件中继承方法,甚至继承的方法在 首先我们需要思考的是如何使用 让我们来看看 var myComponent = { ... require: { parent: '^^anotherComponent' },controller: function () { this.$onInit = function () { this.foo = 'bar'; this.bar = 'foo'; }; this.fooBar = function () { console.log(this.foo); // 'bar' }; } }; 如上面的例子, var myComponent = { ... require: { parent: '^^anotherComponent' },controller: function () { this.$onInit = function () { this.foo = 'bar'; this.bar = 'foo'; this.parent.sayHi(); }; this.fooBar = function () { console.log(this.foo); // 'bar' }; } }; 注意,在Angular 1.5.6版本(见CHANGELOG)中,如果require对象中属性名和require的控制器同名,那么就可以省略控制器名。这一特性并没有带来给功能带来很大的改变,我们可以如下使用它: var myComponent = { ... require: { parent: '^^' },controller: function () { ... } }; 正如你所见,我们完全省略了需要requre的控制器名而直接使用 Real world $onInit + require让我们使用 <tabs> <tab label="Tab 1"> Tab 1 contents! </tab> <tab label="Tab 2"> Tab 2 contents! </tab> <tab label="Tab 3"> Tab 3 contents! </tab> </tabs> 这意味着我们需要两个组件, 首先,组件定义了每个组件都必须使用的一些属性: var tab = { bindings: {},require: {},transclude: true,template: ``,controller: function () {} }; var tabs = { transclude: true,controller: function () {} }; angular .module('app',[]) .component('tab',tab) .component('tabs',tabs);
让我们来实现 var tab = { ... template: ` <div class="tabs__content" ng-if="$ctrl.tab.selected"> <div ng-transclude></div> </div> `,... }; 对于 var tabs = { ... template: ` <div class="tabs"> <ul class="tabs__list"> <li ng-repeat="tab in $ctrl.tabs"> <a href="" ng-bind="tab.label" ng-click="$ctrl.selectTab($index);"></a> </li> </ul> <div class="tabs__content" ng-transclude></div> </div> `,... }; 对于 接下来让我们来处理 var tab = { bindings: { label: '@' },... template: ` <div class="tabs__content" ng-if="$ctrl.tab.selected"> <div ng-transclude></div> </div> `,controller: function () { this.$onInit = function () { this.tab = { label: this.label,selected: false }; }; } ... }; 你可以看到我在控制器中使用了 接下来让我们来看看 var tabs = { ... template: ` <div class="tabs"> <ul class="tabs__list"> <li ng-repeat="tab in $ctrl.tabs"> <a href="" ng-bind="tab.label" ng-click="$ctrl.selectTab($index);"></a> </li> </ul> <div class="tabs__content" ng-transclude></div> </div> `,controller: function () { this.$onInit = function () { this.tabs = []; }; this.addTab = function addTab(tab) { this.tabs.push(tab); }; this.selectTab = function selectTab(index) { for (var i = 0; i < this.tabs.length; i++) { this.tabs[i].selected = false; } this.tabs[index].selected = true; }; },... }; 我们在 接下来我们通过 var tab = { ... require: { tabs: '^^' },... }; 正如我们在文章关于 var tab = { ... require: { tabs: '^^' },selected: false }; // this.tabs === require: { tabs: '^^' } this.tabs.addTab(this.tab); }; } ... }; 把所有代码放一起: var tab = { bindings: { label: '@' },require: { tabs: '^^' },template: ` <div class="tabs__content" ng-if="$ctrl.tab.selected"> <div ng-transclude></div> </div> `,selected: false }; this.tabs.addTab(this.tab); }; } }; var tabs = { transclude: true,template: ` <div class="tabs"> <ul class="tabs__list"> <li ng-repeat="tab in $ctrl.tabs"> <a href="" ng-bind="tab.label" ng-click="$ctrl.selectTab($index);"></a> </li> </ul> <div class="tabs__content" ng-transclude></div> </div> ` }; 点击选项卡相应内容就会呈现出来,当时,我们并没有设置一个初始化的展示的选项卡?这就是接下来 $postLink我们已经知道, function myDirective() { restrict: 'E',scope: { foo: '=' },compile: function compile($element,$attrs) { return { pre: function preLink($scope,$element,$attrs) { // access to child elements that are NOT linked },post: function postLink($scope,$attrs) { // access to child elements that are linked } }; } } 你也可能知道如下: function myDirective() { restrict: 'E',link: function postLink($scope,$attrs) { // access to child elements that are linked } } 当我们只需要使用 Using $postLink
var myComponent = { ... controller: function () { this.$postLink = function () { // fire away... }; } }; 我们已经知道, Real world $postLink我们可以通过 <tabs selected="0"> <tab label="Tab 1">...</tab> <tab label="Tab 2">...</tab> <tab label="Tab 3">...</tab> </tabs> 现在我们就可以通过 var tabs = { bindings: { selected: '@' },... controller: function () { this.$onInit = function () { this.tabs = []; }; this.addTab = function addTab(tab) { this.tabs.push(tab); }; this.selectTab = function selectTab(index) { for (var i = 0; i < this.tabs.length; i++) { this.tabs[i].selected = false; } this.tabs[index].selected = true; }; this.$postLink = function () { // use `this.selected` passed down from bindings: {} // a safer option would be to parseInt(this.selected,10) // to coerce to a Number to lookup the Array index,however // this works just fine for the demo :) this.selectTab(this.selected || 0); }; },... }; 现在我们已经有一个生动的实例,通过 What $postLink is not在 那么 $onChanges这是一个很大的部分(也是最重要的部分), What calls $onChanges?在以下情况下 Using $onChanges使用 var childComponent = { bindings: { user: '<' },controller: function () { this.$onChanges = function (changes) { // `changes` is a special instance of a constructor Object,// it contains a hash of a change Object and // also contains a function called `isFirstChange()` // it's implemented in the source code using a constructor Object // and prototype method to create the function `isFirstChange()` }; } }; angular .module('app') .component('childComponent',childComponent); 注意,这儿 但是,正如上面提到,我们需要一个 var parentComponent = { template: ` <div> <child-component></child-component> </div> ` }; angular .module('app') .component('parentComponent',parentComponent); 需要注意的是: var parentComponent = { template: ` <div> <a href="" ng-click="$ctrl.changeUser();"> Change user (this will call $onChanges in child) </a> <child-component user="$ctrl.user"> </child-component> </div> `,controller: function () { this.$onInit = function () { this.user = { name: 'Todd Motto',location: 'England,UK' }; }; this.changeUser = function () { this.user = { name: 'Tom Delonge',location: 'California,USA' }; }; } }; 再次,我们使用 现在,让我们来看看 var childComponent = { bindings: { user: '<' },template: ` <div> <pre>{{ $ctrl.user | json }}</pre> </div> `,controller: function () { this.$onChanges = function (changes) { this.user = changes; }; } }; 这儿,我们使用 点击按钮来观察 var childComponent = { ... controller: function () { this.$onChanges = function (changes) { this.user = changes.user.currentValue; }; } }; 现在我们可以使用 Cloning “change” hashes for “immutable” bindings现在我们已经从组件中获取到从单向数据绑定的数据,我们可以在深入的思考。虽然单项数据绑定并没有被Angular所 这个是一个fiddle例子(注意 作为替换,我们可以使用 var childComponent = { ... controller: function () { this.$onChanges = function (changes) { this.user = angular.copy(changes.user.currentValue); }; } }; 做得更好,我们添加了 var childComponent = { ... controller: function () { this.$onChanges = function (changes) { if (changes.user) { this.user = angular.copy(changes.user.currentValue); } }; } }; 甚至我们还可以再优化我们的代码,因为当父组件中数据发生变化,该变化会立即反应在 this.$onChanges = function (changes) { if (changes.user) { this.user = angular.copy(this.user); this.user = angular.copy(changes.user.currentValue); } }; 我更偏向于的途径(使用 现在就开始尝试,通过深拷贝开复制从父组件传递下来的对象,然后赋值给子组件控制器相应属性。 感觉还不错吧?现在我们使用拷贝对象,我们可以任意改变对象而不用担心会影响到父组件(对不起,双向数据绑定真的不推荐了!)因此当我们更新数据后,通过事件来通知父组件,单向数据流并不是生命周期钩子的一部分,但是这 One-way dataflow + events上面我们讨论了 为了使数据能够回流到 var parentComponent = { ... controller: function () { ... this.updateUser = function (event) { this.user = event.user; }; } }; 从这我们可以看出,我们期待 var parentComponent = { template: ` <div> ... <child-component user="$ctrl.user" on-update="$ctrl.updateUser($event);"> </child-component> </div> `,controller: function () { ... this.updateUser = function (event) { this.user = event.user; }; } }; 注意我创建了一个带有 现在我们已经将该函数传递给了 var childComponent = { bindings: { user: '<',onUpdate: '&' // magic ingredients },... controller: function () { this.$onChanges = function (changes) { if (changes.user) { this.user = angular.copy(this.user); } }; // now we can access this.onUpdate(); } }; 通过 接下来,我们需要扩展我们的模板来时的用户可以更新深拷贝的数据: var childComponent = { ... template: ` <div> <input type="text" ng-model="$ctrl.user.name"> <a href="" ng-click="$ctrl.saveUser();">Update</a> </div> `,... }; 这意味着我们需要在控制器中添加 var childComponent = { ... template: ` <div> <input type="text" ng-model="$ctrl.user.name"> <a href="" ng-click="$ctrl.saveUser();">Update</a> </div> `,controller: function () { ... this.saveUser = function () { }; } }; 尽管,当我们在子组件中"保存"的时候,这其实仅仅是父组件回调函数的一个封装,因此我们在子组件中直接调用父组件方法 var childComponent = { ... controller: function () { ... this.saveUser = function () { // function reference to "this.updateUser" this.onUpdate(); }; } }; 好的,相信我,我们已经到了最后阶段,这也会使得事情变得更加有趣。相反我们并不是直接把 this.updateUser = function (event) { this.user = event.user; }; 我们期待event对象带有一个user的属性,好吧,那就让我们来在子组件中 var childComponent = { ... controller: function () { ... this.saveUser = function () { this.onUpdate({ $event: { user: this.user } }); }; } }; 上面的代码看上去有些怪异。也许有一点吧,但是他是始终一致的,当你使用十遍以后,你就再也不会停止使用它了。必要的我们需要在子组件中创建 尝试如上方式写代码吧: 这儿也有一个免费的教学视频,是我关于 Is two-way binding through “=” syntax dead?是的,单向数据绑定已经被认为是数据流的最佳方式,React,Angular 2 以及其他的类库都是用单向数据流,现在轮到Angualr 1了,虽然Angular 1加入单向数据流有些晚,但是依然很强大并将改变Angular 1.x应用开发方式。
Using isFirstChange()
function SimpleChange(previous,current) { this.previousValue = previous; this.currentValue = current; } SimpleChange.prototype.isFirstChange = function () { // don't worry what _UNINITIALIZED_VALUE is :) return this.previousValue === _UNINITIALIZED_VALUE; }; 这就是变化对象根据不同的绑定策略怎么被创造出来(通过 你为什么会想着使用该方法呢?上面我们已经提到过, this.$onChanges = function (changes) { if (changes.user.isFirstChange()) { console.log('First change...',changes); return; // Maybe? Do what you like. } if (changes.user) { this.user = angular.copy(this.user); } }; Here’sa JSFiddleif you want to check the $onDestroy我们最后来讨论下最简单的一个生命周期钩子, function SomeController($scope) { $scope.$on('$destroy',function () { // destroy event }); } Using $onDestroy你可以猜想到该生命周期钩子怎么使用: var childComponent = { bindings: { user: '<' },controller: function () { this.$onDestroy = function () { // component scope is destroyed }; } }; angular .module('app') .component('childComponent',childComponent); 如果你使用了 ConclusionAngular 1.x 应用开发者的的开发模式也随着单向数据流,生命周期事件及生命周期钩子函数的出现而改变,不久将来我将发布更多关于组件架构的文章。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |