m(selector, attributes, children)
설명
Mithril.js 뷰에서 HTML 엘리먼트를 표현합니다.
m('div.foo', { style: { color: 'red' } }, 'hello');
// 다음과 같은 HTML로 렌더링됩니다:
// <div class="foo" style="color: red">hello</div>
Babel을 사용하여 동등한 하이퍼스크립트 함수 호출로 변환할 수 있는 JSX라는 HTML과 유사한 구문도 사용할 수 있습니다. 다음은 위와 동일한 표현입니다.
<div class="foo" style="color: red">
hello
</div>
시그니처
vnode = m(selector, attrs, children)
인수 | 유형 | 필수 | 설명 |
---|---|---|---|
selector | String|Object|Function | 필수 | CSS 선택자 또는 컴포넌트 |
attrs | Object | 선택 | HTML 속성 또는 엘리먼트 프로퍼티 |
children | Array<Vnode>|String|Number|Boolean | 선택 | 자식 vnode. 스프레드 인수 형태로 작성할 수 있습니다. |
반환 값 | Vnode | vnode |
작동 방식
Mithril.js는 JavaScript 구문을 사용하여 모든 HTML 구조를 표현할 수 있는 하이퍼스크립트 함수인 m()
을 제공합니다. selector
문자열(필수), attrs
객체(선택 사항) 및 children
배열(선택 사항)을 인수로 받습니다.
m('div', { id: 'box' }, 'hello');
// 다음과 같은 HTML로 렌더링됩니다:
// <div id="box">hello</div>
m()
함수는 실제 DOM 엘리먼트를 반환하지 않습니다. 대신 생성될 DOM 엘리먼트를 나타내는 JavaScript 객체인 가상 DOM 노드, 즉 vnode를 반환합니다.
// vnode
var vnode = {
tag: 'div',
attrs: { id: 'box' },
children: [
/*...*/
],
};
vnode를 실제 DOM 엘리먼트로 렌더링하려면 m.render()
함수를 사용해야 합니다.
m.render(document.body, m('br')); // <body>에 <br> 엘리먼트를 추가합니다.
m.render()
를 여러 번 호출하더라도 매번 DOM 트리를 처음부터 다시 생성하지는 않습니다. 대신 각 호출은 호출에 전달된 가상 DOM 트리를 반영하는 데 필요한 최소한의 변경만 DOM 트리에 적용합니다. DOM을 처음부터 다시 만드는 것은 매우 비용이 많이 들고 입력 포커스 손실과 같은 문제를 일으키기 때문에 이 동작이 바람직합니다. 반면, 필요한 부분만 DOM을 업데이트하는 것이 훨씬 빠르며, 다양한 사용자 스토리를 다루는 복잡한 UI를 유지 관리하는 데 용이합니다.
유연성
m()
함수는 다형성 과 가변성 을 모두 갖습니다. 즉, 입력 매개변수에 대해 매우 유연하게 동작합니다.
// 간단한 태그
m('div'); // <div></div>
// 속성 및 자식은 선택 사항입니다.
m('a', { id: 'b' }); // <a id="b"></a>
m('span', 'hello'); // <span>hello</span>
// 자식 노드가 있는 태그
m('ul', [
// <ul>
m('li', 'hello'), // <li>hello</li>
m('li', 'world'), // <li>world</li>
]); // </ul>
// 배열은 선택 사항입니다.
m(
'ul', // <ul>
m('li', 'hello'), // <li>hello</li>
m('li', 'world') // <li>world</li>
); // </ul>
CSS 선택자
m()
함수의 첫 번째 인자는 HTML 엘리먼트를 설명하는 CSS 선택자입니다. #
(id), .
(클래스) 및 []
(속성) 구문의 유효한 CSS 조합을 사용할 수 있습니다.
m('div#hello');
// <div id="hello"></div>
m('section.container');
// <section class="container"></section>
m('input[type=text][placeholder=Name]');
// <input type="text" placeholder="Name" />
m("a#exit.external[href='https://example.com']", 'Leave');
// <a id="exit" class="external" href="https://example.com">Leave</a>
태그 이름을 생략하면 Mithril.js는 div
태그를 기본으로 사용합니다.
m('.box.box-bordered'); // <div class="box box-bordered"></div>
일반적으로 정적인 속성(값이 변하지 않는 속성)에는 CSS 선택자를 사용하고, 동적인 속성 값에는 속성 객체를 전달하는 것이 좋습니다.
var currentURL = '/';
m(
'a.link[href=/]',
{
class: currentURL === '/' ? 'selected' : '',
},
'Home'
);
// 다음과 같은 HTML로 렌더링됩니다:
// <a href="/" class="link selected">Home</a>
두 번째 인수로 전달되는 속성
두 번째 선택적 인수로 HTML 속성, 이벤트 핸들러 및 라이프사이클 훅을 전달할 수 있습니다 (자세한 내용은 다음 섹션을 참고하십시오).
m("button", {
class: "my-button",
onclick: function() {/* ... */},
oncreate: function() {/* ... */}
})
이러한 속성의 값이 null
또는 undefined
이면 해당 속성은 설정되지 않습니다.
m()
의 첫 번째 및 두 번째 인수에 모두 클래스 이름이 있는 경우 예상대로 함께 병합됩니다. 두 번째 인수의 클래스 값이 null
또는 undefined
이면 무시됩니다.
다른 속성이 첫 번째 및 두 번째 인수에 모두 있는 경우 두 번째 인수의 속성이 null
또는 undefined
이더라도 우선합니다.
DOM 속성
Mithril.js는 JavaScript API와 DOM API (setAttribute
)를 모두 사용하여 HTML 속성을 처리합니다. 즉, 두 가지 방법 모두로 속성을 참조할 수 있습니다.
예를 들어, JavaScript API에서 readonly
속성은 element.readOnly
로 표현됩니다 (대문자에 유의하십시오). Mithril.js에서는 다음 표현을 모두 지원합니다.
m('input', { readonly: true }); // 소문자
m('input', { readOnly: true }); // 대문자
m('input[readonly]');
m('input[readOnly]');
여기에는 커스텀 엘리먼트도 포함됩니다. 예를 들어 Mithril.js 내에서 A-Frame을 사용할 수 있습니다. 문제 없습니다!
m('a-scene', [
m('a-box', {
position: '-1 0.5 -3',
rotation: '0 45 0',
color: '#4CC3D9',
}),
m('a-sphere', {
position: '0 1.25 -5',
radius: '1.25',
color: '#EF2D5E',
}),
m('a-cylinder', {
position: '1 0.75 -3',
radius: '0.5',
height: '1.5',
color: '#FFC65D',
}),
m('a-plane', {
position: '0 0 -4',
rotation: '-90 0 0',
width: '4',
height: '4',
color: '#7BC8A4',
}),
m('a-sky', {
color: '#ECECEC',
}),
]);
커스텀 엘리먼트의 경우, 속성 값이 객체, 숫자 또는 문자열이 아닌 다른 타입일 때 자동으로 문자열로 변환하지 않습니다. 따라서 elem.whitelist
배열 getter/setter 속성이 있는 사용자 지정 엘리먼트 my-special-element
가 있다고 가정하면 다음과 같이 사용할 수 있으며 예상대로 작동합니다.
m('my-special-element', {
whitelist: [
'https://example.com',
'https://neverssl.com',
'https://google.com',
],
});
해당 엘리먼트에 대한 클래스 또는 ID가 있는 경우 약식 표현도 예상대로 작동합니다. 다른 A-Frame 예제를 가져오면:
// 다음 두 가지는 동일합니다.
m('a-entity#player');
m('a-entity', { id: 'player' });
라이프사이클 속성, onevent
핸들러, key
s, class
및 style
과 같은 특별한 의미를 갖는 모든 속성은 일반 HTML 엘리먼트와 동일한 방식으로 처리됩니다.
스타일 속성
Mithril.js는 문자열과 객체를 모두 유효한 style
값으로 지원합니다. 즉, 다음 표현을 모두 사용할 수 있습니다.
m('div', { style: 'background:red;' });
m('div', { style: { background: 'red' } });
m('div[style=background:red]');
문자열을 style
속성 값으로 사용하면, 값이 변경된 CSS 규칙뿐만 아니라 해당 엘리먼트의 모든 인라인 스타일이 다시 렌더링될 때 덮어씌워집니다.
하이픈으로 연결된 CSS 속성 이름(background-color
와 같은)과 카멜 케이스 DOM style
속성 이름(backgroundColor
와 같은)을 모두 사용할 수 있습니다. 브라우저가 지원하는 경우 CSS 사용자 지정 속성을 정의할 수도 있습니다.
Mithril.js는 숫자 값에 단위를 추가하려고 시도하지 않습니다. 단순히 문자열로 변환합니다.
이벤트
Mithril.js는 사양에서 on${event}
속성을 정의하지 않는 이벤트(예: touchstart
)를 포함하여 모든 DOM 이벤트에 대한 이벤트 핸들러 바인딩을 지원합니다.
function doSomething(e) {
console.log(e);
}
m('div', { onclick: doSomething });
Mithril.js는 함수 및 EventListener 객체를 허용합니다. 따라서 다음 코드도 작동합니다.
var clickListener = {
handleEvent: function (e) {
console.log(e);
},
};
m('div', { onclick: clickListener });
기본적으로 하이퍼스크립트로 연결된 이벤트가 발생하면, 해당 이벤트 콜백 함수가 실행된 후 Mithril.js의 자동 리드로우 기능이 실행됩니다 (직접 m.render
를 사용하는 대신 m.mount
또는 m.route
를 사용하는 경우). e.redraw = false
를 설정하여 특정 이벤트에 대해 자동 리드로우를 비활성화할 수 있습니다.
m('div', {
onclick: function (e) {
// 자동 다시 그리기 방지
e.redraw = false;
},
});
속성
Mithril.js는 <select>
의 selectedIndex
및 value
속성과 같이 속성을 통해 액세스할 수 있는 DOM 기능을 지원합니다.
m('select', { selectedIndex: 0 }, [
m('option', 'Option A'),
m('option', 'Option B'),
]);
컴포넌트
컴포넌트를 사용하면 로직을 하나의 단위로 캡슐화하여 마치 HTML 엘리먼트처럼 사용할 수 있습니다. 이는 크고 확장 가능한 애플리케이션을 만드는 데 필수적입니다.
컴포넌트는 view
메서드를 포함하는 모든 JavaScript 객체입니다. 컴포넌트를 사용하려면 CSS 선택자 문자열을 전달하는 대신 컴포넌트를 m()
함수의 첫 번째 인수로 전달합니다. 아래 예와 같이 속성 및 자식을 정의하여 컴포넌트에 인수를 전달할 수 있습니다.
// 컴포넌트 정의
var Greeter = {
view: function (vnode) {
return m('div', vnode.attrs, ['Hello ', vnode.children]);
},
};
// 사용
m(Greeter, { style: 'color:red;' }, 'world');
// 다음과 같은 HTML로 렌더링됩니다:
// <div style="color:red;">Hello world</div>
컴포넌트에 대한 자세한 내용은 컴포넌트 페이지를 참조하십시오.
라이프사이클 메서드
Vnode와 컴포넌트는 DOM 엘리먼트의 생명 주기 동안 특정 시점에 호출되는 라이프사이클 메서드 (또는 훅)를 가질 수 있습니다. Mithril.js에서 지원하는 라이프사이클 메서드는 oninit
, oncreate
, onupdate
, onbeforeremove
, onremove
및 onbeforeupdate
입니다.
라이프사이클 메서드는 DOM 이벤트 핸들러와 동일한 방식으로 정의되지만 Event 객체 대신 vnode를 인수로 받습니다.
function initialize(vnode) {
console.log(vnode);
}
m('div', { oninit: initialize });
후크 | 설명 |
---|---|
oninit(vnode) | vnode가 실제 DOM 엘리먼트로 렌더링되기 전에 실행됩니다. |
oncreate(vnode) | vnode가 DOM에 추가된 후 실행됩니다. |
onupdate(vnode) | DOM 엘리먼트가 문서에 연결되어 있는 동안 리드로우가 발생할 때마다 실행됩니다. |
onbeforeremove(vnode) | DOM 엘리먼트가 문서에서 제거되기 전에 실행됩니다. Promise가 반환되면 Mithril.js는 Promise가 완료된 후에만 DOM 엘리먼트를 분리합니다. 이 메서드는 부모 DOM 엘리먼트에서 분리된 엘리먼트에서만 트리거되지만 자식 엘리먼트에서는 트리거되지 않습니다. |
onremove(vnode) | DOM 엘리먼트가 문서에서 제거되기 전에 실행됩니다. onbeforeremove 훅이 정의된 경우 onremove 는 done 이 호출된 후에 호출됩니다. 이 메서드는 부모 엘리먼트에서 분리된 엘리먼트와 모든 자식 엘리먼트에서 트리거됩니다. |
onbeforeupdate(vnode, old) | onupdate 전에 실행되며 false 를 반환하면 해당 엘리먼트와 모든 자식 엘리먼트에 대한 diff 연산을 방지합니다. |
라이프사이클 메서드에 대한 자세한 내용은 라이프사이클 메서드 페이지를 참조하십시오.
키
목록의 Vnode는 key
라는 특수 속성을 가질 수 있으며, 이는 vnode 목록을 생성하는 모델 데이터가 변경될 때 DOM 엘리먼트의 ID를 관리하는 데 사용할 수 있습니다.
일반적으로 key
는 데이터 배열에 있는 객체의 고유 식별자 필드여야 합니다.
var users = [
{ id: 1, name: 'John' },
{ id: 2, name: 'Mary' },
];
function userInputs(users) {
return users.map(function (u) {
return m('input', { key: u.id }, u.name);
});
}
m.render(document.body, userInputs(users));
키를 사용하면 users
배열의 순서가 바뀌어 뷰가 다시 렌더링되더라도, 입력 필드들이 정확히 같은 순서로 재배치되어 포커스와 DOM 상태를 올바르게 유지할 수 있습니다.
키에 대한 자세한 내용은 키 페이지를 참조하십시오.
SVG 및 MathML
Mithril.js는 SVG를 완벽하게 지원합니다. Xlink 또한 지원하지만, Mithril.js v1.0 이전 버전과는 달리 네임스페이스를 명시적으로 지정해야 합니다.
m('svg', [m("image[xlink:href='image.gif']")]);
MathML도 완벽하게 지원됩니다.
템플릿 동적으로 만들기
중첩된 vnode는 일반적인 JavaScript 표현식이므로, JavaScript의 기능을 활용하여 조작할 수 있습니다.
동적 텍스트
var user = { name: 'John' };
m('.name', user.name); // <div class="name">John</div>
루프
map
과 같은 Array
메서드를 사용하여 데이터 목록을 반복합니다.
var users = [{ name: 'John' }, { name: 'Mary' }];
m(
'ul',
users.map(function (u) {
// <ul>
return m('li', u.name); // <li>John</li>
// <li>Mary</li>
})
); // </ul>
// ES6+:
// m("ul", users.map(u =>
// m("li", u.name)
// ))
조건
삼항 연산자를 사용하여 뷰에서 조건부로 콘텐츠를 렌더링합니다.
var isError = false;
m('div', isError ? 'An error occurred' : 'Saved'); // <div>Saved</div>
JavaScript 표현식 내에서 if
또는 for
와 같은 JavaScript 문을 사용할 수 없습니다. 이러한 문을 완전히 사용하지 않고 대신 템플릿의 구조를 선형적이고 선언적으로 유지하기 위해 위의 구문만 사용하는 것이 좋습니다.
HTML 변환
Mithril.js에서 올바른 형식의 HTML은 유효한 JSX입니다. 독립적으로 생성된 HTML 파일을 JSX로 프로젝트에 통합할 때는 복사-붙여넣기만으로 충분합니다.
하이퍼스크립트를 사용하는 경우 코드를 실행하기 전에 HTML을 하이퍼스크립트 구문으로 변환해야 합니다. 이를 용이하게 하기 위해 HTML-to-Mithril-template 변환기를 사용할 수 있습니다.
안티 패턴 방지
Mithril.js는 유연하지만 일부 코드 패턴은 권장되지 않습니다.
동적 선택자 방지
서로 다른 DOM 엘리먼트는 서로 다른 속성을 가지고 있으며 종종 다른 동작을 합니다. 선택자를 동적으로 설정하면 컴포넌트의 구현 세부 사항이 외부로 노출될 수 있습니다.
// 피해야 할 패턴
var BadInput = {
view: function (vnode) {
return m('div', [m('label'), m(vnode.attrs.type || 'input')]);
},
};
선택자를 동적으로 만드는 대신 각 유효한 가능성을 명시적으로 코딩하거나 코드의 가변적인 부분을 분리하는 것이 좋습니다.
// 명시적 코드 사용
var BetterInput = {
view: function (vnode) {
return m('div', [m('label', vnode.attrs.title), m('input')]);
},
};
var BetterSelect = {
view: function (vnode) {
return m('div', [m('label', vnode.attrs.title), m('select')]);
},
};
// 가변성 리팩터링 사용
var BetterLabeledComponent = {
view: function (vnode) {
return m('div', [m('label', vnode.attrs.title), vnode.children]);
},
};
뷰 외부에서 vnode 생성 방지
리드로우 과정에서 이전 렌더링 시점의 vnode와 완전히 동일한 vnode를 만나면, 해당 vnode는 건너뛰어지고 내용이 업데이트되지 않습니다. 이는 성능 최적화를 위한 기회처럼 보일 수 있지만 해당 노드 트리의 동적 변경을 방지하므로 피해야 합니다. 이로 인해 다운스트림 라이프사이클 메서드가 다시 그리기를 트리거하지 못하는 것과 같은 부작용이 발생합니다. 이러한 의미에서 Mithril.js vnode는 불변(immutable)입니다. 새 vnode는 이전 vnode와 비교됩니다. vnode에 대한 변경 사항은 유지되지 않습니다.
컴포넌트 설명서에는 이 안티 패턴에 대한 자세한 내용과 예제가 포함되어 있습니다.