v1.x에서 v2.x로의 마이그레이션
v2.x는 v1.x와 거의 완벽하게 API 호환되지만, 몇 가지 호환성에 영향을 주는 변경 사항이 있습니다.
vnode.state
에 할당
v1.x에서는 vnode.state
를 자유롭게 조작하고 값을 할당할 수 있었습니다. 하지만 v2.x에서는 vnode.state
에 직접 할당하려고 하면 오류가 발생합니다. 마이그레이션 방법은 여러 가지가 있지만, 대부분의 경우 vnode.state
에 대한 참조를 vnode.state.foo
로 변경하고, foo
에 적절한 이름(예: 카운터의 현재 값인 count
)을 지정하는 것으로 간단하게 해결할 수 있습니다. v2.x에서는 직접 할당을 시도하면 오류가 발생합니다.
v1.x
var Counter = {
oninit: function (vnode) {
vnode.state = 0;
},
view: function (vnode) {
return m('.counter', [
m(
'button',
{
onclick: function () {
vnode.state--;
},
},
'-'
),
vnode.state,
m(
'button',
{
onclick: function () {
vnode.state++;
},
},
'+'
),
]);
},
};
v2.x
var Counter = {
oninit: function (vnode) {
vnode.state.count = 0;
},
view: function (vnode) {
return m('.counter', [
m(
'button',
{
onclick: function () {
vnode.state.count--;
},
},
'-'
),
vnode.state.count,
m(
'button',
{
onclick: function () {
vnode.state.count++;
},
},
'+'
),
]);
},
};
v1.0이 처음 릴리스되었을 때 클래스 및 클로저 컴포넌트가 존재하지 않았으므로 vnode.tag
에서 필요한 정보를 가져왔습니다. 이러한 구현 방식 덕분에 가능했으며, 일부 문서에서는 이를 암시하기도 했습니다. 이제는 상황이 달라졌으며, 상태에 대한 참조가 두 개가 아닌 하나만 있으므로 구현 관점에서 관리가 더 쉬워졌습니다.
라우트 앵커 변경 사항
v1.x에서는 oncreate: m.route.link
를 사용하여 라우팅 가능한 vnode를 생성하고, 링크가 변경될 수 있는 경우 onupdate: m.route.link
도 사용했습니다. 이들은 각각 라우팅 가능한 vnode의 라이프사이클 훅으로 사용되었습니다. v2.x에서는 이제 m.route.Link
컴포넌트를 사용해야 합니다. m("a", ...)
외의 다른 엘리먼트를 사용하는 경우, selector:
속성을 통해 선택자를 지정할 수 있습니다. 옵션은 options:
로, 비활성화 여부는 disabled:
로 지정할 수 있으며, href:
(필수)를 포함한 다른 속성들은 인라인으로 지정할 수 있습니다. selector:
자체는 m
의 첫 번째 인수로 유효한 모든 선택기를 포함할 수 있으며, [href=...]
및 [disabled]
속성은 일반 옵션뿐만 아니라 선택기에서도 지정할 수 있습니다.
v1.x
m('a', {
href: '/path',
oncreate: m.route.link,
});
m('button', {
href: '/path',
oncreate: m.route.link,
});
m('button.btn[href=/path]', {
oncreate: m.route.link,
});
v2.x
m(m.route.Link, {
href: '/path',
});
m(m.route.Link, {
selector: 'button',
href: '/path',
});
m(m.route.Link, {
selector: 'button.btn[href=/path]',
});
m.request
오류 변경 사항
v1.x에서 m.request
는 JSON 호출에서 오류를 파싱하고, 파싱된 결과 객체의 속성을 응답에 할당했습니다. 예를 들어 HTTP 상태 코드가 403이고 응답 본문이 {"code": "backoff", "timeout": 1000}
인 경우, 오류 객체에 err.code = "backoff"
및 err.timeout = 1000
과 같은 속성이 추가되었습니다.
v2.x에서는 응답이 결과 객체의 response
속성에 할당되고, code
속성에는 HTTP 상태 코드가 포함됩니다. 따라서 HTTP 상태 코드가 403이고 응답 본문이 {"code": "backoff", "timeout": 1000}
인 경우, 오류 객체에는 err.response = {code: "backoff", timeout: 1000}
및 err.code = 403
속성이 할당됩니다.
m.withAttr
제거됨
v1.x에서는 이벤트 리스너를 oninput: m.withAttr("value", func)
와 같이 사용할 수 있었습니다. v2.x에서는 이벤트의 대상에서 직접 값을 읽어야 합니다. 스트림과 함께 사용하면 유용했지만, m.withAttr("value", stream)
형태의 사용 빈도가 m.withAttr("value", prop)
에 비해 현저히 낮았기 때문에 m.withAttr
는 제거되었습니다.
v1.x
var value = '';
// In your view
m('input[type=text]', {
value: value(),
oninput: m.withAttr('value', function (v) {
value = v;
}),
});
// OR
var value = m.stream('');
// In your view
m('input[type=text]', {
value: value(),
oninput: m.withAttr('value', value),
});
v2.x
var value = '';
// In your view
m('input[type=text]', {
value: value,
oninput: function (ev) {
value = ev.target.value;
},
});
// OR
var value = m.stream('');
// In your view
m('input[type=text]', {
value: value(),
oninput: function (ev) {
value(ev.target.value);
},
});
m.route.prefix
v1.x에서 m.route.prefix
는 m.route.prefix(prefix)
를 통해 호출되는 함수였습니다. 이제는 m.route.prefix = prefix
를 통해 설정하는 속성이 되었습니다.
v1.x
m.route.prefix('/root');
v2.x
m.route.prefix = '/root';
m.request
/m.jsonp
params 및 body
data
와 useBody
옵션은 URL에 삽입되어 요청에 추가되는 쿼리 파라미터를 위한 params
와, HTTP 요청 본문에 담아 전송할 데이터를 위한 body
로 변경되었습니다. 이를 통해 실제 전송되는 HTTP 요청을 더욱 세밀하게 제어할 수 있으며, POST
요청 시에도 쿼리 파라미터를 사용할 수 있고, 본문 데이터를 포함하는 GET
요청을 생성하는 것도 가능합니다.
의미 있는 "body"가 없는 m.jsonp
는 params
만 사용하므로 data
를 params
로 이름을 변경하는 것으로 충분합니다.
v1.x
m.request('https://example.com/api/user/:id', {
method: 'GET',
data: { id: user.id },
});
m.request('https://example.com/api/user/create', {
method: 'POST',
data: userData,
});
v2.x
m.request('https://example.com/api/user/:id', {
method: 'GET',
params: { id: user.id },
});
m.request('https://example.com/api/user/create', {
method: 'POST',
body: userData,
});
경로 템플릿
v1.x에는 유사하지만 별도로 설계된 2개의 구문과 3개의 다른 구현이 있는 세 개의 개별 경로 템플릿 구문이 있었습니다. 이들은 상당히 임시적인 방식으로 정의되었고 파라미터는 일반적으로 이스케이프되지 않았습니다. 이제 :key
형태는 자동으로 인코딩되고, :key...
형태는 인코딩되지 않은 원시 값으로 처리됩니다. 예상치 못한 인코딩 문제가 발생하면 :path...
를 사용하면 됩니다. 간단합니다.
구체적으로 각 메서드에 미치는 영향은 다음과 같습니다.
m.request
및 m.jsonp
URL, m.route.set
경로
v2.x에서는 경로 구성 요소가 URL에 삽입될 때 자동으로 이스케이프 처리됩니다. m.route.set("/user/:name/photos/:id", {name: user.name, id: user.id})
를 호출한다고 가정합니다. 이전에는 user
가 {name: "a/b", id: "c/d"}
인 경우 경로가 /user/a%2Fb/photos/c/d
로 설정되었지만 이제는 /user/a%2Fb/photos/c%2Fd
로 설정됩니다. 만약 키를 이스케이프 처리 없이 URL에 삽입하고 싶다면 :key...
를 사용하십시오.
v2.x의 키에는 .
또는 -
문자가 포함될 수 없습니다. v1.x에서는 /
문자를 제외한 모든 문자를 포함할 수 있었습니다.
/api/search?q=:query
와 같은 인라인 쿼리 문자열의 보간은 v2.x에서 지원되지 않습니다. 쿼리 문자열에 직접 지정하는 대신, 적절한 키 이름으로 params
를 통해 전달해야 합니다.
m.route
라우트 패턴
:key...
형식의 경로 키는 v1.x에서 URL 디코딩된 값을 반환했지만, v2.x에서는 원시 URL을 반환합니다.
과거에는 :key.md
와 같은 형태가 잘못 허용되어 keymd: "..."
와 같은 파라미터가 생성되기도 했습니다. 이제는 더 이상 허용되지 않습니다. .md
는 이제 이름이 아닌 패턴의 일부로 취급됩니다.
라이프사이클 호출 순서
v1.x에서는 컴포넌트 vnode에 정의된 속성 라이프사이클 훅이 항상 컴포넌트 자체의 라이프사이클 훅 보다 먼저 호출되었습니다. v2.x에서는 onbeforeupdate
훅에 대해서만 이러한 순서가 유지됩니다. 따라서 코드에 따라 조정이 필요할 수 있습니다.
v1.x
var Comp = {
oncreate: function () {
console.log('Component oncreate');
},
view: function () {
return m('div');
},
};
m.mount(document.body, {
view: function () {
return m(Comp, {
oncreate: function () {
console.log('Attrs oncreate');
},
});
},
});
// Logs:
// Attrs oncreate
// Component oncreate
v2.x
var Comp = {
oncreate: function () {
console.log('Component oncreate');
},
view: function () {
return m('div');
},
};
m.mount(document.body, {
view: function () {
return m(Comp, {
oncreate: function () {
console.log('Attrs oncreate');
},
});
},
});
// Logs:
// Component oncreate
// Attrs oncreate
m.redraw
동기성
v2.x에서 m.redraw()
는 항상 비동기 방식으로 동작합니다. 현재 리드로우가 진행 중이 아닐 때, m.redraw.sync()
를 호출하여 동기적인 리드로우를 명시적으로 요청할 수 있습니다.
선택기 속성 우선 순위
v1.x에서는 선택기 속성이 속성 객체에 지정된 속성보다 우선했습니다. 예를 들어 m("[a=b]", {a: "c"}).attrs
는 {a: "b"}
를 반환했습니다.
v2.x에서는 속성 객체에 지정된 속성이 선택기 속성보다 우선합니다. 예를 들어 m("[a=b]", {a: "c"}).attrs
는 {a: "c"}
를 반환합니다.
기술적으로 이 변경은 v0.2.x의 동작으로 되돌아가는 것입니다.
자식 정규화
v1.x에서는 컴포넌트 vnode의 자식 노드가 다른 vnode와 마찬가지로 정규화되었습니다. v2.x에서는 더 이상 자동 정규화가 이루어지지 않으므로, 이에 맞춰 코드를 조정해야 합니다. 이는 렌더링에서 수행되는 정규화에는 영향을 미치지 않습니다.
m.request
헤더
v1.x에서 Mithril.js는 useBody
가 true
(기본값)로 설정되고 나열된 다른 조건이 충족되는 경우에만 모든 비-GET
요청에 대해 다음 두 헤더를 설정했습니다.
- JSON 본문이 있는 요청에 대한
Content-Type: application/json; charset=utf-8
- JSON 응답을 예상하는 요청에 대한
Accept: application/json, text/*
v2.x에서는 Mithril.js가 JSON 형식의 요청 본문이 존재하고 그 값이 null
이 아닐 경우, 모든 요청에 대해 Content-Type
헤더를 application/json; charset=utf-8
로 설정합니다. 이는 HTTP 메서드와 상관없이 적용됩니다.
두 헤더 중 첫 번째 헤더인 Content-Type
은 지정된 콘텐츠 유형으로 인해 CORS-안전 목록 요청 헤더가 아니므로 CORS 프리플라이트 요청을 트리거하고 서버에서 CORS가 구성된 방식에 따라 새로운 오류가 발생할 수 있습니다. 이로 인해 CORS 관련 문제가 발생한다면, headers: {"Content-Type": "text/plain"}
과 같이 Content-Type
헤더를 직접 설정하여 재정의해야 할 수 있습니다. ( Accept
헤더는 아무것도 트리거하지 않으므로 재정의할 필요가 없습니다.)
Fetch 사양에서 CORS 프리플라이트 검사를 피할 수 있는 유일한 콘텐츠 유형은 application/x-www-form-urlencoded
, multipart/form-data
및 text/plain
입니다. 다른 콘텐츠 유형은 허용하지 않으며, 의도적으로 JSON을 허용하지 않습니다.
라우트의 해시 문자열에 있는 쿼리 파라미터
v1.x에서는 쿼리 문자열과 해시 문자열 모두에서 라우트에 대한 쿼리 파라미터를 지정할 수 있었으므로 m.route.set("/route?foo=1&bar=2")
, m.route.set("/route?foo=1#bar=2")
및 m.route.set("/route#foo=1&bar=2")
는 모두 동일하게 취급되었고, 추출된 속성은 {foo: "1", bar: "2"}
였습니다.
v2.x에서는 해시 문자열의 내용은 무시되지만 보존됩니다. 따라서 각각에서 추출된 속성은 다음과 같습니다.
m.route.set("/route?foo=1&bar=2")
→{foo: "1", bar: "2"}
m.route.set("/route?foo=1#bar=2")
→{foo: "1"}
m.route.set("/route#foo=1&bar=2")
→{}
이렇게 변경된 이유는 https://example.com/#!/route#key
와 같은 URL이 URL 사양에 따라 기술적으로 유효하지 않으며, 이전 RFC에서도 유효하지 않았기 때문입니다. 이는 HTML 사양의 특이한 점일 뿐입니다.
또는 간단히 말해, 유효하지 않은 URL 사용을 지양해야 합니다!
키
v1.x에서는 키가 있는 vnode와 키가 없는 vnode를 자유롭게 혼합할 수 있었습니다. 첫 번째 노드에 키가 있는 경우 키가 있는 diff가 수행되어 모든 요소에 키가 있다고 가정하고 키가 없는 노드를 무시했습니다. 그렇지 않으면 반복적인 diff가 수행되고 노드에 키가 있는 경우 태그 등과 유사한 항목이 확인되는 동시에 변경되지 않았는지 확인했습니다.
v2.x에서는 프래그먼트와 요소의 자식 목록이 모두 키가 있거나 키가 없어야 합니다. 여기서 '키가 없는 노드'는 키가 없는 것으로 취급되며, 더 이상 무시되지 않습니다.
해결해야 하는 경우 [m("div", {key: whatever})]
와 같이 단일 vnode를 포함하는 프래그먼트 패턴을 사용하십시오.
m.version
제거됨
일반적으로 거의 사용되지 않았고 언제든지 직접 다시 추가할 수 있습니다. 사용 가능한 기능을 파악하기 위해 기능 감지(feature detection)를 사용하는 것이 좋으며, v2.x API는 이러한 기능 감지를 더욱 용이하게 하도록 설계되었습니다.