总结下Vue和Angular组件交互方式
回想起来Vue和Angular,他们的父子组件的交互方式不管是组件传值的方式,还是监听值发生变化的方式都很相似,心里第一个想法就是他们的实现是不是也极为相似,还是让熟悉其中一个框架的人能迅速上手另一个?另外因为没用过React,不了解React组件交互是怎样的。这会儿特别想去读一下他们组件交互相关的源码,后面再来填上这个坑吧!
目录
一、组件交互
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组件交互是怎样的。这会儿特别想去读一下他们组件交互相关的源码,后面再来填上这个坑吧!
更多推荐
所有评论(0)