Angular Pipe 在 嵌套对象上的非预期行为分析

Angular Pipe 在 嵌套对象上的非预期行为分析

场景

在工做中,存在一个嵌套对象,须要展现嵌套对象内层的一些信息,因而写了个Pipe 来处理,可是发现当嵌套的对象发生变化时,pipe 不会从新执行。例若有下面一个数据。javascript

var feer = {
    name: 'joe',
    skills: [
        {
            name:'js'
        },
        {
            name: 'ts'
        }
    ]
}

咱们想要的结果是把skills 里面的name 所有展现出来,以, 分割。css

// component
export class AppComponent {
  private skills = ['css', 'html', 'java', 'gulp']
  name = 'Angular 6';
  feer = {
    name: 'joe',
    skills: [
      {
        name: 'js'
      },
      {
        name: 'ts'
      }
    ]
  }

  add() {
    const skill = this.skills.shift();
    if (skill) {
      this.feer.skills.push({
        name: skill
      })
    }
  }
}
// html
{{ feer.skills | defaultPure }}
// Pipe
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
    name: 'defaultPure'
})

export class DefaultPurePipe implements PipeTransform {
    transform(feer:any): string {
        return feer.skills.map((v)=>v.name).join(',');
    }
}

在这种状况下,若是调用add 引发 skills 发生变化,pipe 不会从新计算,显示的仍是初始值 js,tshtml

问题

这里存在一个问题,对于上面👆例子中存在的嵌套结构,在 skills 变化的时候 ,transform 并无从新执行。这里推测多是 内部检测时候并不会作 deep-change-detection (也就是不会关注 skills 里的变化),最终致使了上面的问题: skills 发生了变化,但 pipe 并无从新执行计算变动输出结果。vue

下面是摘自官网的一段话:java

Angular executes a pure pipe only when it detects a pure change to the input value. A pure change is either a change to a primitive input value ( String, Number, Boolean, Symbol) or a changed object reference ( Date, Array, Function, Object).

Angular ignores changes within (composite) objects. It won't call a pure pipe if you change an input month, add to an input array, or update an input object property.git

pipe 忽略了 对象内部复合对象的变更(如例子中的 skills ), angular 不会作深度检查,当咱们调用 add 方法时候,往 skills 数组里 push 里一个对象,对于 angular 来讲, skills 是“未”发生变化的,由于引用是同样的。github

解决方案

@Pipe 的 decorator 中除了name 还有一个叫 pure 类型为 booleanmetadata,官方解释以下typescript

If Pipe is pure (its output depends only on its input.) Normally pipe's transform method is only invoked when the inputs to pipe's transform method change. If the pipe has internal state (it's result are dependant on state other than its arguments) than set pure to false so that the pipe is invoked on each change-detection even if the arguments to the pipe do not change.

大意是 pipe 仅在输入发生改变的时候会再次执行 transform 方法。可是若是 pipe 有一个内部状态,而且输出依赖于这个内部状态,那么将 pure 设置为 false 以便在每次 change-detection (数据更新检测) 时候执行 transform即便输入的数据并无改变。gulp

对于pure, 默认值为 true ,若是设置为 false ,则能够将上面的功能实现出来。可是同时要注意一个问题,一个 impurepipe (即 pure = false ) 会常常被调用,若是有大量运算,则可能影响用户体验 。数组

一样看一段官网的解释:

Angular executes an impure pipe during every component change detection cycle. An impure pipe is called often, as often as every keystroke or mouse-move.

对于 impurepipeangular 会在每次 component 的 变化检测周期里调用执行,甚至一次按键、一次鼠标事件都会触发 pipe 的执行。

最终,对于上述的问题,只需修改 pipe 的代码便可。

@Pipe({
    name: 'defaultPure',
    pure: false
})

export class DefaultPurePipe implements PipeTransform {
    transform(feer:any): string {
        return feer.skills.map((v)=>v.name).join(',');
    }
}

完结

对于上述问题,表面上来讲加一下 pure 便可,对于深层次来讲,涉及到了 angular 内部的一些工做原理。可是话说回来,目前仅仅研究到这种方案来处理,实际上应该还会有其余的解决方案,大胆猜想能够利用 change-detection 的一些钩子来处理,待研究好了再记录一下。

以前在遇到这个问题的时候,也是一脸懵逼的找答案,却不知答案就在文档上写的清清楚楚。 🤷‍♀️~~~

BTW,其实在 vue 中也存在了一个相似的问题, 为了性能等的考虑,不会对复合对象作深度变动检测。而 Vue的作法则简单粗暴一些:从新装饰数组方法。 具体能够查看 Vue源代码

相关文章
相关标签/搜索