NgRx의 장점 및 기능에 대한 심층 분석

게시 됨: 2022-03-11

팀장이 개발자에게 특정 문제를 해결하기 위해 몇 가지 방법을 작성하는 대신 많은 상용구 코드를 작성하도록 지시하는 경우 설득력 있는 인수가 필요합니다. 소프트웨어 엔지니어는 문제 해결사입니다. 그들은 일을 자동화하고 불필요한 상용구를 피하는 것을 선호합니다.

NgRx는 일부 상용구 코드와 함께 제공되지만 개발을 위한 강력한 도구도 제공합니다. 이 기사는 코드 작성에 더 많은 시간을 할애하면 노력할 가치가 있는 이점을 얻을 수 있음을 보여줍니다.

대부분의 개발자는 Dan Abramov가 Redux 라이브러리를 출시했을 때 상태 관리를 사용하기 시작했습니다. 일부는 상태 관리가 부족해서가 아니라 추세였기 때문에 상태 관리를 사용하기 시작했습니다. 상태 관리를 위해 표준 "Hello World" 프로젝트를 사용하는 개발자는 동일한 코드를 반복해서 작성하여 이득 없이 복잡성을 증가시키는 자신을 빠르게 발견할 수 있습니다.

결국 일부는 좌절하고 국가 관리를 완전히 포기했습니다.

NgRx의 초기 문제

이 상용구 문제가 NgRx의 주요 문제라고 생각합니다. 처음에는 그 배후의 큰 그림을 볼 수 없었습니다. NgRx는 프로그래밍 패러다임이나 사고방식이 아닌 라이브러리입니다. 그러나 이 라이브러리의 기능과 유용성을 완전히 이해하려면 지식을 조금 더 확장하고 함수형 프로그래밍에 집중해야 합니다. 그 때 상용구 코드를 작성하고 그것에 대해 만족할 수 있습니다. (내 말은) 나는 한때 NgRx 회의론자였다. 이제 저는 NgRx의 팬입니다.

얼마 전 저는 상태 관리를 사용하기 시작했습니다. 위에서 설명한 상용구 경험을 겪었으므로 라이브러리 사용을 중단하기로 결정했습니다. 저는 JavaScript를 사랑하기 때문에 오늘날 사용되는 모든 인기 있는 프레임워크에 대한 최소한의 기본 지식을 얻으려고 노력합니다. 다음은 React를 사용하면서 배운 내용입니다.

React에는 Hooks라는 기능이 있습니다. Angular의 구성 요소와 마찬가지로 Hook은 인수를 받아들이고 값을 반환하는 간단한 함수입니다. 후크는 부작용이라고 하는 상태를 가질 수 있습니다. 예를 들어 Angular의 간단한 버튼은 다음과 같이 React로 번역될 수 있습니다.

 @Component({ selector: 'simple-button', template: ` <button>Hello {{ name }}</button> `, }) export class SimpleButtonComponent { @Input() name!: string; } export default function SimpleButton(props: { name: string }) { return <button>{props.name} </button>; }

보시다시피 이것은 간단한 변환입니다.

  • SimpleButtonComponent => SimpleButton
  • @Input() 이름 => props.name
  • 템플릿 => 반환 값

그림: Angular Component와 React Hooks는 매우 유사합니다.
Angular Component와 React Hooks는 매우 유사합니다.

우리의 React 함수 SimpleButton 은 함수형 프로그래밍 세계에서 한 가지 중요한 특성을 가지고 있습니다. 순수 함수 입니다. 이 글을 읽고 계시다면 한 번쯤은 들어보셨을 거라 생각합니다. NgRx.io는 주요 개념에서 순수 함수를 두 번 인용합니다.

  • 상태 변경은 현재 상태와 새 상태를 계산하기 위한 최신 작업을 취하는 reducers 라고 하는 순수 함수 에 의해 처리됩니다.
  • 선택자는 상태 조각을 선택, 파생 및 구성하는 데 사용되는 순수 함수 입니다.

React에서 개발자는 Hooks를 가능한 한 순수 함수로 사용하는 것이 좋습니다. Angular는 또한 개발자가 Smart-Dumb 구성 요소 패러다임을 사용하여 동일한 패턴을 구현하도록 권장합니다.

그때 저는 제가 몇 가지 중요한 함수형 프로그래밍 기술이 부족하다는 것을 깨달았습니다. 함수형 프로그래밍의 핵심 개념을 배운 후 "아하! 순간”: NgRx에 대한 이해가 향상되었고 NgRx가 제공하는 이점을 더 잘 이해하기 위해 더 많이 사용하고 싶었습니다.

이 기사는 내 학습 경험과 NgRx 및 함수형 프로그래밍에 대해 얻은 지식을 공유합니다. NgRx용 API나 액션을 호출하거나 선택기를 사용하는 방법을 설명하지 않습니다. 대신 NgRx가 훌륭한 라이브러리라는 사실을 알게 된 이유를 공유합니다. NgRx는 비교적 새로운 추세일 뿐만 아니라 많은 이점을 제공합니다.

함수형 프로그래밍 부터 시작하겠습니다.

함수형 프로그래밍

함수형 프로그래밍은 다른 패러다임과 크게 다른 패러다임입니다. 이것은 많은 정의와 지침이 있는 매우 복잡한 주제입니다. 그러나 함수형 프로그래밍에는 몇 가지 핵심 개념이 포함되어 있으며 이를 아는 것은 NgRx(및 일반적으로 JavaScript)를 마스터하기 위한 전제 조건입니다.

이러한 핵심 개념은 다음과 같습니다.

  • 순수 기능
  • 불변 상태
  • 부작용

반복합니다: 그것은 단지 패러다임일 뿐입니다. 그 이상은 아닙니다. 우리가 다운로드하여 기능적 소프트웨어를 작성하는 데 사용하는 function.js 라이브러리는 없습니다. 응용 프로그램 작성에 대한 생각의 한 방법일 뿐입니다. 가장 중요한 핵심 개념인 순수 함수 부터 시작하겠습니다.

순수 기능

함수는 다음과 같은 두 가지 간단한 규칙을 따르는 경우 순수 함수로 간주됩니다.

  • 동일한 인수를 전달하면 항상 동일한 값을 반환합니다.
  • 내부 함수 실행과 관련된 관찰 가능한 부작용 부족(외부 상태 변경, I/O 작업 호출 등)

따라서 순수 함수는 일부 인수(또는 인수가 전혀 없음)를 받아들이고 예상 값을 반환하는 투명한 함수일 뿐입니다. 이 함수를 호출해도 네트워킹이나 일부 전역 사용자 상태 변경과 같은 부작용이 발생하지 않습니다.

세 가지 간단한 예를 살펴보겠습니다.

 //Pure function function add(a,b){ return a + b; } //Impure function breaking rule 1 function random(){ return Math.random(); } //Impure function breaking rule 2 function sayHello(name){ console.log("Hello " + name); }
  • 첫 번째 함수는 동일한 인수를 전달할 때 항상 동일한 응답을 반환하기 때문에 순수합니다.
  • 두 번째 함수는 비결정적이며 호출될 때마다 다른 응답을 반환하기 때문에 순수하지 않습니다.
  • 세 번째 함수는 부작용( console.log 호출)을 사용하기 때문에 순수하지 않습니다.

함수가 순수한지 여부를 식별하기 쉽습니다. 순수 함수가 불순 함수보다 나은 이유는 무엇입니까? 생각하는 것이 더 간단하기 때문입니다. 소스 코드를 읽고 있고 순수하다는 함수 호출을 보고 있다고 상상해 보십시오. 함수 이름이 올바르면 탐색할 필요가 없습니다. 당신은 그것이 아무것도 변경하지 않는다는 것을 알고 있습니다. 그것은 당신이 기대하는 것을 반환합니다. 시간을 크게 절약할 수 있으므로 많은 비즈니스 논리가 있는 거대한 엔터프라이즈 응용 프로그램이 있는 경우 디버깅에 중요합니다.

또한 테스트가 간단합니다. 내부에 아무 것도 주입하거나 일부 기능을 조롱할 필요가 없습니다. 인수를 전달하고 결과가 일치하는지 테스트하기만 하면 됩니다. 테스트와 논리 사이에는 강력한 연결이 있습니다. 구성 요소가 테스트하기 쉽다면 작동 방식과 이유를 이해하기 쉽습니다.

순수 함수에는 메모이제이션이라는 매우 편리하고 성능 친화적인 기능이 있습니다. 동일한 인수를 호출하면 동일한 값이 반환된다는 것을 안다면 결과를 캐시하고 다시 호출하는 데 시간을 낭비하지 않을 수 있습니다. NgRx는 확실히 메모이제이션 위에 있습니다. 그것이 빠른 이유 중 하나입니다.

변환은 직관적이고 투명해야 합니다.

“부작용은 어떻습니까? 그들은 어디로 가나요?” GOTO 강연에서 Russ Olsen은 우리 고객이 순수한 기능에 대해 비용을 지불하는 것이 아니라 부작용에 대해 비용을 지불한다고 농담했습니다. 사실입니다. Calculator 순수 함수가 어딘가에 인쇄되지 않으면 아무도 신경 쓰지 않습니다. 부작용은 함수형 프로그래밍 세계에서 그 자리를 차지하고 있습니다. 곧 보게 될 것입니다.

지금은 복잡한 애플리케이션 아키텍처를 유지 관리하는 다음 단계인 불변 상태( immutable state )의 핵심 개념으로 넘어가겠습니다.

불변 상태

불변 상태에 대한 간단한 정의가 있습니다.

  • 상태를 생성하거나 삭제할 수만 있습니다. 업데이트할 수 없습니다.

간단히 말해서 사용자 개체의 나이를 업데이트하려면 ... :

 let user = { username:"admin", age:28 }

... 다음과 같이 작성해야 합니다.

 // Not like this newUser.age = 30; // But like this let newUser = {...user, age:29 }

모든 변경 사항은 이전 속성에서 속성을 복사한 새 개체입니다. 이처럼 우리는 이미 불변의 상태에 있습니다.

문자열, 부울 및 숫자는 모두 변경할 수 없는 상태입니다. 기존 값을 추가하거나 수정할 수 없습니다. 대조적으로 Date는 변경 가능한 객체입니다. 항상 동일한 날짜 객체를 조작합니다.

불변성은 애플리케이션 전체에 적용됩니다. 연령을 변경하는 함수 내부에 사용자 개체를 전달하면 사용자 개체가 변경되지 않아야 하며 업데이트된 연령으로 새 사용자 개체를 생성하고 반환해야 합니다.

 function updateAge(user, age) { return {...user, age: age) } let user = {username: 'admin', age: 29}; let newUser = updateAge(user, 32);

왜 우리는 이것에 시간과 관심을 기울여야 합니까? 강조할 가치가 있는 몇 가지 이점이 있습니다.

백엔드 프로그래밍 언어의 한 가지 이점은 병렬 처리입니다. 상태 변경이 참조에 의존하지 않고 모든 업데이트가 새로운 객체인 경우 프로세스를 청크로 분할하고 동일한 메모리를 공유하지 않고도 수많은 스레드로 동일한 작업을 수행할 수 있습니다. 서버 간에 작업을 병렬화할 수도 있습니다.

Angular 및 React와 같은 프레임워크의 경우 병렬 처리는 애플리케이션 성능을 개선하는 더 유익한 방법 중 하나입니다. 예를 들어 Angular는 구성 요소를 다시 렌더링해야 하는지 여부를 식별하기 위해 입력 바인딩을 통해 전달하는 모든 개체의 속성을 확인해야 합니다. 그러나 기본값 대신 ChangeDetectionStrategy.OnPush 를 설정하면 각 속성이 아닌 참조로 확인합니다. 대규모 응용 프로그램에서 이것은 확실히 시간을 절약합니다. 상태를 변경할 수 없도록 업데이트하면 이 성능 향상을 무료로 얻을 수 있습니다.

모든 프로그래밍 언어와 프레임워크가 공유하는 불변 상태의 또 다른 이점은 순수 함수의 이점과 유사합니다. 생각하고 테스트하기가 더 쉽습니다. 변경 사항이 이전 상태에서 태어난 새로운 상태인 경우 작업 중인 항목을 정확히 알고 상태가 변경된 방법과 위치를 정확히 추적할 수 있습니다. 업데이트 기록을 잃지 않고 상태에 대한 변경 사항을 실행 취소/재실행할 수 있습니다(React DevTools가 한 예임).

그러나 단일 상태가 업데이트되면 해당 변경 내역을 알 수 없습니다. 은행 계좌의 거래 내역과 같은 불변 상태를 생각해 보십시오. 거의 필수품입니다.

이제 불변성과 순수성을 검토했으므로 나머지 핵심 개념인 부작용에 대해 알아보겠습니다.

부작용

부작용의 정의를 일반화할 수 있습니다.

  • 컴퓨터 과학에서 연산, 함수 또는 표현식은 로컬 환경 외부에서 일부 상태 변수 값을 수정하는 경우 부작용이 있다고 합니다. 즉, 작업 호출자에게 값(주요 효과)을 반환하는 것 외에 관찰 가능한 효과가 있습니다.

간단히 말해서, 함수 범위 외부에서 상태를 변경하는 모든 것(모든 I/O 작업 및 함수와 직접 연결되지 않은 일부 작업)은 부작용으로 간주될 수 있습니다. 그러나 사이드 이펙트는 함수형 프로그래밍 철학과 모순되기 때문에 순수 함수 내에서 사이드 이펙트를 사용하는 것을 피해야 합니다. 순수 함수 내에서 I/O 작업을 사용하면 순수 함수가 아닙니다.

그럼에도 불구하고 부작용이 없는 응용 프로그램은 무의미하기 때문에 어딘가에 부작용이 있어야 합니다. Angular에서 순수 함수는 부작용으로부터 보호되어야 할 뿐만 아니라 구성 요소 및 지시문에서도 사용하지 않아야 합니다.

Angular 프레임워크 내에서 이 기술의 아름다움을 구현하는 방법을 살펴보겠습니다.

일러스트: NgRx Angular Side Effect

함수형 각도 프로그래밍

Angular에 대해 이해해야 할 첫 번째 사항 중 하나는 유지 관리 및 테스트를 더 쉽게 수행할 수 있도록 구성 요소를 가능한 한 자주 더 작은 구성 요소로 분리해야 한다는 것입니다. 이것은 비즈니스 로직을 분할해야 하기 때문에 필요합니다. 또한 Angular 개발자는 구성 요소를 렌더링 목적으로만 남겨두고 모든 비즈니스 로직을 서비스 내부로 이동하는 것이 좋습니다.

이러한 개념을 확장하기 위해 Angular 사용자는 어휘에 "Dumb-Smart Component" 패턴을 추가했습니다. 이 패턴을 사용하려면 작은 구성 요소 내부에 서비스 호출이 존재하지 않아야 합니다. 비즈니스 로직이 서비스에 있기 때문에 우리는 여전히 이러한 서비스 메소드를 호출하고 응답을 기다린 다음 상태를 변경해야 합니다. 따라서 구성 요소에는 내부에 일부 동작 논리가 있습니다.

이를 방지하기 위해 비즈니스 및 동작 논리를 포함하는 하나의 스마트 구성 요소(루트 구성 요소)를 만들고 입력 속성을 통해 상태를 전달하고 출력 매개 변수를 수신하는 작업을 호출할 수 있습니다. 이런 식으로 작은 구성 요소는 실제로 렌더링 목적으로만 사용됩니다. 물론 루트 구성 요소에는 내부에 일부 서비스 호출이 있어야 하며 이를 제거할 수는 없지만 그 유틸리티는 렌더링이 아닌 비즈니스 로직으로만 제한됩니다.

Counter Component의 예를 살펴보겠습니다. 카운터는 값을 늘리거나 줄이는 두 개의 버튼과 currentValue 를 표시하는 하나의 displayField 가 있는 구성 요소입니다. 그래서 우리는 네 가지 구성 요소로 끝납니다.

  • 카운터 컨테이너
  • 증가 버튼
  • 감소 버튼
  • 현재 가치

모든 논리는 CounterContainer 내부에 있으므로 세 가지 모두 렌더러일 뿐입니다. 다음은 세 가지에 대한 코드입니다.

 @Component({ selector: 'decrease-button', template: `<button (click)="increase.emit()" [disabled]="disabled"> Decrease </button>`, }) export class DecreaseButtonComponent { @Input() disabled!: boolean; @Output() increase = new EventEmitter(); } @Component({ selector: 'current-value', template: `<button> {{ currentValue }} </button>`, }) export class CurrentValueComponent { @Input() currentValue!: string; } @Component({ selector: 'increase-button', template: `<button (click)="increase.emit()" [disabled]="disabled"> Increase </button>`, }) export class IncreaseButtonComponent { @Input() disabled!: boolean; @Output() increase = new EventEmitter(); }

그들이 얼마나 단순하고 순수한지 보십시오. 상태나 부작용이 없으며 입력 속성과 방출 이벤트에만 의존합니다. 그것들을 테스트하는 것이 얼마나 쉬운지 상상해 보십시오. 우리는 그것들을 순수한 구성 요소라고 부를 수 있습니다. 입력 매개변수에만 의존하고 부작용이 없으며 항상 동일한 매개변수를 전달하여 동일한 값(템플릿 문자열)을 반환합니다.

따라서 함수형 프로그래밍의 순수 함수는 Angular의 순수 구성 요소로 전송됩니다. 그러나 모든 논리는 어디로 가나요? 논리는 여전히 존재하지만 약간 다른 위치, 즉 CounterComponent 에 있습니다.

 @Component({ selector: 'counter-container', template: ` <decrease-button [disabled]="decreaseIsDisabled" (decrease)="decrease()"> </decrease-button> <current-value [currentValue]="currentValue"> </current-value> <increase-button (increase)="increase()" [disabled]="increaseIsDisabled"> </increase-button> `, }) export class CounterContainerComponent implements OnInit { @Input() disabled!: boolean; currentValue = 0; get decreaseIsDisabled() { return this.currentValue === 0; } get increaseIsDisabled() { return this.currentValue === 100; } constructor() {} ngOnInit(): void {} decrease() { this.currentValue -= 1; } increase() { this.currentValue += 1; } }

보시다시피 동작 논리는 CounterContainer 에 있지만 렌더링 부분은 순수 구성 요소에 대한 것이기 때문에 렌더링 부분이 없습니다(템플릿 내부에 구성 요소 선언).

여기에서 모든 데이터 조작과 상태 변경을 처리하기 때문에 원하는 만큼 서비스를 주입할 수 있습니다. 언급할 가치가 있는 한 가지는 깊은 중첩 구성 요소가 있는 경우 루트 수준 구성 요소를 하나만 생성해서는 안 된다는 것입니다. 더 작은 스마트 구성 요소로 나누고 동일한 패턴을 사용할 수 있습니다. 궁극적으로 각 구성 요소의 복잡성과 중첩 수준에 따라 다릅니다.

해당 패턴에서 바로 위에 있는 NgRx 라이브러리 자체로 쉽게 이동할 수 있습니다.

NgRx 라이브러리

모든 웹 애플리케이션을 세 가지 핵심 부분으로 나눌 수 있습니다.

  • 비즈니스 로직
  • 애플리케이션 상태
  • 렌더링 로직

비즈니스 논리, 응용 프로그램 상태 및 렌더링 논리에 대한 그림입니다.

비즈니스 로직 은 네트워킹, 입력, 출력, API 등과 같이 애플리케이션에서 발생하는 모든 동작입니다.

애플리케이션 상태 는 애플리케이션의 상태입니다. 현재 승인된 사용자로서 전역적일 수 있고 현재 카운터 구성요소 값으로서 로컬일 수도 있습니다.

Rendering Logic 은 DOM을 사용한 데이터 표시, 요소 생성 또는 제거 등과 같은 렌더링을 포함합니다.

Dumb-Smart 패턴을 사용하여 비즈니스 로직과 애플리케이션 상태에서 렌더링 로직을 분리했지만 둘 다 개념적으로 서로 다르기 때문에 둘도 나눌 수 있습니다. 애플리케이션 상태는 현재 시간의 앱 스냅샷과 같습니다. 비즈니스 로직은 앱에 항상 존재하는 정적 기능과 같습니다. 그것들을 나누는 가장 중요한 이유는 Business Logic이 대부분 우리가 응용 프로그램 코드에서 최대한 피하고 싶은 부작용이기 때문입니다. 기능적 패러다임을 가진 NgRx 라이브러리가 빛날 때입니다.

NgRx를 사용하면 이러한 모든 부분을 분리할 수 있습니다. 세 가지 주요 부분이 있습니다.

  • 감속기
  • 행위
  • 선택기

함수형 프로그래밍과 결합하면 세 가지 모두가 결합되어 모든 규모의 응용 프로그램을 처리할 수 있는 강력한 도구를 제공합니다. 각각을 살펴보겠습니다.

감속기

리듀서는 단순한 서명을 가진 순수 함수입니다. 이전 상태를 매개변수로 사용하고 이전 상태 또는 새 상태에서 파생된 새 상태를 반환합니다. 상태 자체는 애플리케이션의 수명 주기와 함께 작동하는 단일 개체입니다. 단일 루트 개체인 HTML 태그와 같습니다.

상태 개체를 직접 수정할 수 없으며 감속기로 수정해야 합니다. 다음과 같은 이점이 있습니다.

  • 상태 변경 논리는 한 곳에 있으며 상태가 어디에서 어떻게 변경되는지 알 수 있습니다.
  • 감속기 기능은 테스트 및 관리가 쉬운 순수 기능입니다.
  • 리듀서는 순수 함수이기 때문에 메모화할 수 있어 캐시에 저장하고 추가 계산을 피할 수 있습니다.
  • 상태 변경은 변경할 수 없습니다. 동일한 인스턴스를 업데이트하지 않습니다. 대신 항상 새 것을 반환합니다. 이것은 "시간 여행" 디버깅 경험을 가능하게 합니다.

이것은 감속기의 간단한 예입니다.

 function usernameReducer(oldState, username) { return {...oldState, username} }

매우 단순한 더미 감속기이지만 길고 복잡한 모든 감속기의 골격입니다. 그들은 모두 동일한 이점을 공유합니다. 애플리케이션에 수백 개의 감속기가 있을 수 있으며 원하는 만큼 만들 수 있습니다.

Counter Component의 경우 state와 reducer는 다음과 같습니다.

 interface State{ decreaseDisabled:boolean; increaseDisabled:boolean; currentValue:number; } const MIN_VALUE=0; const MAX_VALUE =100; function decreaseReducer(oldState) { const newValue = oldState.currentValue -1 return {...oldState,currentValue : newValue, decreaseDisabled: newValue===MIN_VALUE } function increaseReducer(oldState) { const newValue = oldState.currentValue + 1 return {...oldState,currentValue : newValue, decreaseDisabled: newValue===MAX_VALUE }

구성 요소에서 상태를 제거했습니다. 이제 상태를 업데이트하고 적절한 감속기를 호출하는 방법이 필요합니다. 그때 행동이 시작됩니다.

행위

작업은 NgRx에 감속기를 호출하고 상태를 업데이트하도록 알리는 방법입니다. 그것이 없으면 NgRx를 사용하는 것은 의미가 없습니다. 액션은 현재 리듀서에 연결하는 간단한 객체입니다. 호출한 후 적절한 감속기가 호출되므로 이 예에서는 다음 작업을 수행할 수 있습니다.

 enum CounterActions { IncreaseValue = '[Counter Component] Increase Value', DecreaseValue = '[Counter Component] Decrease Value', } on(CounterActions.IncreaseValue,increaseReducer); on(CounterActions.DecreaseValue,decreaseReducer);

우리의 행동은 감속기에 연결됩니다. 이제 컨테이너 구성 요소를 추가로 수정하고 필요할 때 적절한 작업을 호출할 수 있습니다.

 @Component({ selector: 'counter-container', template: ` <decrease-button [disabled]="decreaseIsDisabled" (decrease)="decrease()"> </decrease-button> <current-value [currentValue]="currentValue"> </current-value> <increase-button (increase)="increase()" [disabled]="increaseIsDisabled"> </increase-button> `, }) export class CounterContainerComponent implements OnInit { constructor(private store: Store<any>) {} decrease() { this.store.dispatch(CounterActions.DicreaseValue); } increase() { this.store.dispatch(CounterActions.IncreaseValue); } }

참고: 상태를 제거했으며 곧 다시 추가할 예정 입니다.

이제 CounterContainer 에는 상태 변경 논리가 없습니다. 무엇을 발송할지 알고 있을 뿐입니다. 이제 이 데이터를 뷰에 표시할 방법이 필요합니다. 이것이 선택자의 유용성입니다.

선택기

선택기도 매우 단순한 순수 함수이지만 감속기와는 달리 상태를 업데이트하지 않습니다. 이름에서 알 수 있듯이 선택기는 단순히 선택합니다. 이 예에서는 세 가지 간단한 선택기를 가질 수 있습니다.

 function selectCurrentValue(state) { return state.currentValue; } function selectDicreaseIsDisabled(state) { return state.decreaseDisabled; } function selectIncreaseIsDisabled(state) { return state.increaseDisabled; }

이러한 선택기를 사용하여 스마트 CounterContainer 구성 요소 내부의 각 상태 조각을 선택할 수 있습니다.

 @Component({ selector: 'counter-container', template: ` <decrease-button [disabled]="ecreaseIsDisabled$ | async" (decrease)="decrease()" > </decrease-button> <current-value [currentValue]="currentValue$ | async"> </current-value> <increase-button (increase)="increase()" [disabled]="increaseIsDisabled$ | async" > </increase-button> `, }) export class CounterContainerComponent implements OnInit { decreaseIsDisabled$ = this.store.select(selectDicreaseIsDisabled); increaseIsDisabled$ = this.store.select(selectIncreaseIsDisabled); currentValue$ = this.store.select(selectCurrentValue); constructor(private store: Store<any>) {} decrease() { this.store.dispatch(CounterActions.DicreaseValue); } increase() { this.store.dispatch(CounterActions.IncreaseValue); } }

이러한 선택은 기본적으로 비동기식입니다(일반적으로 Observables와 마찬가지로). 이것은 적어도 패턴 관점에서 중요하지 않습니다. 상태에서 무언가를 선택하기 때문에 동기식의 경우에도 마찬가지입니다.

뒤로 물러나 큰 그림을 보고 지금까지 달성한 ​​것이 무엇인지 살펴보겠습니다. 서로 거의 분리된 세 가지 주요 부분이 있는 카운터 응용 프로그램이 있습니다. 애플리케이션 상태가 자체적으로 관리되는 방식이나 렌더링 계층이 상태를 렌더링하는 방식을 아는 사람은 아무도 없습니다.

분리된 부분은 브리지(액션, 선택기)를 사용하여 서로 연결합니다. 그것들은 전체 State Application 코드를 가져와 모바일 버전과 같은 다른 프로젝트로 이동할 수 있을 정도로 분리됩니다. 우리가 구현해야 할 유일한 것은 렌더링입니다. 그러나 테스트는 어떻습니까?

내 겸손한 의견으로는 테스트가 NgRx의 가장 좋은 부분입니다. 이 샘플 프로젝트를 테스트하는 것은 틱택토 게임과 유사합니다. 순수한 기능과 순수한 구성 요소만 있으므로 테스트하는 것은 쉽습니다. 이제 이 프로젝트가 수백 개의 구성 요소로 더 커지면 상상해 보십시오. 우리가 같은 패턴을 따른다면 더 많은 조각을 함께 추가할 것입니다. 지저분하고 읽을 수 없는 소스 코드 덩어리가 되지는 않을 것입니다.

거의 다 끝났습니다. 다루어야 할 중요한 사항은 하나뿐입니다. 바로 부작용입니다. 지금까지 부작용에 대해 여러 번 언급했지만 어디에 보관해야 하는지 설명이 부족했습니다.

그 이유는 부작용이 케이크 위에 장식되어 있고 이 패턴을 구축함으로써 애플리케이션 코드에서 부작용을 제거하는 것이 매우 쉽기 때문입니다.

부작용

카운터 애플리케이션에 타이머가 있고 3초마다 값이 자동으로 1씩 증가한다고 가정해 보겠습니다. 이것은 어딘가에 살아야 하는 단순한 부작용입니다. 정의상 Ajax 요청과 동일한 부작용입니다.

부작용에 대해 생각해보면 대부분이 존재하는 두 가지 주요 이유가 있습니다.

  • 상태 환경 외부에서 모든 작업 수행
  • 애플리케이션 상태 업데이트

예를 들어 LocalStorage 내부에 일부 상태를 저장하는 것이 첫 번째 옵션이고 Ajax 응답에서 상태를 업데이트하는 것이 두 번째 옵션입니다. 그러나 둘 다 같은 서명을 공유합니다. 각 부작용에는 시작점이 있어야 합니다. 작업을 시작하라는 메시지를 표시하려면 최소한 한 번은 호출해야 합니다.

앞서 설명했듯이 NgRx에는 누군가에게 명령을 내리기 위한 훌륭한 도구가 있습니다. 그것은 행동입니다. 액션을 디스패치하여 부작용을 호출할 수 있습니다. 의사 코드는 다음과 같을 수 있습니다.

 function startTimer(){ setInterval(()=>{ console.log("Hello application"); },3000) } on(CounterActions.StartTime,startTimer) ... // We start timer by dispatching an action dispatch(CounterActions.StartTime);

아주 사소한 일입니다. 앞서 언급했듯이 부작용은 무언가를 업데이트하거나 업데이트하지 않습니다. 부작용이 업데이트되지 않으면 할 일이 없습니다. 우리는 그냥 둡니다. 하지만 상태를 업데이트하려면 어떻게 해야 할까요? 구성 요소가 상태 업데이트를 시도하는 것과 같은 방식으로 다른 작업을 호출합니다. 그래서 우리는 상태를 업데이트하는 사이드 이펙트 내에서 액션을 호출합니다:

 function startTimer(store) { setInterval(()=> { // We are dispatching another action dispatch(CounterActions.IncreaseValue) }, 3000) } on(CounterActions.StartTime, startTimer); ... // We start timer by dispatching an action dispatch(CounterActions.StartTime);

이제 완전한 기능의 애플리케이션이 생겼습니다.

NgRx 경험 요약

NgRx 여정을 마치기 전에 언급하고 싶은 몇 가지 중요한 주제가 있습니다.

  • 표시된 코드는 이 기사를 위해 내가 발명한 간단한 의사 코드입니다. 데모용으로만 적합합니다. NgRx는 실제 소스가 있는 곳입니다.
  • 함수형 프로그래밍을 NgRx 라이브러리와 연결하는 것에 대한 내 이론을 증명하는 공식 지침은 없습니다. 고도로 숙련된 사람들이 작성한 수십 개의 기사와 소스 코드 샘플을 읽고 형성된 제 생각일 뿐입니다.
  • NgRx를 사용한 후에는 이 간단한 예제보다 훨씬 더 복잡하다는 것을 확실히 깨닫게 될 것입니다. 내 목표는 실제보다 단순하게 보이게 하는 것이 아니라 약간 복잡하고 목적지까지 더 긴 경로를 초래할 수 있지만 추가 노력을 기울일 가치가 있음을 보여주기 위한 것입니다.
  • NgRx의 최악의 사용법은 애플리케이션의 크기나 복잡성에 관계없이 모든 곳에서 사용하는 것입니다. NgRx를 사용 하지 말아야 하는 경우가 있습니다. 예를 들어 양식에서. NgRx 내부에서 양식을 구현하는 것은 거의 불가능합니다. 양식은 DOM 자체에 붙어 있습니다. 그들은 따로 살 수 없습니다. 그것들을 분리하려고 하면 NgRx뿐만 아니라 일반적으로 웹 기술을 싫어하게 될 것입니다.
  • 때로는 작은 예에서도 동일한 상용구 코드를 사용하는 것이 미래에 우리에게 도움이 되더라도 악몽으로 바뀔 수 있습니다. 그렇다면 NgRx 생태계(ComponentStore)의 일부인 또 다른 놀라운 라이브러리와 통합하십시오.