본문 바로가기

HTML ⁄ CSS ⁄ JS

상태관리(Stage Management)와 옵저버패턴(Observer Pattern)

 

자동차경주 미션에서 아쉽게도 반영하지 못한 것이 있었다. isGameOver, isPopupShowing 과 같이 게임의 '상태'를 관리해서 View가 모델의 상태 변화를 '감지'하도록 하는 것이다. 갈피를 잡지 못한 나를 위해 리뷰어님은 '옵저버 패턴'이라는 키워드로 공부해 볼 것을 추천해주셨다. 이 기회에 상태관리와 자바스크립트에서의 상태관리 방법에 대해 정리해보자.

 

리뷰어님의 조언👍

 

상태관리는 어떻게 하는걸까

'상태(State)'는 변화하기 때문에 관리의 대상이 되는 모든 데이터 값을 말한다. 거꾸로 말하면, 변하지 않는 값은 관리할 필요가 없고 이는 상태가 아니다. 상태를 관리하는 것을 '상태관리(State Management)'라고 한다.

말은 쉽지만 상태관리는 어렵다. 왜 어려울까? 상태는 실시간으로, 또 비동기적으로 계속해서 변화한다. A에서 B로 변한 상태의 변화를 처리하는 일이 아직 끝나지 않았는데 다시 A로 변하거나 C로 변하는 경우를 생각해보자. 상태가 '언제, 어떻게, 왜' 변화했는지 추적하고 컨트롤하기가 상당히 어려울 것이다. 사용자와의 지속적인 상호작용을 하는 프론트엔드 분야에서는 상태관리가 더욱 중요하기도 하고 어렵기도 하다. 극단적인 경우에는 눈에 보이는 모든 것이 상태인 경우도 있다. 그렇기 때문에 상태를 어떻게 관리할 것인지는 앱 전체의 구조를 결정하는 매우 중요한 요소이다.

상태관리에서 상태가 변경되면 View도 그에 맞게 바꾸어주어야 할 것이다. 그렇다면 View는 상태가 변경된 것은 어떻게 감지해야 할까? 1초마다 상태 값이 변했는지 검사를 하면 충분할까? 0.5초 사이에 두 번 바뀌어버리면 어떻게 할까? 계속 열심히 검사를 했는데 1시간 동안 상태가 한 번도 안바뀌었다면 이대로 1초마다 계속 검사를 하는게 맞을까? 검사 주기를 늘려야 할까?

 

옵저버 패턴으로 문제 해결!

옵저버 패턴(Observer Pattern)을 사용하면 위와 같은 고민을 하지 않아도 된다. 애초에 View는 상태 변화를 알아채려고 노력하지 않아도 된다. 관심있는 상태를 대해 구독(Subscribe)해놨기 때문이다. 관심있는 유튜브 채널을 구독해놓으면 영상 업로드 소식을 바로바로 전해 받을 수 있다. 굳이 유튜브 채널을 수시로 들어가지 않아도, 신규 영상이 업로드 되면 구독자들은 자동으로 알람을 받고 즉시 뜨끈뜨끈한 영상을 확인할 수 있게된다. View도 우리처럼 관심있는 상태를 구독해놓으면 상태가 변화했을 때 즉각적으로 알림을 받아 그에 알맞는 View를 보여줄 수 있는 것이다.

옵저버 패턴에서는 상태와 같이 관심의 대상이 되는 것을 'Subject(대상)' 라고 부른다. 'Subject' 는 관찰자들(Observers)과 1 대 다의 관계를 갖는다. 즉, 'Subject'가 변경되면 이를 관찰하고 있는 '모든' 관찰자들은 각각 자동으로 알림을 받는다(Notify). 

문제를 해결을 위해 적용하는 모든 디자인 패턴이 그렇듯, 옵저버 패턴 또한 언제나 통하는 만병통치약(panacea)은 아니다. 옵저버 패턴은 객체 간의 결합이 너무 강할 경우에 사용하면 좋은 패턴이다. 예를 들어 옵저버 패턴은 객체간의 결합도를 낮춰주기 때문에 MVC 패턴에서 모델과 뷰 사이를 느슨히 연결하기 위해서 사용된다. isGameOver, isPopupShowing 과 같은 모델의 상태 변화를 관찰하는 옵저버를 통해 View의 내용을 바꾸는 방식도 이 경우에 해당한다. 이렇게 옵저버패턴은 클라이언트 측 자바스크립트 프로그래밍에서 널리 사용된다.

다음은 앞서 얘기한 옵저버 패턴을 자바스크립트로 간단하게 구현한 것이다. 

// Subject.js
class Subject {
  constructor(name) {
    this.observers = [];
    this.name = name;
  }

  subscribe(observer) {
    this.observers.push(observer);
  }
  
  unsubscribe(observer) {
    this.observers.splice(this.observers.indexOf(observer), 1);
  }
  
  notifyAll(changeOfState) {
    this.observers.forEach((observer) => observer.notify(changeOfState));
  }
}
// App.js
const observer1 = { notify: (change) => console.log(`Set ${change} Button Clicked`) };
const observer2 = { notify: (change) => console.log(`Increment ${change} Number`) };
const observer3 = { notify: (change) => console.log(`Increment Total ${change} Number`) };

// 관심있는 상태, 대상
const subject = new Subject('Like-Button');

// 미리 구독해 놓는다.
subject.subscribe(observer1);
subject.subscribe(observer2);
subject.subscribe(observer3);

// 상태변화가 생기면 모든 관찰자에게 알리고, 각 관찰자는 통지를 받은 즉시 후속작업을 처리한다.
subject.observer.notifyAll('Like');
// observer1 : Set Like Button Clicked
// observer2 : Increment Like Number
// observer3 : Increment Total Like Number

subject.observer.unsubscribe(observer3);

subject.observer.notifyAll('Like');
// observer1 : Set Like Button Clicked
// observer2 : Increment Like Number

 

Redux를 활용한 상태관리

자바스크립트를 위한 상태관리 패러다임의 변화는 현재 진행형이다. 2010년 이전에는 jQuery, 2012년 이후 Angular.js, 그리고 2015년 이후로는 Redux로 변화해오고 있다. Redux는 간단히 말해 자바스크립트로 만든 애플리케이션들을 위한 '예측가능한' 상태의 저장소이다. 현재의 상태 뿐만 아니라 과거의 상태까지 모두 기록되고 되돌아갈 수 있어 상태를 추적하기 용이하다. 데이터를 중앙집중적으로 관리하는 Redux의 구조에서 화살표가 단방향으로 흐른다는 점에도 주목하며 상태관리가 어떠한 플로우로 이루어지는 살펴보자. 

View에서 '좋아요'를 눌러서 Like를 1 증가시켜, 상태가 변경되는 상황을 상상해보자. 우선 '좋아요 버튼 클릭'이라는 타입의 Action이 dispatch 된다. Dispatcher는 Reducer함수를 호출하면서 현재 State와 Action을 전달한다. Reducer 함수는 현재 State 값과 Action을 참조해서 새로운 State 만들고 이를 반환하는 State 가공자이다. 다른 함수에서는 State를 직접 수정하지 못한다. 이 덕분에 예기치 못한 이유로 데이터가 변경될 일이 없다. Store에는 모든 State가 저장되고, State의 값이 바뀔 때마다 Subscribe하고 있는 모든 View는 자동으로 업데이트될 수 있다. 

 

 

참고자료

Javascript Pattern 요약 - 디자인 패턴
(영상) Observer Pattern - Design Patter (ep 2)
(영상) TECH CONCERT: FRONT END 2019 - 데이터 상태 관리. 그것을 알려주마
자바스크립트 디자인 패턴, 모듈 패턴과 옵저버 패턴
생활코딩: Redux 강의
[번역] 초보 프론트엔드 개발자들을 위한 Pub-Sub(Publish-Subscribe) 패턴을 알아보기