자바스크립트 상태관리

merci·2023년 11월 5일
0

JavaScript

목록 보기
14/15
post-custom-banner

Observer Pattern

옵저버패턴을 이용한 상태관리를 만들어보도록 하겠습니다.

옵저버패턴을 이용한 상태관리는 기본적으로 여러 컴포넌트가 상태를 관리할 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객체의 각 속성값을 반환하는 메소드를 생성합니다.

Object.keys

객체 내부의 속성들을 열거할 수 있는 배열로 반환합니다.

const object1 = {
  a: 'somestring',
  b: 42,
  c: false,
};

console.log(Object.keys(object1)); // ["a", "b", "c"]

Object.defineProperty

객체 내부의 새로운 속성을 정의하거나 수정하여 반환합니다.
프로퍼티에 아래와 같은 속성을 함께 부여할 수 있습니다.
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}으로 변경됩니다.


클로저를 이용한 여러 Observer 관리

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

profile
작은것부터
post-custom-banner

0개의 댓글