이 프로젝트는 프레임워크에 구애받지 않고 테스트하기 쉽고 개발자 친화적인 방식으로 애플리케이션 상태를 모델링하기 위한 독자적인 패턴을 구현하여 SPA 개발의 어려움을 완화하는 것을 목표로 합니다. Sparix를 사용하면 유형이 안전한 저장소에 상태를 캡슐화하고 해당 상태에서 발생할 수 있는 변환을 정의할 수 있습니다. 업데이트는 결코 상태를 변경할 수 없으며, 대신 (redux 감속기와 마찬가지로) 상태의 새로운 변환된 인스턴스를 생성합니다. 변환된 상태 시퀀스는 RxJS Observable<State>
로 공개적으로 제공됩니다.
Sparix는 TypeScript로 작성되었으며 코드 샘플도 마찬가지입니다. RxJS와 마찬가지로 NPM 모듈에 유형 정의가 포함된 JavaScript 라이브러리로 배포됩니다.
$ npm i -S sparix rxjs
import { Store } from 'sparix'
import { add } from 'ramda'
export interface CounterState {
count : number
}
const initialState : CounterState = {
count : 0
}
export class Counter extends Store < CounterState > {
constructor ( ) {
super ( initialState )
}
increment ( ) {
// ALL THESE ARE EQUIVALENT
this . update ( state => { count : state . count + 1 } )
this . updateState ( { count : val => val + 1 } )
this . updateState ( { count : add ( 1 ) } ) // Using Ramda's automatically curryied functions
}
}
import { Counter , CounterState } from './counter'
const counter = new Counter ( )
// Recommended way
const state$ : Observable < CounterState > = counter . state$
const count$ : Observable < number > = counter . map ( state => state . count )
// Alternative way (useful for testing)
expect ( counter . currentState . count ) . toEqual ( 0 )
counter . increment ( )
expect ( counter . currentState . count ) . toEqual ( 1 )
첫째, 패턴이다. 둘째, RxJS를 기반으로 한 구현입니다. 구현은 매우 간단하며 다른 반응형 라이브러리로 다시 작성하는 데 몇 시간밖에 걸리지 않습니다. 그러나 SPA 세계는 React와 Angular2가 지배하고 후자는 RxJS와 함께 제공되므로 sparix의 참조 구현에 이 라이브러리를 사용하는 것이 합리적입니다.
sparix에서 상태는 상태 전환의 불변 스트림인 Observable<State>
로 모델링됩니다.
Store의 API는 단순하게 유지되며, 오래된 객체 지향 프로그래밍에서와 마찬가지로 모든 복잡한 논리는 캡슐화되어 외부에서 숨겨집니다. Store를 테스트하려면 입력을 시뮬레이션하고(공개 메서드 중 하나를 호출하여) 출력(상태)을 확인하기만 하면 됩니다.
Sparix는 상태 변환이 이전 상태를 변경하지 않는 순수 함수로 정의되는 redux 원칙(또는 Elm 아키텍처 원칙)을 완전히 준수합니다.
Redux에서는 상태를 업데이트해야 할 때 작업을 전달합니다. 하지만 자세히 살펴보면 작업이 두 가지 범주로 분류될 수 있다는 것을 알 수 있습니다.
내 주장은 (대부분의 경우처럼) 단순히 단일 Store의 상태를 업데이트하는 것이 목표인 경우 작업이 너무 무거운 메커니즘이라는 것입니다. sparix에서 Store는 다음과 같은 세레모니 없이 상태를 직접 업데이트할 수 있습니다.
// Increment counter
this . update ( state => ( {
counter : state . counter + 1
} ) )
이러한 상태 업데이트 프로그램을 작성하는 더 세밀하고 선언적인 방법이 있습니다.
this . updateState ( {
counter : prevCounter => prevCounter + 1
} )
또는 더 나은 방법은 다음과 같습니다.
const increment = value => value + 1
this . updateState ( {
counter : increment
} )
실제로 Ramda의 자동 커링을 활용해야 합니다.
import { add } from 'ramda'
this . updateState ( {
counter : add ( 1 )
} )
나는 이러한 상태 업데이트 프로그램을 익명의 작업으로 생각하고 싶습니다. Redux에서는 감속기를 파견하는 것과 같습니다. 하지만 액션 제작자는 어떻습니까? 글쎄요, 실제로는 필요하지 않습니다.
const increment = val => val + 1
class SomeStore extends Store < SomeState > {
// constructor
incrementCounter ( ) {
this . updateState ( {
counter : increment
} )
}
}
여기서 incrementCounter()
메서드는 Store의 공개 API의 일부입니다. 더 이상 액션 생성자가 생성한 전역 액션을 전달할 필요가 없습니다. 메소드를 호출하면 됩니다!
Sparix는 애플리케이션의 핵심을 모델링하는 것입니다. 그런데 코어란 무엇인가? 아니면 핵심에 없는 것은 무엇입니까?
애플리케이션 코어는 불가지론적이어야 합니다. 프레임워크와 데이터베이스에 구애받지 않고, 프레젠테이션 계층에 구애받지 않으며, 데이터 가져오기 메커니즘과 프로토콜에 구애받지 않습니다. 그것은 모든 것에 불가지론적이어야 합니다.
애플리케이션 코어는 HTML, DOM, Angular 또는 React, 로컬 저장소, HTTP 또는 WebSocket에 대해 알지 못합니다. 웹 브라우저에 있다는 것조차 모릅니다! 코드 한 줄도 변경하지 않고 동일한 애플리케이션 코어를 Cordova, NativeScript 또는 Electron 앱에서 재사용할 수 있어야 합니다!
그러면 코어에는 무엇을 넣나요? 대답은 매우 간단합니다. 그 밖의 모든 것 ! 코어의 일부가 될 수 있다면 코어의 일부가 되어야 합니다. 모든 비즈니스 로직, 데이터 변환, 상호 작용 로직은 애플리케이션 코어로 모델링되어야 합니다. 그리고 그 중 어느 것도 이를 모델링하는 데 사용된 프로그래밍 언어 이외의 다른 것에 의존해서는 안 됩니다.
그럼 스파릭스로 돌아가죠. 이는 RxJS 및 sparix 자체를 제외하고 타사 라이브러리 및 프레임워크에 의존하지 않는 애플리케이션 코어를 모델링하는 데 도움이 됩니다. 하지만 그건 별로 문제가 되지 않습니다. Observable은 표준 ECMAScript 기능이 되는 과정에 있으며 sparix는 비침해적 라이브러리이므로 애플리케이션 코어의 하위 집합만 쉽게 모델링할 수 있습니다.