21세기소년, Javascript, 스터디

(Javascript | mithril) mithril 앱의 기본 구성 및 m()

mithril 관련 글

  1. * mithril 앱의 기본 구성 및 m()
  2. m.render – 가상 DOM 렌더링하기
  3. m.mountmithril을 이용한 양방향 바인딩 & 템플릿 렌더링
  4. m.prop 양방향 바인딩을 위한 데이터 래퍼
  5. m.withAttr 양방향 바인딩을 위한 양방향 바인딩을 위한 이벤트 매퍼
  6. m.componentmithril 앱을 컴포넌트 화하기
  7. Todo
  8. m.route – 단일페이지 애플리케이션 및 라우팅 규칙
  9. m.request – 서버 API와 통신하기

mithril로 작성되는 앱은 크게 두 가지 요소로 구성된다.

  1. UI 요소와 양방향으로 바인딩되는 데이터 컨트롤러
  2. 이를 실제로 표현하는 가상 DOM 템플릿 생성함수

따라서 mithril 앱은 거창할 것 없이, controller, view 두 개의 키를 가지는 객체이며, 이 두 키는 각각 다음과 같은 특성을 가진다.

  1. controller : 뷰에서 사용될 데이터를 조작하는 컨트롤러를 생성하는 함수. 객체를 리턴하는 함수이거나, 특정 클래스의 생성자일 수 있다.
  2. view : controller가 리턴하는 객체, 혹은 controller를 통해 생성한 클래스 인스턴스를 인자로 받는 함수로, m() 에 의해 렌더링될 가상 DOM 트리 객체를 리턴하는 함수

가장 간단한 예는 다음과 같이 만들어진다.

app = {}
app.controller = ->
  message: "Hello"
  name: "sooop"
app.view = (ctrl) ->
  m \div.app.greeting,
  * m \h1, ctrl.message
    m \p, "my name is #{ctrl.name}."

m.mount document.body, app
  1. 이 페이지는 간단한 인사말을 출력하는 데모이다.
  2. 컨트롤러는 {message: "Hello", name: "sooo"} 이라는 객체를 리턴하는 간단한 함수이다.
  3. view 는 컨트롤러가 생성해주는 객체를 받고, 그로부터 얻은 정보를 이용하여 화면을 그려준다. m() 이라는 생성자 함수를 이용해서 가상 DOM을 리턴한다.

이 예제에서 app.view가 생성하는 가상 DOM이 body 요소에 마운트된다. 대충 렌더링된 결과는 다음과 같다.

<body>
  <div class="app gretting">
    <h1>Hello</h1>
    <p>my name is sooop.</p>  
  </div>
</body>

이 때 app 객체는 controller, view 키를 갖는 객체로, 앱의 최소 구성단위이며 이를 mithril에서는 컴포넌트라 부른다. 다음에 설명하겠지만 하나의 앱 내에서도 기능 단위별로 컴포넌트를 각각 작성하고, 다른 컴포넌트를 중간에 삽입하는 것도 가능하다.

그렇다면 앱의 구성 요소인 controller, view에 대해서 좀 더 자세하게 알아보자.

controller

컴포넌트의 controller 키는 MVC 모델에서 모델컨트롤러 역할을 하는 부분이다. 디자인하기에 따라서는 모델 객체를 외부에 두고 컨트롤러 역할만 정의할 수 있다. 일단 앞서 살펴본 예제에서는 단순히 모델 객체를 생성해서 뷰에 넘겨주는 일을 하는 함수였다. 이 요소는 임의의 객체를 리턴하는 함수이면 되는데, 리턴되는 객체는 view 함수 내에서 첫번째 인자로 받아 그 내용을 사용할 수 있다.

만약 view 함수가 어떤 데이터를 얻는데 있어서 첫번째 인자인 ctrl에 대한 의존이 없다면 controller는 작성하지 않아도 된다. 보통은 예제처럼 단순한 데이터를 전해주는 것보다는 UI와 관련되는 어떤 값을 내부에 저장하고 사용자의 액션에 반응하여 이 값을 변경해나가는 역할을 수행하는데서 진가가 발휘된다.

또한 단순히 데이터를 만들어서 view에 넘겨주는 것 이외에 특정 클래스의 constructor로서 작성된 컨트롤러를 이용하면 보다 기능이 풍부한 양방향 앱을 만들 수 있다.

위 예제는 아래와 같은 형식으로 다시 쓸 수 있으며, 다음 글들에서 소개할 m.prop, m.withAttr 등의 메소드들과 함께 쉽게 양방향 바인딩을 구현할 수도 있을 것이다.

view

view 함수는 특별히 첫번째 인자를 항상 고정적으로 ctrl로 받는다. (물론 인자의 이름은 마음대로 정해도 된다.) 컨트롤러 함수가 객체를 리턴하는 함수일 때는 controller가 호출된 결과값을 받고, 어떤 클래스의 constructor인 경우에는 새 인스턴스 객체가 넘어온다.

view 함수의 역할은 간단히 가상 DOM 객체를 만들어서 리턴하는 것인데, mithril은 redraw 사이클마다 view를 매번 호출하여 가상 DOM을 전달받는다. 1

m()

m() 함수는 가상 DOM을 만든다고 했다. 이 함수는 대략 다음과 같이 쓰인다.

VirtualDOM m(String HTMLSelector [, Object<any> DOM-Attributes] [, String InnerHTML] [,VirtualDOM | Array<VirtualDOM>)
  1. 첫번째 인자로 HTML 셀렉터를 받는다. 태그 종류나 ID, 클래스 속성등을 지정해줄 수 있다. 대부분의 CSS 셀렉터를 지원한다.
  2. DOM 요소 자체의 속성을 지정할 수 있다. HTML Attribute및 이벤트 핸들러를 이곳에서 지정할 수 있다.
  3. 해당 요소의 텍스트 노드 값을 지정할 수 있다.
  4. 그외에 하위 노드들을 지정할 수 있다. 만약 하위 노드가 여러 개인 경우, 배열의 형태로 지정한다.

따라서 조금이라도 복잡한 모양의 UI를 이 m() 함수로 구성하려고 하면 엄청 헷갈린다. m() 자체가 함수이고, 중간에 각 인자는 배열 리터럴 혹은 객체 리터럴이며, 배열 리터럴 내에서도 다시 m() 을 쓰기 때문에 쉽게 괄호 지옥으로 떨어질 가능성이 있다.

불행중 다행인 것은 우연한 기회에 mithril과 비슷한 시기에 접한 Livescript의 객체리터럴 및 배열 리터럴이 서로 잘 조화될 수 있다는 점이다.

이렇게 작성한 앱 컴포넌트는 m.render() 혹은 m.mount 함수를 이용해서 실제 페이지의 한 요소내에 가상 DOM을 렌더링하게 된다.


  1. 물론 mithril은 내부적으로 캐시를 사용하며, 변경된 부분만 새로 그리는 등의 성능 향상을 위한 내부적인 장치들을 가지고 있다.