JunilHwang / blog-comment

TIL comments
1 stars 0 forks source link

TIL/Javascript/Design/Vanilla-JS-Store/ #24

Open utterances-bot opened 2 years ago

utterances-bot commented 2 years ago

Vanilla Javascript로 상태관리 시스템 만들기 | 개발자 황준일

Vanilla Javascript로 Vuex와 Redux 같은 상태관리 시스템을 만드는 방법에 대해 소개합니다.

https://junilhwang.github.io/TIL/Javascript/Design/Vanilla-JS-Store/

elrion018 commented 2 years ago

한글 변수명보니 신기하네요. 토스에서도 그렇게 한다던데?

JunilHwang commented 2 years ago

@elrion018 ㅋㅋ 저도 토스 세미나 영상 보고 처음 알았습니다.. ㅎㅎ

gyofeel commented 2 years ago

좋은 글 감사합니다. 재밌게 읽었습니다~

developersoom commented 2 years ago

안녕하세요, 딱 필요한 부분에 대한 글을 작성해주셔서 리덕스를 공부하는데 도움이 되었습니다! 감사합니다. 실제로 구현을 해보다가 수정하면 좋을 것 같은 부분을 발견했는데요.

//store.js
 setState (newState) {
    for (const [key, value] of Object.entries(newState)) {
      if (!this.state[key]) continue;
      this.state[key] = value;
    }
  }

위 블록에서 !this.state[key] 으로 값이 없는 경우를 처리해주면 값이 0인 경우도 들어가게 됩니다!
저는 카운터를 예시로 만들어보고 있었는데 계속 값이 업데이트가 안되서 찾아보게 되었어요. 값이 없는 경우를 다르게 처리하면 좋을 것 같습니다~ :)

jinhyukoo commented 2 years ago

좋은 글 적어주셔서 감사합니다! 공부하는데 많은 도움 됐습니다!

// state를 직접적으로 수정하지 못하도록 다음과 같이 정의한다.
    Object.keys(state).forEach(key => {
      Object.defineProperty(
        this.state,
        key,
        { get: () => this.#state[key] },
      )
    })

혹시 이 부분이 어떤 원리에 의해 state를 직접적으로 수정하지 못하는건지 여쭤봐도 될까요? 개인적인 생각으로는 property에 접근할 때 private 변수를 반환해줌으로써 state 수정을 막는 것이라 이해했는데 맞게 생각한 건지 확신이 가지 않아서요!

JunilHwang commented 2 years ago

@developersoom 헛.. 0일 경우를 생각을 못했네요 😂 오류 찾아주셔서 감사합니다 😁 빠르게 반영해놓겠습니다!

@jinhyukoo 설명 대신 코드로 대체하겠습니다!

const foo = {};
const bar = 10;
Object.defineProperty(foo, 'bar', { get () { return bar } });
console.log(foo.bar); // 10;
foo.bar = 20;
console.log(foo.bar); // 10;

image

Object.defineProperty에 대해 한 번 쭉 살펴보시면 좋을 것 같아요!

jinhyukoo commented 2 years ago

답변 감사합니다!

jin-Pro commented 2 years ago

let _value = obj[key] Object.defineProperty(obj, key, { get () { if (currentObserver) observers.add(currentObserver); return _value; },

set (value) {
  _value = value;
  observers.forEach(fn => fn());
}

})

부분에서 _value = value로 값을 재할당 해주었는데 왜 obj[key]의 값이 변경이 되는 건지 알 수 있을까요? Object.defineProperty의 속성때문인가요???

jin-Pro commented 2 years ago

질문을 연속으로 남겨드려 죄송합니다!

constructor ({ state, mutations, actions }) { this.#state = observable(state); this.#mutations = mutations; this.#actions = actions;

// state를 직접적으로 수정하지 못하도록 다음과 같이 정의한다.
Object.keys(state).forEach(key => {
    Object.defineProperty(
        this.state,
        key,
        { get: () => this.#state[key] },
    )
})

}

위 코드에서 Object.keys(state).forEach 코드를 다시 한번 사용하시는 이유가 set의 사용을 막으려고 덮어 씌우는거라고 이해하면 될까요?? 맞다면 observable에서 set 관련 로직을 수정해도 되지 않은가 생각이 듭니다. 혹시 다시 코드를 작성해주신 이유가 있을까요??

감사합니다!!

JunilHwang commented 2 years ago

@jin-Pro

_value = value로 값을 재할당 해주었는데 왜 obj[key]의 값이 변경이 되는 건지 알 수 있을까요? Object.defineProperty의 속성때문인가요???

obj[key]로 접근했을 때, 기존의 값이 아닌 _value의 값을 반환하기 때문입니다! 조금 헷갈린다면 차라리 Proxy를 사용해보시는게 좋을 것 같아요 ㅎㅎ

Object.keys(state).forEach 코드를 다시 한번 사용하시는 이유가 set의 사용을 막으려고 덮어 씌우는거라고 이해하면 될까요??

정확히는 get만 하기 위해서라고 보시면 될 것 같습니다! 즉, 변수 접근만 허용하는거죠 ㅎㅎ 아니면 Object.freeze()를 사용해도 된답니다! 이건 observable을 수정한다기보단, flux 패턴을 지키기 위함이라고 생각해주세요! commit이나 dispatch로만 값을 수정하기 위해서입니다!

만약에 #state의 값을 그대로 외부에 노출시키고 또 수정도 가능하게 만든다면 flux 패턴이 무너지게 되기 때문입니다.

yoonminsang commented 2 years ago
const debounceFrame = (callback) => {
  let currentCallback = -1;
  return () => {
    cancelAnimationFrame(currentCallback); // 현재 등록된 callback이 있을 경우 취소한다.
    currentCallback = requestAnimationFrame(callback); // 1프레임 뒤에 실행되도록 한다.
  }
};

debounceFrame(() => console.log(1));
debounceFrame(() => console.log(2));
debounceFrame(() => console.log(3));
debounceFrame(() => console.log(4));
debounceFrame(() => console.log(5)); // 이것만 실행된다.

이 코드에서 로그가 찍히지 않습니다. 이것저것해봤는데 잘 이해가 안되네요 return을 왜하는지모르겠고 debounceFrame(() => console.log(1));여기서 콜백함수에 requestAnimationFrame을 왜 안붙이신지 모르겠습니다. 혹시 설명해주실수있나요??

JunilHwang commented 2 years ago

@yoonminsang image

const debounceFrame = (callback) => {
  let currentCallback = -1;
  return () => {
    cancelAnimationFrame(currentCallback); // 현재 등록된 callback이 있을 경우 취소한다.
    currentCallback = requestAnimationFrame(callback); // 1프레임 뒤에 실행되도록 한다.
  }
};

const debounced = debounceFrame(() => console.log(1));
debounced();
debounced();
debounced();
debounced();
debounced();

일단 작성해주신 코드로는 이렇게 하는게 맞습니다. debounced에 실행하고 싶은 함수를 정의한 다음에, 해당 함수를 여러번 실행했을 때 제일 마지막으로 실행한 함수만 실행되도록 만드는 로직입니다.

debounceFrame이 반환하는 것은 함수입니다. 그래서 return () => {} 처럼 사용했고, return 내부에서 requestAnimationFrame에 등록됨 함수가 있다면, 해당 함수를 취소(cancelAnimationFrame)하고 다시 requestAninmationFrame에 새로운 함수를 등록합니다. 이 과정이 반복을 통해서 마지막에 등록된 함수만 실행하는거죠

이해되지 않는 부분이 있으면 다시 답변 남겨주세요!

skulter commented 2 years ago

안녕하세요. 옵저버 패턴 리팩터링 소스중에 궁금한점이 있어 질문드립니다.

  const state = {
    a: 10,
    b: 20,
  };

  const stateKeys = Object.keys(state);
  const observer = () => console.log(`a + b = ${state.a + state.b}`);

  for (const key of stateKeys) {
    let _value = state[key];

    Object.defineProperty(state, key, {
      get() {
        console.log("첫번째 : ",_value);
        return _value;
      },
      set(value) {
        console.log("두번째 : ",_value);
        _value = value;
        console.log("세번째 : ",_value);
        observer();
      },
    });
  }

  // observer();

  state.a = 100;
  state.a = 200;

위 코드는 테스트하기위해 콘솔과 마지막에 state.b 를 a로 바꿔준 코드입니다.

여기서 궁금증이 생기는데 get, set에 존재하는 _value 이전에 할당해준값을 기억하고있는데 이부분이 이해가 가지 않습니다. for문은 처음 한번만 실행이 되고 메모리에서 해제되고 _value 변수는 사라져야할것같은데 남아있다는것이 이해가 되지않습니다. 어떻게 이해하면 될까요?

skulter commented 2 years ago

한가지 더 질문드립니다. Proxy에서 set 부분에서 observerMap[name] 를 콘솔로 찍어보게되면 set에 함수가 계속 늘어나게되는데 정상으로 동작하는것인지 그렇다면 어떠한 이유로 동작하는것인지 궁금합니다. ex) 0: () => { cancelAnimationFrame(currentCallback); currentCallback = requestAnimationFrame(callback); } 1: () => { cancelAnimationFrame(currentCallback); currentCallback = requestAnimationFrame(callback); } 2: () => { cancelAnimationFrame(currentCallback); currentCallback = requestAnimationFrame(callback); } 3: () => { cancelAnimationFrame(currentCallback); currentCallback = requestAnimationFrame(callback); } 4: () => { cancelAnimationFrame(currentCallback); currentCallback = requestAnimationFrame(callback); } 5: () => { cancelAnimationFrame(currentCallback); currentCallback = requestAnimationFrame(callback); } 6: () => { cancelAnimationFrame(currentCallback); currentCallback = requestAnimationFrame(callback); } 7: () => { cancelAnimationFrame(currentCallback); currentCallback = requestAnimationFrame(callback); } 8: () => { cancelAnimationFrame(currentCallback); currentCallback = requestAnimationFrame(callback); }

JunilHwang commented 2 years ago

@skulter 안녕하세요! 답변이 늦었네요, 죄송합니다 🙇‍♂️

일단 첫 번째 질문의 경우, 클로저와 관련되어 있다고 보시면 되는데 get과 set은 결과적으로 함수입니다. 이 함수들이 외부의 변수(_value)를 내부에서 사용하고 있기 때문에 _value가 해제되지 않고 유지되는 현상입니다.

Proxy에서 set 부분에서 observerMap[name] 를 콘솔로 찍어보게되면 set에 함수가 계속 늘어나게되는데 정상으로 동작하는것인지 그렇다면 어떠한 이유로 동작하는것인지 궁금합니다.

해당 함수들이 Proxy로 감싸준 Object의 Key(name)값이 참조하고 있는 값이 변경될 때 실행되는 함수입니다.

예를들어서

observable({ a: 1, b: 2 });

observer(() => console.log(state.a + state.b)); // a와 b에 `() => console.log(state.a + state.b)` 함수 등록
observer(() => console.log(state.a)); // a에 `() => console.log(state.a)` 함수 등록
observer(() => console.log(state.b)); // b에 `() => console.log(state.b)` 함수 등록
observer(() => console.log(`state.b = ` + state.b)); // b에 `() => console.log(state.b)` 함수 등록

state.a = 10; // 총 3개의 함수 실행
state.b = 20; // 총 4개의 함수 실행

이런 느낌입니다.

mahwin commented 10 months ago

재밌게 읽었습니다. 좋은 글 감사합니다.

044apde commented 4 months ago

바닐라 자바스크립트로 어플리케이션을 만드는 데 도움이 많이 되었습니다. 감사합니다!!

jasongoose commented 6 days ago

observable 객체를 정의할 때 forEach문의 callback 함수에 클로져를 적용하여 개별 obj의 getter/setter에서 이전 값(_value)을 참조하면서 수정하는 로직이 굉장히 인상적입니다👍

글을 읽으면서 설레는 기분을 느낀 건 간만이네요ㅎㅎ