>source

단일 페이지 애플리케이션에서 RxJS 동작 주제를 사용하여 애플리케이션의 상태와 모든 돌연변이를 처리하는 중앙 집중식 상점 클래스를 개발했습니다. 현재 애플리케이션 상태에 대한 업데이트를 수신하기 위해 애플리케이션의 여러 구성 요소가 스토어의 동작 주제를 구독합니다. 그런 다음이 상태는 UI에 바인딩되므로 상태가 변경 될 때마다 UI가 해당 변경 사항을 반영합니다. 구성 요소가 상태의 일부를 변경하려고 할 때마다 필요한 작업을 수행하고 작동 주제에 대해 다음에 호출하는 상태를 업데이트하는 상점에서 공개하는 함수를 호출합니다. 지금까지 특별한 것은 없습니다. (우리는 양방향 바인딩을 수행하는 프레임 워크로 Aurelia를 사용하고 있습니다)

우리가 직면 한 문제는 구성 요소가 저장소에서 수신하는 로컬 상태 변수를 변경하자마자 하위 구성 요소 자체에서 next ()가 호출되지 않은 경우에도 다른 구성 요소가 업데이트된다는 것입니다.

또한 Observable은 모든 구독자에게 다른 데이터 사본을 보내야하지만 그렇지 않은 것처럼 보이기 때문에 Observable 버전의 주제를 구독하려고했습니다.

모든 주제 가입자가 행동 주제에 저장된 객체에 대한 참조를받는 것처럼 보입니다.

import { BehaviorSubject, of } from 'rxjs'; 
const initialState = {
  data: {
    id: 1, 
    description: 'initial'
  }
}
const subject = new BehaviorSubject(initialState);
const observable = subject.asObservable();
let stateFromSubject; //Result after subscription to subject
let stateFromObservable; //Result after subscription to observable
subject.subscribe((val) => {
  console.log(`**Received ${val.data.id} from subject`);
  stateFromSubject = val;
});
observable.subscribe((val) => {
  console.log(`**Received ${val.data.id} from observable`);
  stateFromObservable = val;
});
stateFromSubject.data.id = 2;
// Both stateFromObservable and subject.getValue() now have a id of 2.
// next() wasn't called on the subject but its state got changed anyway
stateFromObservable.data.id = 3;
// Since observable aren't bi-directional I thought this would be a possible solution but same applies and all variable now shows 3

위의 코드로 stackblitz를 만들었습니다. https://stackblitz.com/edit/rxjs-bhkd5n

지금까지 우리가 가진 유일한 해결 방법은 다음과 같이 바인딩을 통해 에디션을 지원하는 구독자 일부의 상태를 복제하는 것입니다.

observable.subscribe((val) => {
  stateFromObservable = JSON.parse(JSON.stringify(val));
});

그러나 이것은 실제 솔루션보다 해킹처럼 느껴집니다. 더 좋은 방법이 있어야합니다 ...

  • 답변 # 1

    예, 모든 가입자는 행위 주체에서 동일한 객체 인스턴스를받습니다. 즉 행위 주체가 작동하는 방식입니다. 객체를 변경하려면 복제해야합니다.

    이 함수를 사용하여 각도 형식에 바인딩하려는 객체를 복제합니다

    const clone = obj =>
      Array.isArray(obj)
        ? obj.map(item => clone(item))
        : obj instanceof Date
        ? new Date(obj.getTime())
        : obj && typeof obj === 'object'
        ? Object.getOwnPropertyNames(obj).reduce((o, prop) => {
            o[prop] = clone(obj[prop]);
            return o;
          }, {})
        : obj;
    
    

    따라서 관찰 가능한 데이터가있는 경우 관찰 가능한 복제를 생성 할 수 있습니다.이 관찰 가능한 구독자는 다른 구성 요소에 영향을주지 않으면 서 변경 될 수있는 복제를 얻을 수 있습니다.

    clone$ = data$.pipe(map(data => clone(data)));
    
    

    따라서 데이터 만 표시하는 구성 요소는 효율성을 위해 data $를 구독 할 수 있고 데이터를 변경하는 구성 요소는 clone $을 구독 할 수 있습니다.

    Angular 라이브러리에 대한 https://github.com/adriandavidbrand/ngx-rxcache 및 https://medium.com/@adrianbrand/angular-state-management-with-rxcache- 468a865fc3fb 객체를 복제해야하므로 양식에 바인딩하는 데이터를 변경하지 않습니다.

    상점의 목표가 내 Angular 상태 관리 라이브러리와 같은 것 같습니다. 아이디어가 생길 수 있습니다.

    Aurelia에 익숙하지 않거나 파이프가있는 경우 매장에서 클론 $관찰 가능 데이터를 사용하여 복제 기능을 사용할 수 있으며 복제 파이프를 사용하여 템플릿으로 사용할 수있는 복제 기능을 사용할 수 있습니다

    data$ | clone as data
    
    

    중요한 부분은 언제 복제하고 복제하지 않는지를 아는 것입니다. 데이터가 변경 될 경우에만 복제해야합니다. 그리드에만 표시 될 데이터 배열을 복제하는 것은 실제로 비효율적입니다.

  • 답변 # 2

    The only workaround we have so far is to clone the state in some of our subscriber where we support edition through binding like follow:

    상점을 다시 쓰지 않으면 대답 할 수 없다고 생각합니다.

    const initialState = {
      data: {
        id: 1, 
        description: 'initial'
      }
    }
    
    

    이 상태 객체는 깊게 구조화 된 데이터를 가지고 있습니다. 상태를 변경해야 할 때마다 개체를 재구성해야합니다.

    또는

    const initialState = {
       1: {id: 1, description: 'initial'},
       2: {id: 2, description: 'initial'},
       3: {id: 3, description: 'initial'},
       _index: [1, 2, 3]
    };
    
    

    이것은 내가 만들 상태 객체의깊은과 같습니다. 키/값 쌍을 사용하여 ID와 객체 값을 매핑하십시오. 이제 선택기를 쉽게 작성할 수 있습니다.

    function getById(id: number): Observable<any> {
       return subject.pipe(
           map(state => state[id]),
           distinctUntilChanged()
       );
    }
    function getIds(): Observable<number[]> {
       return subject.pipe(
          map(state => state._index),
          distinctUntilChanged()
       );
    }
    
    

    데이터 개체를 변경하려는 경우 상태를 재구성하고 데이터를 설정해야합니다.

    function append(data: Object) {
        const state = subject.value;
        subject.next({...state, [data.id]: Object.freeze(data), _index: [...state._index, data.id]});
    }
    function remove(id: number) {
        const state = {...subject.value};
        delete state[id];
        subject.next({...state, _index: state._index.filter(x => x !== id)});
    }
    
    

    일단 당신이 그 일을했다. 상태 객체의 다운 스트림 소비자를 고정시켜야합니다.

    const subject = new BehaviorSubject(initialState);
    function getStore(): Observable<any> {
       return subject.pipe(
          map(obj => Object.freeze(obj))
       );
    }
    function getById(id: number): Observable<any> {
       return getStore().pipe(
          map(state => state[id]),
          distinctUntilChanged()
       );
    }
    function getIds(): Observable<number[]> {
       return getStore().pipe(
          map(state => state._index),
          distinctUntilChanged()
       );
    }
    
    

    나중에 이런 일을 할 때 :

    stateFromSubject.data.id = 2;
    
    

    런타임 오류가 발생합니다.

    와이즈 비즈

  • 답변 # 3

    이 예제에서 가장 큰 논리적 인 문제는 주제에 의해 전달 된 객체가 실제로 단일 객체 참조라는 것입니다. RxJS는 복제본을 만들기 위해 즉시 아무 것도 수행하지 않으며, 그렇지 않으면 필요하지 않은 경우 기본적으로 불필요한 작업이 발생합니다.

    따라서 가입자가받은 값을 복제 할 수는 있지만 원래 참조를 반환하는 BehaviorSubject.getValue ()에 액세스하기 위해 저장하지는 않습니다. 게다가 상태의 일부에 대해 동일한 참조를 갖는 것은 실제로 여러 가지 표시 구성 요소에 대해 배열을 재사용하고 처음부터 다시 작성해야 할 때와 같이 여러 가지 방법으로 실제로 유리합니다.

    대신하고 싶은 것은 Redux와 비슷한 단일 소스 소스 패턴을 사용하는 것입니다. 가입자가 클론을 확보하는 대신 상태를 변경 불가능한 객체로 취급하고 있습니다. 즉, 모든 수정은 새로운 상태가됩니다. 또한 현재의 새로운 상태와 필요한 변경 사항을 구성하고 새 복사본을 반환하는 작업 (Rux의 작업 + 감속기)에 대한 수정을 제한해야합니다.

    이제 모든 것이 많은 일처럼 들릴지 모르지만 공식 Aurelia Store Plugin을 살펴보아야합니다 .Aurelia Store Plugin은 현재와 거의 동일한 개념을 공유하고 있으며 Redux의 최고의 아이디어가 전달되도록합니다. 아우렐 리아의 세계.

    FYI: The above is written in TypeScript

  • 이전 gdb - 아직 정의되지 않은 함수에 중단 점 설정
  • 다음 python - 중첩 된 객체에서 부모 객체의 속성 편집