• 카테고리

    질문 & 답변
  • 세부 분야

    프론트엔드

  • 해결 여부

    해결됨

[실습결과] 이벤트 detail 활용 측면의 확장성과 관련된 질문입니다

21.03.02 17:26 작성 조회수 132

1

안녕하세요 김정환 강사님!
`꿀`보이스이셔서 귀 호강하며 잘 듣고있습니다.
강의를 1년전에 들었다면 수많은 시행착오를 안겪지 않았까 싶을 정도로 너무나 알찬 강의 감사드립니다.

다름이 아니라,
강의에는 없지만, 실습 과제 진행중에 잘 하고 있는건지, 확인을 받아보고 싶은 마음에 질문을 하게 되었습니다.
약간의 고도화? 를 거쳐 확장성을 부여하기 위해 고심끝에 아래와 같이 작성해 보았습니다.
대략적인 설명과 소스 코드의 일부분입니다.
이외는 강의 대용과 동일 합니다


  1. FormView.js

    - `Esc`키를 눌렀을떄 `취소`버튼과 동일하게 작동합니다.
    - 내부 핸들러 onXxx 는 Event 객체와 둘째 인자로 detail 객체를 받아
    View.emit(e, data) 메서드의 `data` 파라미터로 전달 합니다.
    - detail .type | .key 속성은 각각 `Enter` `Esc` 키 와 `Button` 등으로
    이벤트 발생지 Type 구분을 위해 쓰입니다.
    - bindEvent() 에서 전달되는 detail 객체의 .content 속성은
    사용자의 입력된 값을 담습니다.

        1. bindEvent( ) {

            const detail  = { type: null, key: null, content: null }

          this.on('submit', e => e.preventDefault())

          this.inputEl.addEventListener('keyup', (e) => {
          detail.type = 'key'
          detail.key = e.key
          this.onKeyUp(e, detail)
          })

          this.resetEl.addEventListener('click', (e) => {
          detail.type = 'button'
          this.onClickReset(e, detail)
          })
          }
        2. onKeyUp( event , detail ) {

            this.showResetBtn(this.inputEl.value.length)
          const isValidText = this.inputEl.value.length;
          if (!isValidText && event.key !== KEY_ENTER) {
          this.onClickReset(event, detail)
          return
          }

          detail.content = this.inputEl.value.trim()

          switch (event.key) {
          case KEY_ENTER:
          isValidText && this.onSubmit(event,detail)
          break
          case KEY_ESC:
          this.onClickReset(event,detail)
          break
          }
          }
        3. onClickReset(event , detail ) {

            this.showResetBtn(false)
          this.inputEl.value = ''
          this.inputEl.focus()
          detail.content = ''
          this.emit(EVENT_RESET, detail)
          }
        4. onSubmit( event , detail ) {

            this.emit(EVENT_SUBMIT, detail)
          }

  2. MainController.js

    : 넘겨받은 CustomEvent. detail 속성으로 세부 조건 로직 작성이 가능합니다.
      1. export default {
        ...
        ...

        onSubmit(event) {
        const { type, key, content } = event.detail
        console.log(tag, `onSubmit() type=${ type } key=${ key }`, content)
        },

        onResetForm(event) {
        const { type, key, content } = event.detail
        console.log(tag, `onReset() type=${ type } key=${ key }`, content)
        },
        }

이런 식으로 FormView 외부에서 작동할 것을 대비해서
이벤트 디테일 객체를 외부로 전달하는 것이 맞는것인지,  혹은 더나은 방식과,
고쳐야 될 부분 에 대한 , 김정환 강사님의 조언을 구하고자 질문하게 되었습니다.

읽어주셔서 감사합니다!

답변 2

·

답변을 작성해보세요.

1

FormView를 사용하는 MainController에서 좀더 세밀한 제어를 하기위한 의도인것 같네요. Truestar님 아이디어처럼 이벤와 함께 상세정보(detail)를 전달하는 아이디어도 좋은것 같습니다. 두 가지 첨언드릴게요.

1. 단일 detail 객체를 사용해서 각 이벤트 핸들러에서 수정하고 있는 부분이 조금 우려됩니다. keyup 이벤트 핸들러에서는 type과 key를 모두 설정하는 반면 click 핸들러에서는 type만 설정하기 때문에 잘못된 key 가 들어갈 여지가 있어 보여요. 리터럴 객체를 전달하거나 원시 타입을 여러개 전달하는 방식은 어떨까요?

  • this.onKeyup(e, {type: '', key: '', content: ''})
  • this.onKeyup(e, type, key, content)

2. FormView 가 이벤트 정보를 전달하는 것은 외부에서 사용하라는 의도 같은데요. MainController에서 이걸 사용하는 경우가 있을까요? 오히려 상세한 이벤트 동작은 FormView 내부에서 처리해서 감추는 것이 어떨까요?

0

Truestar님의 프로필

Truestar

질문자

2021.03.05

"각 세부 이벤트 핸들러에서 상세정보를 받는것은 좋은 아이디어" 라고 해주셔서 너무 보람찹니다^^
첨언 해주신 부분을 아래와 같이 적용해보았습니다.
아래는 질문은 아니구요, 결과물 정도로 생각해주시면 좋을것 같습니다^^
(질문한 시점 이후로 많은부분을 생각하며 리팩토링을 시도해 보았습니다)

  1. 말씀해주신 "리터럴 객체를 전달하거나 원시 타입을 여러개 전달하는 방식"
    " 이벤트 데이터 사용측( event consumer ) 메서드 파라미터는 데이터묶음 보다는
    개별적으로 명시적인 것이 유지보수에 용이하다" 는 것으로 이해를 했습니다.

    이에 맞춰, 이벤트와 함께 던져지던 `detail = { ... }` 객체를 제거하고,
    정보를 리터럴 형태로 꼭 필요한 Data 가 주입되도록 함수 시그니처를 바꿔보았습니다.
    적용된 코드는 아래와 같습니다.

    호출:
    bindEvent() 메서드 내부,
    this.inputEl.addEventListener('keyup', (e) => this.onKeyUp(e, e.key, 'key'))
    this.resetEl.addEventListener('click', (e) => this.onResetForm(e, null, 'button'))
    선언:
    `keyup` 이벤트는 확정적이라 기본값 'key'를 할당하고, 나머지는 발생지가 바뀔수 있어서,
    파라미터 기본값을 설정하지 않았습니다
    FormView.onKeyUp = function(event, key, from = 'key') {
    ...

    FormView.onReset = function(event, key, from) {
    ...

    FormView.onSubmit = function(event, key, from) {
    ...


  2. 저의 의도였던, FormView 가 외부로 FormEvent 를 전달(emit(...) 하는것
    const tag = '[FormView]'
    ...
    ...

    FormView.onSubmit = function(event, key, from) {

    this.emit(FormEvent.SUBMIT, {...})

    }
    컴포넌트간 결합도를 낮추는 방안으로 작성해 보았습니다.
    (프레임웍만 사용하다 구현해보려니 막히는것과 모르는게 너무 많네요)

    MainController 가 마치 옵저버 패턴의 "옵저버"는 아니지만 흡사하게 나마,
    MainController 는 이벤트 송/수신 처리(딜리버) 역할 만 하고,
    Form 이벤트가 이벤트를 발생시키면, 다른 컴포넌트에서 해당 이벤트에
    반응하도록(반응성을 갖기위함) 작성 해보았지만,
    역부족이라 아래와 같이 작성해 보았습니다.
    (평상시 구상했던 것이지만, 맹점을 몰라서 한번 시도해 보았습니다)


  3. 리팩토링 을 거친 FormView 입니다
    ( 학창시절 리포트 정도로 여겨주시면 너무나 감사할것 같습니다.
    이런 코드는 JS 프로젝트인 경우 실무에서 사용이 가능할까요???ㅜㅜ)

    추가기능 & 리펙토링 대략적인 목록
    - MainContainer 에서 emit 을 하던 로직을 전부 일반 맴버메서드 호출로 바꾸었습니다.
    - 입력값을 나타내는 `input` 을 `content` 에서 `keyword` 로 변경했습니다
    - 3항 연산을 비롯해 각종 비교연산으로 작성되는 상태 값을, `State` 객체로 관리합니다.
    - 입력데이터는 State.keyword 에 실시간으로 반영됩니다
    - State 객체 속성들의 일부는 `input` 이벤트를 통해 실시간으로 변경됩니다.
    - FormEvent .RESET .SUBMIT 은 input 값 변경이 없으면 중복 emit 하지 않습니다.(직전 키워드 비교)

    import View, { ViewEvent } from './View.js'

    /* fields */

    const
    tag = '[FormView]',
    FormView = Object.create(View),//> OLOO 생성패턴 - https://jeonghwan-kim.github.io/2018/05/12/extended-component.html
    State = {
    reset: true,
    isWriting: false,
    keyword: null,//>향후 LocalStorage 값 가져오기
    submitKeyword: null,//>향후 LocalStorage 값 가져오기
    }

    export const
    Key = {
    ENTER: 'Enter',
    ESC: 'Escape',
    }

    /* methods */

    FormView.setup = function(el) {
    console.log(tag, 'setup()', el)
    this.resetEl = el.querySelector('[type=reset]')
    this.inputEl = el.querySelector('[type=text]')
    this.inputEl.value = ''//> 향후 LocalStorage 에서 로딩
    return this
    .init(el)
    .updateState()
    .bindEvent()
    .showResetBtn(false)
    }

    FormView.updateState = function() {
    State.keyword = this.resetEl.value.trim()
    State.submitKeyword = State.keyword
    return this
    };

    FormView.bindEvent = function() {
    this.on('submit', e => e.preventDefault())
    this.inputEl.addEventListener('input', (e) => this.onInputForStateSync(e))//> https://developer.mozilla.org/en-US/docs/Web/API/InputEvent
    this.inputEl.addEventListener('keyup', (e) => this.onKeyUp(e, e.key, 'key'))
    this.resetEl.addEventListener('click', (e) => this.onReset(e, null, 'button'
    ))
    return this
    }

    FormView.showResetBtn = function(show = false) {
    this.resetEl.style.display = show ? 'block' : 'none'
    return this
    }

    /* handler methods */

    // `반응성`개념 적용을 위한 키 입력값으로 상태값 설정
    FormView.onInputForStateSync = function(event) {
    State.keyword = this.inputEl.value.trim()
    State.isWriting = !! State.keyword.length
    this.showResetBtn(State.isWriting)
    }

    FormView.onKeyUp = function(event, key, from = 'key') {
    const isResetBefore = ! State.reset
    if (! State.isWriting && key !== Key.ENTER) {
    return isResetBefore && this.onReset(event, key, from)
    }

    switch (event.key) {
    case Key.ENTER:
    State.isWriting && this.onSubmit(event, key, from)
    return
    case Key.ESC:
    isResetBefore && this.onReset(event, key, from)
    return
    }
    State.reset = false
    }

    // Reset 조건 - reset 이 이미 되어잇는경우 다시 리셋하지 않음.
    FormView.onReset = function(event, key, from) {
    console.log(tag, 'onResetForm()')
    this.showResetBtn(false)
    this.inputEl.value = ''
    this.inputEl.focus()

    State.keyword = ''
    State.reset = true

    ! State.reset && this.emit(ViewEvent.RESET, { key, from })
    }

    // 전송조건 - 빈 문자 이거나, 동일 키워드가 아닌경우 전송됨
    FormView.onSubmit = function(event, key, from) {
    const isEmpty = ! State.keyword.length;
    const isSameKeywordBefore = State.keyword === State.submitKeyword;
    if (isEmpty || isSameKeywordBefore) return
    console.log(tag, 'onSubmit() keyword:', State.keyword)

    this.updateState()

    this.emit(ViewEvent.SUBMIT, {
    key,
    from,
    keyword: State.keyword,
    })
    }

    export default FormView

강사님의 조언으로 많은 도움이 되었습니다
혹시 뭔가 떠오르는 것이 있으시다면, `옛다 이거 하나더 먹어라`는 마음으로
던저주신다면 감사히 경청하겠습니다.

읽어주셔서 감사합니다!

무척 열심히 공부하시는 것 같네요. 이런식으로 고민하시면서 구조를 개선해 나가는 건 좋다고 생각합니다. 실무에서도 '딱 이것이 최선이다'라는것은 찾기 어려운 것 같고요. 자주 코드를 들여다보고 변화에 따라 개선해 나가는 것이 방법이라고 생각합니다.

Truestar님의 프로필

Truestar

질문자

2021.03.11

감사합니다 강사님! 더 열심히 해볼께요^^