博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
双向绑定底层剖析(用于自定义组件双向数据绑定和非form表单元素)
阅读量:7077 次
发布时间:2019-06-28

本文共 5206 字,大约阅读时间需要 17 分钟。

hot3.png

适用于一切双向数据绑定,不局限与下面的input输入,文末链接1就是例子

Angular 中常见的 ControlValueAccessor 有:

  • DefaultValueAccessor - 用于 text 和 textarea 类型的输入控件

  • SelectControlValueAccessor - 用于 select 选择控件

  • CheckboxControlValueAccessor - 用于 checkbox 复选控件

(注:妹的快写完的时候突然网页挂了,没保存到浪费了半小时重写)

一、问题的发现

公司最近的项目都是通过PrimeNG(ng2的UI组件)来开发,但别人的组件永远都够不着用,所以很有必要进行二次开发,或者自定义组件。今天看到项目中的大神把PrimeNG<p-autoComplete>的组件,自己自定义写了一份,趁有空我也研究了一下,发现里面存在着双向绑定的深层原理及使用方式。(我一直以为双向绑定原理就是[value]="value" (valueChange)="value=$event"  《揭秘Angular2》P202介绍)

import {NG_VALUE_ACCESSOR, ControlValueAccessor} from '@angular/forms';

这是ng2内置接口ControlValueAccessor

二、什么是ControlValueAccessor?

ControlValueAccessor 是一个接口,它的作用是:

  • 把 form 模型中值映射到视图中

  • 当视图发生变化时,通知 form directives 或 form controls

Angular 引入这个接口的原因是,不同的输入控件数据更新方式是不一样的。例如,对于我们常用的文本输入框来说,我们是设置它的 value 值,而对于复选框 (checkbox) 我们是设置它的 checked 属性。实际上,不同类型的输入控件都有一个 ControlValueAccessor,用来更新视图。

这就是MVVM模型,Model -> View,View -> Model 之间的数据绑定

1、ControlValueAccessor接口底层代码

// angular2/packages/forms/src/directives/control_value_accessor.ts export interface ControlValueAccessor {  writeValue(obj: any): void;  registerOnChange(fn: any): void;  registerOnTouched(fn: any): void;  setDisabledState?(isDisabled: boolean): void;}
  • writeValue(obj: any):该方法用于将模型中的新值写入视图或 DOM 属性中。
  • registerOnChange(fn: any):设置当控件接收到 change 事件后,调用的函数
  • registerOnTouched(fn: any):设置当控件接收到 touched 事件后,调用的函数
  • setDisabledState?(isDisabled: boolean):当控件状态变成 DISABLED 或从 DISABLED 状态变化成 ENABLE 状态时,会调用该函数。该函数会根据参数值,启用或禁用指定的 DOM 元素。

三、怎么使用达到双向绑定

import {Component, OnInit, Input, forwardRef} from '@angular/core';import {API} from "app/share/lib/api/api";import {NG_VALUE_ACCESSOR} from "@angular/forms";// 封装一个对象,固定写法export const CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR: any = {    provide: NG_VALUE_ACCESSOR,    useExisting: forwardRef(() => GoodSelectComponent),    multi: true};@Component({    selector: 'good-select',    templateUrl: './good-select.component.html',    styleUrls: ['./good-select.component.css'],    providers: [CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR]})export class GoodSelectComponent implements OnInit {    /**     * 数据     */    suggestions: any[];    @Input()    defaultLabel: string = "请选择…";    @Input()    multiSelect: boolean = false;    @Input()    width: string = "";    @Input()    height: string = "";    @Input()    valueField: string="";    @Input()    styleClass: string;    constructor(public api: API) {    }    ngOnInit() {        this.suggestions = [];    }    public onTouchedCallback: () => () => {};    public onChangeCallback: (_: any) => () => {};    public innerValue;    // 获取属性    get value(): any {        return this.innerValue;    };    // 设置属性,并触发监听器    set value(v: any) {        let vv = v && v.name?v.name:v;        this.innerValue = vv;        console.info(v)        if(this.valueField){            this.onChangeCallback(v[this.valueField]);        }else{            this.onChangeCallback(vv);        }    }    // 写入值    writeValue(value: any) {        if (value !== this.innerValue) {            this.innerValue = value;        }    }    // 注册变化处理事件    registerOnChange(fn: any) {        this.onChangeCallback = fn;    }    // 注册触摸事件    registerOnTouched(fn: any) {        this.onTouchedCallback = fn;    }    /**     * 查询数据     * @param $event     */    queryData($event: any) {        let value = $event.query;        let pageParms = {"first": 0, "rows": 9999};        this.api.call("abnormalOtherHandleController.waybillGoodsQuery", pageParms, {            name: value        }).ok(json => {            let result: any = json.result && json.result.content || [];            this.suggestions = result || [];        }).fail(err => {            throw new Error(err);        });    }}

1、剖析

上面没看到接口ControlValueAccessor,其实NG_VALUE_ACCESSOR就是其别名。固定格式如下

{    provide: NG_VALUE_ACCESSOR,    useExisting: forwardRef(() => GoodSelectComponent),    multi: true}

不知道说别名规范不规范,在别人的例子中都会继承ControlValueAccessor(implements ControlValueAccessor),但本文例子中就没有这种写法也可以实现双向数据绑定

这里面有两个比较重要的知识点

知识点1

1、当组件继承了ControlValueAccessor(implements ControlValueAccessor),那么writeValue、registerOnChange、registerOnTouched,在自定义组件过程中三者缺一不可,就相当于组件implements OnInit 其组件必须包含ngOnInit否则会报错

2、registerOnChange、registerOnTouched都是传入一个函数作为参数,所以上面都各自定义了一个空函数

public onTouchedCallback: () => () => {};public onChangeCallback: (_: any) => () => {};

这里展开registerOnChange来讲,这个函数是用来监听视图层值(就是[(ngModle)]的值)的变化,当视图层值变化时会调用这个方法,实现视图层传值到模型层,writeValue相反。

知识点2

1、setter、getter的使用,老大说仅适合[(ngModle)]传进来的场景,但在我用了一个月来看,这种拦截适合父传子传参的一切输入属性,当视图层接收父组件传进来的[parentvalue]值([parentvalue]="value"),在子组件通过@Input() set parentvalue(value: any){ this._value = value},(注意:这里的parentvalue要和传进来的同名) get parentvalue() {return this._value} ,(注意:this._value会隐式替换[parentvalue]="value"这里的value值)

2.误区

这个案例也有误导的地方就是set/get和writeValue的混用,前面说了writeValue()是M->V的过程,但这里set也是过滤拦截视图层的值的操作,那么究竟以哪个为准。

经过多次断点测试 writeValue()这个方法好像只会在组件ngAfterViewInit之前调用,当过了这个生命周期就不会再进入该方法。初始化的时候,将会使用表单模型中对应的初始值作为参数,调用 writeValue() 方法,但往往不会都有初始值的情况,所以才会出现

writeValue(value: any) {     if (value !== this.innerValue) {  // !== undefined null ''的情况出现         this.innerValue = value;     }}

那么显然主导ngModel双向数据绑定的是set/get拦截器,阿里的NG-ZORRO的组件大量用到set/get拦截器也是这个原理

 

推荐2个网址

   双向绑定底层原理简单案例

   双向绑定的过程

转载于:https://my.oschina.net/u/2949632/blog/917413

你可能感兴趣的文章
hdu 1015 Safecracker
查看>>
HDU 2562 奇偶位互换
查看>>
安装问题-安装Python3.6后文件夹Scripts为空
查看>>
HTML基础 内联样式改进 三毛语录
查看>>
053(六十五)
查看>>
python基础01
查看>>
Sollin算法的C++实现 BY gremount
查看>>
如何在pycharm上使用git
查看>>
关于前端的margin
查看>>
flume 自定义sink
查看>>
LocalConnection实现swf与swf之间通信
查看>>
改变UIAlertController的标题、内容的字体和颜色
查看>>
Membership学习(二)membership入门[xgluxv]
查看>>
Educational Codeforces Round 66 (Rated for Div. 2) A. From Hero to Zero
查看>>
鸡尾酒排序
查看>>
shell脚本 sed命令 特殊符号匹配的解决方法
查看>>
Nginx配置文件详细说明
查看>>
同时可以修改时间和日期的datetime_select and 有关时间的转换
查看>>
IOS Orientation, 想怎么转就怎么转~~~
查看>>
数组格式的多种写法
查看>>