开发过程中,父组件给子组件传值的情况很常见,今天我们就来聊聊父组件给子组件传值可能会发生哪些意外,什么情况下子组件无法接收到父组件最新的传值;
传值情况:
-
基本数据类型 :父组件给子组件传递 基本数据类型,子组件使用变量接收传值;当传值发生变化,子组件接收的值也会跟着变化;
-
引用数据类型 :当父组件传递给子组件的 数据对象(对象或引用类型) 发生变化时,子组件接收到的值不一定会跟着变化;
以下是一些情况下子组件中的变量不会自动更新的场景:
- 输入属性为对象或引用类型 : 如果父组件传递给子组件的输入属性是一个对象或引用类型(如数组);
并且在父组件中修改了该对象的属性 或对其进行了重新赋值 ,子组件中的变量本身不会发生变化。
原因:
变更检测机制只会检测到对象或引用本身 的变化,而不会深度观察对象内部的属性或内容的变化。
在这种情况下,需要手动处理以确保子组件中的变量更新,可以使用 OnChanges 生命周期钩子、set方法接收、订阅 Input 属性的变化来实现。- 使用 @ViewChild 或服务来共享数据: 如果父组件通过 @ViewChild 或服务(例如共享服务)来传递数据给子组件,子组件中的变量通常不会自动更新。这是因为 @ViewChild 或服务提供的数据是一个单独的实例,与父组件中的数据不直接关联。在这种情况下,需要手动更新子组件中的变量,例如使用订阅机制或事件触发等方式。
举例说明:
1. 子组件使用set方法接收传值
javascript
// 父组件
@Component({
selector: 'app-parent',
template: `
<app-child [data]="parentData"></app-child>
<button (click)="changeData()">Change Data</button>
`
})
export class ParentComponent {
parentData = { value: 'Initial data' };
changeData() {
this.parentData.value = 'Updated data';
}
}
// 子组件
@Component({
selector: 'app-child',
template: `<p>{{ childData }}</p>`
})
export class ChildComponent {
private _data: any;
@Input()
set data(value: any) {
this._data = value;
// 在这里可以对数据进行进一步处理
}
get childData(): any {
return this._data;
}
}
在上述示例中,父组件通过 parentData 对象将数据传递给子组件的 data 输入属性。子组件使用 set 方法接收该数据,并将其存储在私有变量 _data 中。
当点击 "Change Data" 按钮时,父组件的 changeData() 方法将修改 parentData.value 的值为 'Updated data'。此时,子组件中的 set 方法会被触发,并接收到新的数据对象 { value: 'Updated data' }。子组件可以在 set 方法内部对数据进行相应的处理以更新子组件的状态。
set 方法的触发是由 Angular 的变更检测机制 自动处理的,无需手动触发该方法。
set 方法触发时机
初始化时触发 : 当父组件传递的数据被子组件
初始化
时,set 方法会在子组件创建和渲染过程中首次触发。这是设置初始值的时机。属性变化时触发: 如果父组件的数据发生了变化(例如由于用户交互或异步请求等),Angular 的变更检测机制会检测到属性的变化,并触发子组件中的 set 方法,从而更新子组件的相应属性。
get 方法触发时机
get 方法的触发是由子组件主动读取属性值时自动触发的,而不是由 Angular 的变更检测机制 触发的。因此,它并不会在每次属性变化时都触发。只有当子组件需要获取属性值时,get 方法才会被调用。
例如,在子组件的模板中使用插值表达式、绑定属性、或者在子组件的 TypeScript 代码中直接访问该属性时,get 方法会被调用。
2. 使用 ngOnChanges 生命周期钩子
在该钩子中监听父组件传入的输入属性变化,并在变化时手动更新子组件内部的变量。通过 SimpleChange 对象可以获取到新值和旧值进行比较,从而进行相应的处理。
javascript
// 父组件
@Component({
selector: 'app-parent',
template: `
<app-child [data]="parentData"></app-child>
<button (click)="changeData()">Change Data</button>
`
})
export class ParentComponent {
parentData = { value: 'Initial data' };
changeData() {
this.parentData.value = 'Updated data';
}
}
// 子组件
@Component({
selector: 'app-child',
template: `<p>{{ childData }}</p>`
})
export class ChildComponent implements OnChanges {
@Input() data: any;
childData: any;
ngOnChanges(changes: SimpleChanges) {
if (changes.data && changes.data.currentValue) {
this.childData = changes.data.currentValue;
}
}
}
3. ViewChild 使用static: false 传递数据
如果父组件通过 @ViewChild 或服务(例如共享服务)来传递数据给子组件,并且使用 static: true
,那么当父组件的值发生变化时,子组件通常不会自动更新。
相反,使用 static: false
,那么当父组件的值发生变化时,子组件也会相应地更新。
原理:
当使用 @ViewChild 来获取对子组件的引用时,如果将 static 设置为 false,它会使得 @ViewChild
成为一个查询,会在每次变更检测时重新查询子组件。这意味着如果父组件的值发生变化,子组件的引用会重新查询并得到更新。
在父组件中,通过 @ViewChild 获取子组件的引用,并传递值给子组件:
javascript
import { Component, ViewChild } from '@angular/core';
import { ChildComponent } from './child.component';
@Component({
selector: 'app-parent',
template: `
<app-child></app-child>
<button (click)="updateData()">Update Parent Data</button>
`
})
export class ParentComponent {
@ViewChild(ChildComponent, { static: false })
childComponent: ChildComponent;
parentData = { name: 'John', age: 25 };
updateData(): void {
this.parentData = { name: 'Jane', age: 30 };
this.childComponent.data = this.parentData;
// 或者调用子组件的方法进行更新:this.childComponent.updateData(this.parentData);
}
}
在子组件中,定义一个属性来接收父组件的值,并在模板中显示该数据:
javascript
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-child',
template: `
<div>Name: {{ data.name }}</div>
<div>Age: {{ data.age }}</div>
`
})
export class ChildComponent {
@Input()
data: any;
}
在上述示例中,当父组件的 updateData 方法被调用时,它会更新 parentData 的值,并将新值传递给子组件的 data 属性。由于 @ViewChild 使用了 static: false,子组件的引用会重新查询,在变更检测期间子组件会获取到新的值并进行更新。
总结来说,如果你使用 @ViewChild 并设置了 static: false,当父组件的值发生变化时,子组件也会随之更新。这使得父组件能够直接影响到子组件的状态和行为。
4. ViewChild 使用static: true 传递数据
使用 @ViewChild 来获取对子组件的引用时,如果将 static 设置为 true,它会使得 @ViewChild 成为一个静态查询,只在 组件初始化时 进行一次查询。这意味着子组件只会获取到父组件初始时的值,并且不会随着父组件值的变化而自动更新。
在这种情况下,需要手动更新子组件中的变量以反映父组件的变化。可以使用订阅机制、事件触发等方式,在父组件的值发生变化时通知子组件进行更新。
javascript
import { Component, ViewChild } from '@angular/core';
import { ChildComponent } from './child.component';
@Component({
selector: 'app-parent',
template: `
<app-child></app-child>
<button (click)="updateData()">Update Parent Data</button>
`
})
export class ParentComponent {
@ViewChild(ChildComponent, { static: true })
childComponent: ChildComponent;
parentData = { name: 'John', age: 25 };
updateData(): void {
this.parentData = { name: 'Jane', age: 30 };
this.childComponent.updateData(this.parentData); // 手动更新子组件中的变量
}
}
在子组件中,定义一个方法来接收父组件的值,并在模板中显示该数据:
javascript
import { Component } from '@angular/core';
@Component({
selector: 'app-child',
template: `
<div>Name: {{ data.name }}</div>
<div>Age: {{ data.age }}</div>
`
})
export class ChildComponent {
data: any;
updateData(newData: any): void {
this.data = newData;
}
}
在上述示例中,当父组件的 updateData 方法被调用时,它会更新 parentData 的值,并手动调用子组件的 updateData 方法来更新子组件中的变量。