m.withAttr – 이벤트 핸들러 생성함수

[UPDATE] mithril 1.0 이후 m.withAttr()을 사용하는 것이 오히려 번거롭고, 과다하게 사용하는 경우 의도한 대로 동작하는지 알기 어렵다는 문제가 있어 제거된 API이다. 이제는 더 이상 사용되지 않으며, 직접 이벤트 핸들러를 사용하는 것으로 변경되었다.


원글에서는 input 요소의 값을 변경했을 때, 자동으로 다른 컨트롤러 객체의 속성 값을 변경하는 예를 보였는데, 아래의 코드로도 충분했던 것이다.

# LiveScript
state = desc : ''

test = do
  view: (vnode) ->
    m \div,
    * m \input do
        oninput: !-> state.desc = it.target.value.to-lower-case!
      m \div, state.desc

m.withAttr() 함소는 가상노드로 만들어지는 DOM 요소에 이벤트 핸들러를 손쉽게 추가해주는 편의함수이다. 예를 들어 input 필드에 텍스트를 입력할 때 항상 소문자로만 입력을 받고 싶다고 하자. 그러면 oninput 이벤트 핸들러를 통해서 입력된 값을 소문자로 변환해서 어딘가에 저장하고, 이 값을 value 속성을 통해서 표시할 수 있다. 대략 다음과 같은 식으로 구현해볼 수 있을 것이다.

# javascript
let state = {desc: ''}

let test = {
  view: function() { 
    return m('input', {
      oninput: function (e) { state.desc = e.target.value.toLowerCase(); },
      value: state.desc
    });
  }
};

m.mount(document.body, test);

이벤트 핸들러는 이벤트 객체가 넘어가게되고 특정한 필드 값을 참조하기 위해서는 event.target.property 와 같은 식으로 접근해야하기 때문에 조금 번거롭다. m.withAttr() 함수는 이벤트가 발생한 요소의 속성 이름과 핸들러 함수(그 속성값을 받아서 처리하는)를 조합하여 이벤트 핸들러로 등록한다. 따라서 보다 범용적으로 만들어진 프로세스 함수를 간단하게 이벤트 핸들러로 편입할 수 있는 장점이 있다.  만약, m.withAttr() 함수를 사용한다면 위 코드는 아래와 같이 정리될 수 있다.

# JS
let state = {
  desc: '',
  setDesc: function(value){ state.desc = value.toLowerCase(); }
};

let test = {
  view: function() {
    return m('input', {
        oninput: m.withAttr('value', state.setDesc),
        value: state.desc
    });
  }
};

m.mount(document.body, test);

## LS equivalent
state = { desc: '' , set-desc: (v) !-> state.desc = v.to-lower-case! }
test = 
  view: -> m \input do
    oninput: m.with-attr \value state.set-desc
    value: state.desc

m.withAttr()의 장점은 주어진 속성을 읽어들이는 객체가 이벤트가 발생한 객체가 아닌, 이벤트핸들러가 바인드되는 객체라는 점이다. 다음과 같은 예를 살펴보자.

state = {desc: '...!'}
test = 
  view: -> 
    m 'div[href="/foo"]' do
      onclick: (e) !-> state.desc = e.target.href
    , m \span state.desc

m.mount document.body, test

위 앱에서 “…!”를 클릭하면 이는 span 요소를 클릭했기 때문에 e.target 은 span 요소를 가리키며, 이 요소의 href 속성은 undefined가 되기 때문에 텍스트가 사라진다. 이벤트 핸들러는 div 객체에 달려 있고, span 객체에서 발생한 이벤트가 전파되면서 div 객체에서 처리했기 때문에 발생하는 불일치이다. (이를 해결하려면 e.target이 아닌 e.currentTarget을 써야 한다.) m.withAttr()은 항상 e.currentTarget을 쓰기 때문에 이러한 불일치를 사전에 방지할 수 있다.