一、组件交互
Vue组件交互
1、父传子通过Prop的方式

父组件写法:


	<shoppingCar :propA="aValue" ></shoppingCar>

子组件写法:
注:在子组件中可以对传入的属性进行类型验证和默认值的配置,也可以自定义验证函数

 props: {
   // 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
    propA: Number,
    // 多个可能的类型
    propB: [String, Number],
    // 必填的字符串
    propC: {
      type: String,
      required: true
    },
    // 带有默认值的数字
    propD: {
      type: Number,
      default: 100
    },
    // 带有默认值的对象
    propE: {
      type: Object,
      // 对象或数组默认值必须从一个工厂函数获取
      default: function () {
        return { message: 'hello' }
      }
    },
    // 自定义验证函数
    propF: {
      validator: function (value) {
        // 这个值必须匹配下列字符串中的一个
        return ['success', 'warning', 'danger'].indexOf(value) !== -1
      }
    }
  }
2、子传父通过自定义事件$emit

注:事件名推荐始终使用 xxx-xxx 的命名方法,因为v-on 事件监听器在 DOM 模板中会被自动转换为全小写 (HTML 是大小写不敏感的),所以假如你发射出去的事件名为myEvent,父组件的v-on:myEvent 将会变成 v-on:myevent,从而导致 myEvent 不可能被监听到。

<button @click="btnClick" >子传父</button>

 btnClick() {
     this.$emit("son-click","测试子传父");
 }

父组件写法

<shoppingCar :propA="aValue" @son-click="logParams"></shoppingCar>

 logParams(params) {
      console.log(params);
 }
Angular组件交互
1、父传子通过@Input()输入型属性传递

父组件写法:
注:这里跟Vue的写法很类似,只不过vue绑定属性是用的v-bind:简写之后为:xxx,
而Angular中使用的[ xxx ]


<app-news #news [news]="news"></app-news>

子组件写法:

export class NewsComponent implements OnInit {
  @Input() news:string;
  constructor() { }
  
  ngOnInit() {
  
  }
 }
1.1、通过本地变量互动

可以在父组件模板里,新建一个本地变量来代表子组件,然后利用这个变量来读取子组件的属性和调用子组件的方法。这里直接用官方文档里的代码

 <h3>Countdown to Liftoff (via local variable)</h3>
  <button (click)="timer.start()">Start</button>
  <button (click)="timer.stop()">Stop</button>
  <div class="seconds">{{timer.seconds}}</div>
  <app-countdown-timer #timer></app-countdown-timer>
1.2、通过@ViewChild获取到子组件:

Angular官方文档的原话为:

这个本地变量方法是个简单便利的方法。但是它也有局限性,因为父组件-子组件的连接必须全部在父组件的模板中进行。父组件本身的代码对子组件没有访问权。
如果父组件的类需要读取子组件的属性值或调用子组件的方法,就不能使用本地变量方法。
当父组件类需要这种访问时,可以把子组件作为 ViewChild,注入到父组件里面。

这里也直接用官方文档的代码:

export class CountdownViewChildParentComponent implements AfterViewInit {

  @ViewChild(CountdownTimerComponent)
  // 可以通过timerComponent访问子组件的属性或方法
  private timerComponent: CountdownTimerComponent;

  seconds() { return 0; }

  ngAfterViewInit() {
    // 如果需要操作子组件的Dom元素,则在此钩子函数内操作
  }

  start() { this.timerComponent.start(); }
  stop() { this.timerComponent.stop(); }
}
2、子传父通过emit

子组件写法:
注:这点跟Vue很相似,Angular官方文档的原话为:

子组件暴露一个 EventEmitter 属性,当事件发生时,子组件利用该属性 emits(向上弹射)事件。
父组件绑定到这个事件属性,并在事件发生时作出回应。

export class NewsComponent implements OnInit {
  @Input() news:string;
  @Output() newsEvent = new EventEmitter<string>();
  constructor() { }
  ngOnInit() {
  
  }
  emitEvent(): void {
      this.newsEvent.emit(this.news);
  }
 }

父组件写法:

    // html文件
	<app-news #news [news]="news" (newsEvent)="sonEvent($event)"></app-news>
	// ts文件
	export class NewsComponent implements OnInit {
	   news = '新闻';
	   constructor() { }
	   ngOnInit() {
	 
	   }
	   sonEvent(event): void {
		    console.log(event);
	   }
   }
二、监听组件交互过程中值的变化
Vue监听props的变化
1、使用计算属性:

比如当pros传递过来的值不能直接使用的时候

props: {
  msg: String,
},
computed: {
  trimMsg(){
    return msg.trim();
  }
}

注:vue中getter,setter的写法,这里与Angular中的写法基本一致,都是利用属性访问器进行操作

props: {
  msg: String,
},
computed: {
  trimMsg: {
    // getter
    get: function () {
      return this.msg + '123123';
    },
    // setter
    set: function (newValue) {
     this.msg = newValue.trim();
    }
  }
} 
2、通过watch监听 触发事件

注:vue官方文档原话

虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。

props: {
  msg: String,
},
watch: {
   msg(newV,oldV) {
     // do something
     console.log(newV,oldV);
   } 
}

3、如果要监听的值为对象中的某个属性

props: {
  result: Object,
},
computed: {
  msg(){
    return result.msg;
  }
}
watch: {
   msg(newV,oldV) {
     // do something
     console.log(newV,oldV);
   } 
}
// 另一种写法
msg:{ //深度监听,可监听到对象、数组的变化
  handler (newV, oldV) {
     // do something
     console.log(newV,oldV)
   },
   deep:true   
 }
 
Angular监听输入属性的变化
1、在子组件里通过setter

使用一个输入属性的 setter,以拦截父组件中值的变化,并采取行动

export class NewsComponent implements OnInit {
  // tslint:disable-next-line:variable-name
  private _news = '';
  @Input() set news(v: string) {
    // 去除传入值头尾的空格
    this._news = v.trim();
  }
  get news(): string {
    return this._news;
  }
  constructor() { }

  ngOnInit() {

  }
2、在子组件里通过ngOnChanges钩子函数

当需要监视多个、交互式输入属性的时候,ngOnChanges比用属性的 setter 更合适。

父组件

export class HomeComponent implements OnInit {
  product: { name: string, price: number } = {
    name: '',
    price: 0
  };
  products: object[];
  title = '标题';
  constructor(private productService: MyProductService) { }

  ngOnInit(): void {
    this.products = this.productService.getProducts();
  }
  add(): void {
    this.products.push(this.product);
    this.products = this.products.concat([]);
  }
}

子组件

export class ProductCardComponent implements OnInit, OnChanges {
  @Input() products: string[];
  @Input() title: string;
  constructor() { }
  ngOnInit(): void {
  }
  ngOnChanges(changes: SimpleChanges): void {
    console.log('changes :>> ', changes);
    for (const propName in changes) {
      if (changes.hasOwnProperty(propName)) {
        const chng = changes[propName];
        const cur = JSON.stringify(chng.currentValue);
        const prev = JSON.stringify(chng.previousValue);
        console.log('发生变化的属性 :>> ', `${propName}: currentValue = ${cur}, previousValue = ${prev}`);
      }
    }
  }
}

在这里插入图片描述
注:
Angular中的ngOnChanges函数无法监听到引用类型的变化,比如对象或者数组等,猜测可能是直接用了===去比较的,但是数据发生变化后,Dom元素却更新了,这就让我很好奇了。后面研究源码的时候,再回来补上。

3、通过父子组件共享一个服务来通信

例子中父组件为新闻发布控制台,子组件为新闻页,新闻页关注发布控制台,当有新的新闻发布时,及时显示,控制台关注新闻是否已读,已读时加入历史记录。
其本质上是通过观察者模式,建立两个Subject,父子组件都相当于彼此的观察者,当Subject发布时,通知观察者进行相应操作。

共享的服务

export class NewsService {

  private newsPublishSource = new Subject<string>();
  private newsReadSource = new Subject<string>();

  newsPublish$ = this.newsPublishSource.asObservable();
  newsRead$ = this.newsReadSource.asObservable();
  constructor() { }

  publishNews(news: string): void {
    this.newsPublishSource.next(news);
  }
  readNews(news: string): void {
    this.newsReadSource.next(news);
  }
}

父组件

export class NewsControlComponent implements OnInit {
  history: string[] = [];
  newsArr = [
    '再画个花边的被窝',
    '画上灶炉与柴火',
    '我们生来一起活'
  ];
  constructor(private newsService: NewsService) {
    newsService.newsRead$.subscribe(news => {
      this.history.push(news);
    });
  }

  ngOnInit(): void {
  }
  publishNews(news: string): void {
    // 发布主题,通知所有关注该主题的观察者
    this.newsService.publishNews(news);
  }
}

子组件

constructor(private newsService: NewsService) {
    this.subscription = newsService.newsPublish$.subscribe(news => {
      // do something
      this.news = news;
      this.isPublish = true;
      this.isRead = false;
    });
  }

  ngOnInit(): void {
  }
  readNews(): void {
    this.isRead = true;
    // 发布主题,通知所有关注该主题的观察者
    this.newsService.readNews(this.news);
  }
  ngOnDestroy(): void {
    this.subscription.unsubscribe();
  }
}

关于为什么要在NewsComponent的ngOnDestroy中退订?

注意,这个例子保存了 subscription 变量,并在 NewsComponent 被销毁时调用 unsubscribe() 退订。 这是一个用于防止内存泄漏的保护措施。实际上,在这个应用程序中并没有这个风险,因为 AstronautComponent 的生命期和应用程序的生命期一样长。但在更复杂的应用程序环境中就不一定了。
不需要在 NewsControlComponent 中添加这个保护措施,因为它作为父组件,控制着 MissionService 的生命期。

总结一下

回想起来Vue和Angular,他们的父子组件的交互方式不管是组件传值的方式,还是监听值发生变化的方式都很相似,心里第一个想法就是他们的实现是不是也极为相似,还是让熟悉其中一个框架的人能迅速上手另一个?另外因为没用过React,不了解React组件交互是怎样的。这会儿特别想去读一下他们组件交互相关的源码,后面再来填上这个坑吧!

Logo

前往低代码交流专区

更多推荐