JesseZhao1990 / blog

learing summary
MIT License
62 stars 7 forks source link

Angular 1.x进阶:组件间通信之Require #167

Open JesseZhao1990 opened 5 years ago

JesseZhao1990 commented 5 years ago

英文原文:https://www.codelord.net/2016/11/30/advanced-angular-1-dot-x-component-communication-with-require/

angular1.5版本中的推出了组件的概念。它隔离了scope。但是这也意味着组件间通信变的有些不那么任性,我们需要更多的思考正确的组件间通信的问题。

angular的require机制为表单组件,特别是“组件套装”提供了一种好的通信方式(这里解释一下组件套装,组件套装是需要一起使用的组件,比如平时我们使用的antd的Tabs和TabPane,再比如element ui中的el-tabs和el-tab-pane)

那么问题来了。 require可以用来做什么? 什么情况下我们才应该使用它? 为什么它不是通用的解决方案? 继续往下阅读吧,答案就在下文...

require 是怎么工作的?

require的机制在angular中早就存在,通常被用于高级指令间的通信。在组件的概念提出之前,你可能用过require让一个指令有权访问另外一个指令的controller,它可以让指令暴露出自己的api供别的指令调用。

举一个简单的在一个元素上使用require通信的例子,当你把你的校验指令挂载到一个input元素上,让其能够访问到input的ngModelController,以便修改校验器。

对于那些需要沟通的套装组件来说,访问父controller是一个常见的并且有用的特征。比如说angular的form验证的工作方式。在form中的每个input元素都可以通过随意的引入父组件的controller(ngForm)进而去更新它。通过这种方式去通知form,告知form自己是否通过了验证之类的。

正如上边你看到的一样,require是一个非常强大的机制,是angular与生俱来的一个东西。是angular的一部分。使用它,你可以让你的组件更加干净。

什么情况下应该使用require,什么情况下不应该使用require呢?

首先需要说明的是,require并不适用在所有的场景。它仅仅是用在组件/指令中,用于访问父controller或者是在和自己处于相同元素的controller。兄弟组件不能用它来通信,父组件也不能通过他来访问子组件(至少不是直接访问,继续阅读下去吧。后文会给出一个有意思的解决方案。)

还有一个对于使用require的限制,那就是使用它的时候,你必须知道子组件想引用的其他组件的名称。无法抽象, 被引用的组件的名字是以硬编码的方式写到引用它的组件中的。

并且,我也不会匆忙地用require去替换任何简单的binding。大部分的情况下,使用bindings在组件之间传递值是不错的选择,因为这样更清晰易懂,易维护。因为你只需要瞅一眼bindings,你就能大概知道这个组件的接口。

那么require在什么场景下才能发出自己的光芒呢?其实,我们在文章的开头也说过了。就是当你创建一个套装组件或者说是需要互相交互的组件家族(a family of interacting components),通过require,可以在他们之间暴露出丰富的接口,而不用再多层组件之间写大量的bindings。

一个深入使用require的例子: Angular的表单(forms)

我们上边已经提到,Angular的整个form和form的验证机制极度依赖require的使用

当你在Angular里创建一个<form>, 它包裹的元素,比如<input> <select> 等等。这些元素都会通过使用ng-model 把自己作为一个被控制者(control)注入到form中。

你可以在ngModel的源码中看到如果它存在一个父的form指令的话,它是怎样引入form指令的。ngModelController 使用form指令的controller并且把自己作为被控制者(control)注入到其中。源码在这里

反过来,form指令会保存所有的被注册到其中的被控制者(control),并且在他们变化的时候更新他们。什么时候会发生变化呢。比如说$setPristine$setUntouched

有必要说明的是,没有直接的方法可以做到从父组件访问子组件。Angular的核心团队在这里使用从child组件中使用require,把自己注册到父组件。进而通过暴露子组件的controller给父组件达到了父组件访问子组件的目的。

Angular的form,反过来也可以被嵌套进另外的form。这个是通过添加可选的(optionally)require来实现引入父form的controller的。具体的源码在此 。 如果require 发现存在一个父指令是form的指令,内置的表单注册器会让父form知道其有嵌套的form。

在以上这个优秀的例子中,我们创建一个组件体系。组件之间可以互相通信,利用他们之间的通信发挥他们的优势。

说实话,你是怎么在你的组件中使用require的?

我们假设我们在项目中有下面这样的一个组件结构

<parent>
  <child></child>
</parent>

我们想让子组件能够访问父组件的controller。

我们在子组件的定义中加入一个require选项。 然后我们就可以在子组件的controller中访问到我们require选项中的controller(父controller),就像下面的例子一样

app.component('child', {
  require: {
    parentCtrl: '^^parent'
  },
  controller: function() {
    var self = this;
    this.$onInit = function() {
      self.parentCtrl.someControllerFunc();
    };
  }
});

我们在这里做了些什么呢? require选项生命的这个对象 允许我们传入许多需要的组件,只要我们乐意,想传入多少就可以传入多少。parentCtrl 是作为我们引入的controller的名字被绑定到我们的组件中。^^parent 可能看起来有些神奇。但是我们可以这样认为,我们引入了一个存在的叫parent的父组件。

^^符号是表示此组件必须是父组件。另外我们还有^符号。这个符号代表着此组件可能是父组件也可以是同一个element。 如果你仅仅简单的写一个名字。比如 fooCtrl: 'foo',他意味着你期望foo指令存在于当前的element上,所以,不会在别的地方进行搜索。

当我们谈论组件,而不是指令的时候, 我们想要的总是 ^^, 尽管你可以使用^,如果你确确实实能确定你不会意外的引入了当前element的东西。

当我们引入一个组件之后,我们可以从我们的controller里通过我们事先定义的名字进而访问到它。例如this.parentCtrl,我个人倾向于不改变它的名字,这样让代码更易读和易懂。

如果require对象中的value像上边我们看到的那样写。 这意味着组件是严格模式的引入。Angular在找不到组件的时候会抛出异常。为了即使引入的组件不存在,本组件也能正常运转的话。你可以设置require中的项为可选。像下面这样

require: {
  parentCtrl: '?^^parent'
}

问好代码的意思就是当Angular发现它不能找到你声明的父组件,Angular会默默的忽略掉它。 如果你尝试着去访问 this.parentCtrl 你回发现它是null

优雅的使用require的前提是优雅的分工

Require 是一个高阶的Angular技术。他可以增强你的应该的分割。对于你写那些高度交互的组件来说,是非常棒的一个功能。

但是但是但是。强调一下。require 当然不能作为所有需要互相通信的场景的默认解决方案。 默认去使用bindings吧。在确实需要的时候再去碰require