從 v1.x 遷移
v2.x 幾乎完全與 v1.x 的 API 相容,但仍存在一些破壞性變更。
賦值給 vnode.state
在 v1.x 中,您可以操作 vnode.state
並賦值為您想要的任何值。在 v2.x 中,如果嘗試直接賦值給 vnode.state
,將會拋出錯誤。遷移方式可能因情況而異,但在大多數情況下,只需將 vnode.state
的引用更改為 vnode.state.foo
,並為 foo
選擇一個適當的名稱(例如,如果它是一個計數器的當前值,則可以命名為 count
)。
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
,如果連結可能會更改,則也使用 onupdate: m.route.link
,每個都作為 vnode 上的生命週期鉤子(hook),可以用於導航。在 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 呼叫的錯誤,並將產生的已解析物件的屬性,賦值到回應物件上。因此,如果您收到狀態為 403 的回應,並且主體為 {"code": "backoff", "timeout": 1000}
,則該錯誤將具有兩個額外的屬性:err.code = "backoff"
和 err.timeout = 1000
。
在 v2.x 中,回應會被賦值給結果的 response
屬性,而 code
屬性包含產生的狀態碼。因此,如果您收到狀態為 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 中,只需直接從事件目標讀取即可。它與流 (stream) 協同工作得很好,但由於 m.withAttr("value", stream)
的慣用法不如 m.withAttr("value", prop)
那麼常見,因此 m.withAttr
失去了大部分用處,因此它被移除了。
v1.x
var value = '';
// 在您的視圖中
m('input[type=text]', {
value: value(),
oninput: m.withAttr('value', function (v) {
value = v;
}),
});
// 或者
var value = m.stream('');
// 在您的視圖中
m('input[type=text]', {
value: value(),
oninput: m.withAttr('value', value),
});
v2.x
var value = '';
// 在您的視圖中
m('input[type=text]', {
value: value,
oninput: function (ev) {
value = ev.target.value;
},
});
// 或者
var value = m.stream('');
// 在您的視圖中
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
參數和主體
data
和 useBody
已重構為 params
(查詢參數會被插入到 URL 中,並附加到請求上)和 body
(要在底層 XHR 中發送的主體)。這使您可以更好地控制發送的實際請求,並允許您使用 POST
請求插入到查詢參數中,並建立帶有主體的 GET
請求。
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 中的路徑元件在插入時會自動轉義。假設您呼叫 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
。如果您故意_想要_插入一個未轉義的鍵,請改用 :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 上的屬性生命週期鉤子(hook),都會在元件自身的生命週期鉤子(hook)之前被呼叫。在 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');
},
});
},
});
// 記錄:
// 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');
},
});
},
});
// 記錄:
// 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 在所有非 GET
請求上設定了這兩個標頭,但僅當 useBody
設定為 true
(預設值)並且列出的其他條件成立時:
- 對於具有 JSON 主體的請求,
Content-Type: application/json; charset=utf-8
- 對於期望 JSON 回應的請求,
Accept: application/json, text/*
在 v2.x 中,Mithril.js 為所有具有 != null
的 JSON 主體的請求設定第一個標頭,否則預設情況下會省略它,並且這是獨立於選擇哪種方法完成的,包括在 GET
請求上。
這兩個標頭中的第一個標頭 Content-Type
將觸發 CORS 預先檢查,因為它不是 CORS 安全列出的請求標頭,因為指定了內容類型,並且這可能會引入新的錯誤,具體取決於 CORS 在您的伺服器上的配置方式。如果您遇到此問題,您可能需要透過傳遞 headers: {"Content-Type": "text/plain"}
來覆寫有問題的標頭。(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 規範的一個怪癖允許它們。(如果 HTML 規範想要遵循規範,它應該從一開始就要求 ID 和位置片段是有效的 URL 片段。)
或者簡而言之,停止使用無效的 URL!
鍵
在 v1.x 中,您可以自由地混合鍵控和未鍵控的 vnode。如果第一個節點是鍵控的,則執行鍵控差異,假設每個元素都有一個鍵,並且在執行時忽略這些空隙。否則,執行迭代差異,如果一個節點有一個鍵,則會檢查它是否在檢查標籤和類似內容的同時發生了更改。
在 v2.x 中,片段和元素的子節點列表必須全部鍵控或全部未鍵控。為了此檢查的目的,孔也被認為是未鍵控的 - 它不再忽略它們。
如果您需要解決它,請使用包含單個 vnode 的片段的慣用法,例如 [m("div", {key: whatever})]
。
m.version
已移除
它通常沒有太多用途,您可以隨時自己將其添加回去。您應該更喜歡使用功能檢測來了解哪些功能可用,並且 v2.x API 旨在更好地啟用此功能。