옵저버패턴을 이용한 상태관리를 만들어보도록 하겠습니다.
옵저버패턴을 이용한 상태관리는 기본적으로 여러 컴포넌트가 상태를 관리할 store를 구독하고 store에서 상태를 갱신한 뒤 통지함으로써 컴포넌트들의 상태가 변경됩니다.
class Pub{
construtor(state){
this.observers = new Set();
this.state = state;
Object.keys(state).forEach(key => Object.defineProperty(this, key, {
get : () => this.state[key]
}));
}
setState(newState){
this.state = { ...this.state, ...newState};
this.notifyAll();
}
register(subscriber){
this.observers.add(subscriber);
}
notifyAll(){
this.observers.forEach(fn => fn());
}
}
get : () => this.state[key]
로 인해 state
객체의 각 속성값을 반환하는 메소드를 생성합니다.
객체 내부의 속성들을 열거할 수 있는 배열로 반환합니다.
const object1 = {
a: 'somestring',
b: 42,
c: false,
};
console.log(Object.keys(object1)); // ["a", "b", "c"]
객체 내부의 새로운 속성을 정의하거나 수정하여 반환합니다.
프로퍼티에 아래와 같은 속성을 함께 부여할 수 있습니다.
writable: false
: 읽기전용
enumerable: false
: 열거불가
configurable: false
: 삭제 및 변경불가
const object1 = {};
Object.defineProperty(object1, 'property1', {
value: 42,
writable: false,
});
object1.property1 = 77; // 읽기전용 -> 변경불가
console.log(object1.property1); // 44
class Sub{
constructor(fn){
this.fn = fn; // 생성될때 주입되는 프로퍼티
}
subscribe(Pub){
Pub.register(this.fn);
}
}
const store = new Pub({
a : 10,
b : 20,
}) // a, b 속성을 가진 state를 가진 Pub 객체
const addCal = new Sub(() => console.log(store.a + store.b)); // console함수가 주입되면서 생성
const minusCal = new Sub(() => console.log(store.a - store.b));
addCal.subscribe(store);
minusCal.subscribe(store); // observers에 console 등록
store.notifyAll(); // observers 순회 -> 30, -10
store.setState({ a : 100, b : 20}); // 120, 80
마지막 라인인 setState
는 스프레드 연산자를 통해 배열을 추가하게 됩니다.
스프레드 연산자는 동일한 이름의 속성이 있을경우 덮어쓰게 되고 다른 이름의 속성이 올 경우 추가하게 되므로
Pub의 store는 {a:10, b:20}
에서 {a:100, b:20}
으로 변경됩니다.
let currentObserver = null; // 활성화된 옵저버
const state = {
a : 10,
b : 20,
};
const stateKeys = Object.keys(state);
for( const key of stateKeys ) {
// 클로저를 통해 접근가능한 private 변수
let _value = state[key];
const observers = new Set();
// 모든 key에 getter, setter를 정의
Object.defineProperty(state, key, {
get(){
if ( currentObserver ) observers.add(currentObserver);
return _value;
},
set(value){
_value = value;
observers.forEach(observer => observer());
}
})
}
const addCal = () => {
currentObserver = addCal;
console.log(`a+b = ${state.a + state.b}`);
}
const minusCal = () => {
currentObserver = minusCal;
console.log(`a - b = ${state.a - state.b}`);
}
addCal(); // a+b = 30, 옵저버에 addCal() 추가
state.a = 100;
// 1. 클로저를 통해 _value를 변경
// 2. 등록된 옵저버 실행 (addCal)
// 3. a+b = 120 출력
minusCal(); // a-b = 80, 옵저버에 minusCal() 추가
state.b = 200;
// 1. 클로저를 통해 _value를 변경
// 2. 등록된 옵저버 실행 (addCal, minusCal)
// 3. a+b = 300, a-b = -100 출력
state.a = 1;
// a+b = 201, a-b = -199
state.b = 2;
// a+b = 3, a-b = -1
여기서 _value
는 for문의 지역변수입니다.
하지만 내부함수인 get()
, set()
에 의해서 상태가 캡처되므로 내부함수인 클로저를 이용해서 변수를 캡슐화하여 사용할 수 있습니다.
또한 이러한 클로저를 이용함으로서 추가 로직을 get()
, set()
에 정의할 수 있습니다.
여기서 추가로직은 옵저버가 될수 있으므로 이를 통해 간단한 리액리브 프로그래밍이 구현됩니다.
let currentObserver = null;
const observe = fn => {
currentObserver = fn;
fn();
currentObserver = null;
}
const observable = obj => {
Object.keys(obj).forEach(key => {
let _value = obj[key]
const observers = new Set();
Object.defineProperty(obj,key,{
get(){
if(currentObserver) observers.add(currentObserver);
return _value;
},
set(value){
_value = value;
observers.forEach(fn => fn());
}
})
})
return obj;
}
const store = observable({a : 10, b : 20});
observe(() => console.log(`a = ${store.a}`)); // a = 10
observe(() => console.log(`b = ${store.b}`)); // b = 20
observe(() => console.log(`a + b = ${store.a + store.b}`)); // a + b = 30
observe(() => console.log(`a - b = ${store.a - store.b}`)); // a - b = -10
![](https://velog.velcdn.com/images/merci/post/dc76c6e5-d417-4820-ad54-7dad09ce75fb/image.jpg)
store.a = 100; // a = 100, a + b = 120, a - b = 80
store.b = 200; // b = 200, a + b = 300, a - b = -100
참고 https://jin-pro.tistory.com/55
나중에 공부해볼까 ..? https://velog.io/@ljinsk3/JavaScript-%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC