이 글을 읽게 되면 알게 되는 개념
- 깊은 복사, 얕은 복사
- 배열의 메모리 구조
- Javascript의 원시값
1. 이 글을 쓰는 이유
6개월 학원 과정을 마치거나 6개월 학원 과정을 수료한 후 바로 Angular 프런트엔드로 취업한 분들께 도움이 되는 글을 적고자한다.
Angular를 이용해 페이지를 만들면서 부분적으로 컴포넌트들로 쪼갤 때, 자식 컴포넌트와 부모 컴포넌트 사이에 유동적으로 정보를 나누면서 반응을 하려면 ngOnChanges 사이클을 사용해야만 한다.
반드시 사용해야 되는 것은 아니고, 다른 우회 방법이 있긴 하지만, ngOnChanges를 사용했을 때 깔끔하게 코드가 나오는 경우가 있어 사용 방법을 반드시 알아야한다.
2. 사용 예시 (난이도: 최하)
ngOnChanges는 부모 컴포넌트의 public 클래스 변수가 자식 컴포넌트의 프로퍼티 Input값으로 들어가 자식 컴포넌트가 Input값에 따라 반응할 수 있도록 해주는 라이프사이클 함수이다.
예를 들어
부모컴포넌트.ts
export class 부모컴포넌트 {
busName: string = '내 이름은 버스입니다';
ngOnInit() {
let i: number = 0;
setTimeout(() => {
this.busName += i.toString();
i++;
}, 1000)
}
}
자식컴포넌트.ts
export class 자식컴포넌트 {
@Input() busName: string;
ngOnChanges(changes: SimpleChange) {
console.log(this.busName);
}
}
이 페이지를 실행하면, console에 다음과 같이 1초(1000ms)마다 다음과 같이 찍힐 것이다.
내 이름은 버스입니다.0
내 이름은 버스입니다.1
내 이름은 버스입니다.2
내 이름은 버스입니다.3
...
자식컴포넌트의 ngOnChanges가 부모로부터 받은 busName 프로퍼티의 변화를 감지해서 라이프사이클을 실행시키는 것이다.
3. 사용 예시 (잘못된 사용 예시)
방금은 너무 쉬웠다. 그렇다면 부모 클래스 변수의 배열을 자식 컴포넌트에 넘겨줘보자.
부모컴포넌트.ts
export class 부모컴포넌트 {
busSeatNoList: number[] = [];
ngOnInit() {
let i: number = 0;
setTimeout(() => {
this.busSeatNoList.push(i);
i++;
}, 1000)
}
}
자식컴포넌트.ts
export class 자식컴포넌트 {
@Input() busSeatNoList: number[];
ngOnChanges(changes: SimpleChange) {
console.log(this.busSeatNoList);
}
}
결과를 예상해보자면, 다음과 같을 것이다.
[1]
[1, 2]
[1, 2, 3]
[1, 2, 3, 4]
[1, 2, 3, 4, 5]
하지만 그렇지 않다. 결과는 이럴 것이다.
[]
왜냐하면 ngOnChanges는 부모의 busSeatNoList의 변화를 감지하지 못하기 때문이다.
ngOnChanges는 Input 프로퍼티값의 메모리 주소를 바라보고 있다. 그리고 메모리 주소가 바뀌면 그 때 ngOnChanges가 발동한다.
Javascript에서 배열의 메모리 구조에 대해서 배우려면 다음을 참고하면 된다.
https://evan-moon.github.io/2019/06/15/diving-into-js-array/
4. 사용 예시 (배열을 감지하게 하려면)
부모컴포넌트.ts
export class 부모컴포넌트 {
busSeatNoList: number[] = [];
ngOnInit() {
let i: number = 0;
setTimeout(() => {
this.busSeatNoList = [ ...this.busSeatNoList, i ];
i++;
}, 1000)
}
}
자식컴포넌트.ts
export class 자식컴포넌트 {
@Input() busSeatNoList: number[];
ngOnChanges(changes: SimpleChange) {
console.log(this.busSeatNoList);
}
}
아까와 다른 점은 this.busSeatNoList의 메모리 주소를 바꿔준 점이다.
this.busSeatNoList = [ ...this.busSeatNoList, i ];
이렇게 하면 this.busSeatNoList의 메모리가 바뀐다.
[ ...this.busSeatNoList, i ];
이런 식으로 하는 배열을 새로 만드는 방법은 불변성(Immutability)를 지키는 데 자주 사용하는 방식이다. 불변성이라는 건 여기서 설명하려면 말이 길어지니 그냥 “불변성이라는 것을 위해 사용되기도 하는 구나”라고 넘어가면 된다.
5. 또 다른 예시들
ngOnChanges는 Input 프로퍼티값의 메모리 주소를 바라보고 있다. ~라고 했다.
이제 메모리 주소가 바뀌는 지 안 바뀌는 지 헷갈리는 유형에 대해서 설명하겠다. 이것을 유의하면 된다.
const A = {
ab: 12;
cd: 34;
};
A.ab = 33;
이 A라는 객체 안에 있는 ab 프로퍼티에 12 대신 33을 넣었다. A라는 객체의 메모리 주소는 바뀌었을까? 그렇지 않다.
그렇다면 메모리 주소를 바꾸려면 어떻게 해야할 까?
let A = {
ab: 12;
cd: 34;
};
A = { ...A, ab: 33 };
let A = {
ab: 12;
cd: 34;
};
A = { ...A, ab: 33 };
다음과 같이 하면 A의 주소가 바뀐다. 새로운 객체를 만들어서 A를 담은 다음 다시 A변수에 값을 넣었기 때문이다.
깊은 복사와 얕은 복사에 대해 공부해보자.
https://cocobi.tistory.com/156
6. ngOnChanges가 100% 감지하는 원시값
자바스크립트에는 원시값이 있다.
이 7가지 원시값들은 항상 100% ngOnChanges가 변화를 감지할 수 있다. 원시값들은 객체나 배열 처럼 메모리 주소가 있는 것이 아니라 그냥 변수의 메모리에 값이 박히는 방식이다. 원시값들은 다른 원시값들에 의해 메모리가 덮어씌여진다. 그래서 항상 ngOnChanges가 변화를 감지할 수 있는 것이다.
7. 결론
나는 이 글에 대해서 계속 써야지 써야지 하다가 이제야 쓴다.
ngOnChanges에 대해서 알아보았다. ngOnChanges의 매우 기본만 다뤘는 데, ngOnChanges는 여러 개의 프로퍼티들 중에 단 하나라도 수정이 되면 작동한다. 그래서 불필요한 동작을 막아줘야 하고, 그건 SimpleChange를 사용하면 된다.