美文网首页
23.angular的一些零散知识(二)

23.angular的一些零散知识(二)

作者: 原来哥哥是万家灯火 | 来源:发表于2020-07-13 22:52 被阅读0次
    1.ElementRef、TemplateRef、ViewContainerRef

    ElementRef 相当于获取一个 DOM,其nativeElement属性就是DOM
    TemplateRef 用来获取一个 template 模板
    ViewContainerRef 用来获取一个视图容器(作用就是获取一个DOM,并把它当插入其他DOM的容器,只不过插入是插入到其后面,成为其兄弟元素)

    场景:想获取当前组件内的某个DOM。可使用ElementRef或者@ViewChild

    // 使用ElementRef,直接注入ElementRef,可获得当前组件的 ElementRef
    export class AboutComponent implements OnInit {
      constructor(private ele: ElementRef) {}
      ngOnInit(): void {
        const btn = this.ele.nativeElement.querySelector('#btn');
      }
    }
    
    // 使用ViewChild
    export class AboutComponent implements OnInit {
      @ViewChild('#btn', {static: true}) btnEle: ElementRef
    }
    
    2.ViewChild、ViewChildren、ContentChild、ContentChildren

    ViewChild 获取页面元素 \ 组件

    export declare interface ViewChildDecorator {
        (selector: Type<any> | Function | string, opts: {
            read?: any;
            static: boolean;
        }): any;
    }
    

    这个 api 的语义话非常好:可以理解为"表示查询一个子元素,并从它身上获取(read)什么"

    selector
    一个组件或一个指令
    @ViewChild(ChildComponent)
    一个模板引用变量的字符串
    <my-component #cmp></my-component>
    @ViewChild('cmp')
    一个子组件上注册的提供商
    @ViewChild(TOKEN)
    一个TemplateRef
    @ViewChild(TemplateRef) tpl: TemplateRef

    函数形式怎么用不知道

    read (从查询到的元素中读取另一个令牌)
    read的内容才是希望获取的东西,比如:

    // 查询子元素ChildComponent,并从它身上读取ElementRef
    @ViewChild(ChildComponent, {static: true, read: ElementRef} ) ele: ElementRef
    
    // 查询子元素ChildComponent,并从它身上读取一个视图容器
    @ViewChild(ChildComponent, {static: true, read: ViewContainerRef} ) vcref: ViewContainerRef
    
    // 查询子元素ChildComponent,并从它身上读取一个服务
    @ViewChild(ChildComponent, {static: true, read: MsgService} ) msg: MsgService
    

    read是可选的,当不指明的时候,应该是查询什么,就获取什么。比如

    // 二者作用一样
    @ViewChild(ChildComponent, {static: true, read: ChildComponent} ) component: ChildComponent
    @ViewChild(ChildComponent, {static: true} ) component: ChildComponent
    

    static (指定查询元素的时机,查询只会执行一次,其结果会缓存
    true 在变更检测前查询元素(ngChanges之前)
    false 在变更检测后查询元素(ngDoCheck之后,ngAfterViewInit之前)

    @Component({
      template: `
        <div #div1></div>
        <div #div2 *ngIf="true"></div>
      `,
    })
    export class AboutComponent implements OnInit, AfterViewInit {
      @ViewChild('div1', { static: true }) div1: ElementRef;
      @ViewChild('div2', { static: false }) div2: ElementRef;
    
      ngOnInit() {
        console.log(this.div1);
        console.log(this.div2);
      }
    
      ngAfterViewInit() {
        console.log(this.div1);
        console.log(this.div2);
      }
    }
    

    上述过程是:
    div1生成 -> 查询div1 -> ngChanges -> ngOnInit -> ngDoCheck -> div2生成 -> 查询div2 -> ngAfterViewInit...

    3.zone.js

    angular 和 angularjs 中双向绑定的实现方式是脏值检查,当某些可能导致值发生变化的事情发生之后,就去检查值是否变化、更新视图......可能导致值发生变化的事情包括setTimeout、setInterval、XHR、dom事件。

    angularjs中,脏检查就存在问题,不能让异步事件完毕后自动调用触发检测。比如:

    function foo() {
        $scope.user = '张三'
    }
    setTomeout(foo, 0)
    $apply() // 更新视图
    

    当使用原生setTimeout去改变值后,无法让foo执行完毕之后,自动调用 $apply() 或者 $degist()。解决办法是开发者手动去调用 $apply() 触发新一轮的变更检测

    function foo() {
        $scope.user = '张三';
        $apply() // 更新视图
    }
    setTomeout(foo, 0)
    

    或者用封装的 $timeout 代替 setTimeout

    function foo() {
        $scope.user = '张三';
    }
    $timeout(foo, 0)
    

    $timeout可能就长这样:

    var $timeout = function(fn, time) {
        var that = this;
        return setTimeout(function() {
            fn.apply(that);
            $apply();
        }, time)
    }
    

    angular2 改进了这个问题:
    zone.js 使用mokey patch(称猴子补丁或动态补丁)的方式覆盖掉了原生的setTimeout等异步方法,其具体实现很复杂,反正效果是使得异步任务进入执行栈、执行完毕等过程都可以被监听到。

    *zone.js不仅覆盖掉了 setTomeout 等原生API,还专门覆盖掉了其 toString 方法,使得直接在控制台 setTomeout 或者 setTomeout.toString 得到的是 native code,可以直接 console.dir(window) 去控制台查看 setTomeout *

    这个例子展示了zone.js中onScheduleTask、onInvokeTask两个钩子监听到异步任务进入执行栈、执行完毕。

    let timer;
    
    const zone = Zone.current.fork({
        name: 'z',
        onScheduleTask(delegate, currentZone, targetZone, task) {
          const result = delegate.scheduleTask(targetZone, task);
          const name = task.callback.name;
          console.log(
              Date.now() - timer, 
             `task with callback '${name}' is added to the task queue`
          );
          return result;
        },
        onInvokeTask(delegate, currentZone, targetZone, task, ...args) {
          const result = delegate.invokeTask(targetZone, task, ...args);
          const name = task.callback.name;
          console.log(
            Date.now() - timer, 
           `task with callback '${name}' is removed from the task queue`
         );
         return result;
        }
    });
    
    function a1() {}
    function a2() {}
    
    function b() {
        timer = Date.now();
        setTimeout(a1, 2000);
        setTimeout(a2, 4000);
    }
    
    zone.run(b);
    

    在监听到异步执行完毕之后,再由 ngZone 去执行启动变更检测等一系列调度。
    所以zone.js是和angular框架完全解耦的,可以单独拿到其他项目中去用,去监听异步任务。
    ngZone,还可以用来对项目进行一些优化。
    比如有个频繁变动的值,将导致频繁执行变更检测。可以用ngZone使angular暂时不跟踪其变化

    constructor(private zone: NgZone) { }
    
    this.zone.runOutsideAngular(() => {
       for (let i = 0; i < 100; i++) {
         setInterval(() => this.counter++, 10);
       }
    });
    

    稳定后,重新跟踪变化

    this.zone.run(() => {
      setTimeout(() => this.foo = this.foo, 1000);
    });
    
    4.变更检测

    结论:

    • 可以使用ChangeDetectionStrategy.OnPush 对项目进行优化。OnPush策略会使组件及其子组件在发生外部异步事件时,不再执行变更检测。对于其输入属性发生变化,或其自己内部发生异步事件,还是会正常进行检测并更新视图
    • 输入属性改变是指 oldValue !== newValue,所以修改了对象的属性不算输入属性改变。这算是一个“小缺点”
    • 对于上面这个"小缺点",可以使用Immutable 变量避免,或使用bservable作为输入属性
    • ChangeDetectorRef 是当前组件的变更检测器的引用。可用来分离当前检测器、重新恢复变更检测、手动执行检测等等操作
    • OnPush策略下,ngDoCheck钩子函数依然会执行,可在ngDoCheck调用ChangeDetectorRef

    以下是一些具体的例子或叙述说明:

    angular应用是由组件组成的树状结构。每当异步事件发生后,angular都会从上而下地检测当前"存活"的每个组件

    @Component({
      selector: 'parent',
      template: `<child [user]="person"></child>`
    })
    
    export class ParentComponent implements OnInit {
      person = { name: '张三' };
    
      ngOnInit(): void {
        setTimeout( () => {
          this.person.name = '李四';
        }, 2000);
      }
    }
    
    @Component({
      selector: 'child',
      template: `{{user | json}}`
    })
    export class ChildComponent {
      @Input() user: any;
    }
    

    可以看到2秒后child组件的视图更新

    当某个组件设置 ChangeDetectionStrategy.OnPush 后,无关的异步事件发生时,这个组件及其子组件不再执行变更检测。只有其输入属性改变,或其本身内部发生异步事件才检测。

    输入属性改变,指的是 oldValue !== newValue,所以以下情况就不会触发变更检测。

    @Component({
      selector: 'parent',
      template: `<child [user]="person"></child>`
    })
    
    export class ParentComponent implements OnInit {
      person = { name: '张三' };
    
      ngOnInit(): void {
        setTimeout( () => {
          this.person.name = '李四';
        }, 2000);
      }
    }
    
    @Component({
      selector: 'child',
      template: `{{ user | json }}`,
      changeDetection: ChangeDetectionStrategy.OnPush
    })
    export class ChildComponent {
      @Input() user: any;
    }
    

    可以看到两秒后child组件的视图并不更新

    child组件的输入属性被判定为“没有”发生变化,所以其变更检测不会执行。为避免这种情况,需使用 Immutable (不可变)变量。Immutable 变量的不是说这个变量无法修改,是指我们遵守不修改原有的数据模型,而是创建一个新的数据模型的原则。

    @Component({
      selector: 'parent',
      template: `<child [user]="person"></child>`
    })
    
    export class ParentComponent implements OnInit {
      person = { name: '张三' };
    
      ngOnInit(): void {
        setTimeout( () => {
          this.person = { '李四' };
        }, 2000);
      }
    }
    
    @Component({
      selector: 'child',
      template: `{{ user | json }}`,
      changeDetection: ChangeDetectionStrategy.OnPush
    })
    export class ChildComponent {
      @Input() user: any;
    }
    

    可以看到两秒后视图更新

    除了Immutable 方式外,还可以使用 Observable作为输入属性

    export class CounterComponent implements OnInit {
        counter: number = 0;
    
        @Input() addStream: Observable<any>;
    
        constructor(private cdRef: ChangeDetectorRef) { }
    
        ngOnInit() {
            this.addStream.subscribe(() => {
                this.counter++;
                this.cdRef.markForCheck();
            });
        }
    }
    

    组件自己内部发生异步事件,依然可以触发变更检测

    @Component({
      selector: 'child',
      template: `{{ user | json }}`,
      changeDetection: ChangeDetectionStrategy.OnPush
    })
    
    export class ChildComponent implements OnInit {
      @Input() user: any;
    
      ngOnInit(): void {
         setTimeout( () => {
           this.user.name = '李四';
        }, 2000);
      }
    }
    

    参考资料
    翻阅源码后,我终于理解了Zone.js

    相关文章

      网友评论

          本文标题:23.angular的一些零散知识(二)

          本文链接:https://www.haomeiwen.com/subject/vhrthktx.html