Skip to content
Mithril.js 2
Main Navigation 指南API

繁體中文

English
简体中文
Español
Français
Русский
Português – Brasil
Deutsch
日本語
한국어
Italiano
Polski
Türkçe
čeština
magyar

繁體中文

English
简体中文
Español
Français
Русский
Português – Brasil
Deutsch
日本語
한국어
Italiano
Polski
Türkçe
čeština
magyar

外觀

Sidebar Navigation

API

核心 API

m(selector, attributes, children)

render(element, vnodes)

mount(root, component)

route(root, defaultRoute, routes)

request(options)

parseQueryString(string)

buildQueryString(object)

buildPathname(object)

parsePathname(string)

trust(html)

fragment(attrs, children)

redraw()

censor(object, extra)

可選 API

stream()

指南

本頁導覽

stream() ​

描述 ​

Stream 是一種響應式資料結構,類似於試算表應用程式中的儲存格。

例如,在試算表中,如果 A1 = B1 + C1,則變更 B1 或 C1 的值會自動變更 A1 的值。

同樣地,您可以讓一個 Stream 依賴於其他 Stream,以便變更一個 Stream 的值會自動更新另一個 Stream。當您有非常耗費資源的計算,並且只想在必要時執行它們,而不是在每次重繪時都執行時,這非常有用。

Stream 沒有與 Mithril.js 的核心發布版本捆綁在一起。要包含 Stream 模組,請使用:

javascript
var Stream = require('mithril/stream');

如果您的環境不支援打包工具鏈,可以直接下載該模組:

html
<script src="https://unpkg.com/mithril/stream/stream.js"></script>

當直接使用 <script> 標籤載入(而不是使用 require)時,Stream 庫會暴露為 window.m.stream。如果 window.m 已經定義(例如,因為您也使用了主要的 Mithril.js 腳本),它將會附加到現有的物件。否則,它會建立一個新的 window.m。如果您想將 Stream 與 Mithril.js 一起作為原始腳本標籤使用,您應該在 mithril/stream 之前於頁面中引入 Mithril.js,因為 mithril 會覆寫由 mithril/stream 定義的 window.m 物件。當函式庫作為 CommonJS 模組使用(使用 require(...))時,這不是問題。

簽名 ​

創建一個 Stream

stream = Stream(value)

參數類型是否必填描述
valueany否如果此參數存在,Stream 的值將設定為該值
返回Stream返回一個 Stream

如何閱讀簽名

靜態成員 ​

Stream.combine ​

創建一個計算 Stream,當其任何上游 Stream 更新時,會響應式地更新。請參閱 組合 Stream

stream = Stream.combine(combiner, streams)

參數類型是否必填描述
combiner(Stream..., Array) -> any是請參閱 combiner 參數
streamsArray<Stream>是要合併的 Stream 列表
返回Stream返回一個 Stream

如何閱讀簽名

combiner ​

指定如何產生計算 Stream 的值。請參閱 組合 Stream

any = combiner(streams..., changed)

參數類型是否必填描述
streams...splat of Streams否對應於作為第二個參數傳遞給 stream.combine 的 Stream 的零個或多個 Stream 的 splat (展開運算子)
changedArray<Stream>是受更新影響的 Stream 列表
返回any返回一個計算值

如何閱讀簽名

Stream.merge ​

創建一個 Stream,其值是來自 Stream 陣列之值的陣列

stream = Stream.merge(streams)

參數類型是否必填描述
streamsArray<Stream>是Stream 列表
返回Stream返回一個 Stream,其值是輸入 Stream 值的陣列

如何閱讀簽名

Stream.scan ​

創建一個新的 Stream,其中包含對 Stream 中每個值呼叫函式的結果,該函式帶有累加器和傳入值。

請注意,您可以通過在累加器函數中返回特殊值 stream.SKIP 來防止相依 Stream 被更新。

stream = Stream.scan(fn, accumulator, stream)

參數類型是否必填描述
fn(accumulator, value) -> result | SKIP是一個接受累加器和值參數並返回相同類型的新累加器值的函式
accumulatorany是累加器的起始值
streamStream是包含值的 Stream
返回Stream返回一個包含結果的新 Stream

如何閱讀簽名

Stream.scanMerge ​

接受 Stream 和掃描函式配對的陣列,並使用給定的函式將所有 Stream 合併到單個 Stream。

stream = Stream.scanMerge(pairs, accumulator)

參數類型是否必填描述
pairsArray<[Stream, (accumulator, value) -> value]>是Stream 和掃描函式的元組陣列
accumulatorany是累加器的起始值
返回Stream返回一個包含結果的新 Stream

如何閱讀簽名

Stream.lift ​

創建一個計算 Stream,當其任何上游 Stream 更新時,會響應式地更新。請參閱 組合 Stream。與 combine 不同,輸入 Stream 是可變數量的參數(而不是陣列),並且回呼接收 Stream 值而不是 Stream。沒有 changed 參數。對於應用程式而言,這通常是比 combine 更容易使用的函式。

stream = Stream.lift(lifter, stream1, stream2, ...)

參數類型是否必填描述
lifter(any...) -> any是請參閱 lifter 參數
streams...list of Streams是要提升的 Stream
返回Stream返回一個 Stream

如何閱讀簽名

lifter ​

指定如何產生計算 Stream 的值。請參閱 組合 Stream

any = lifter(streams...)

參數類型是否必填描述
streams...splat of Streams否對應於傳遞給 stream.lift 的 Stream 值的零個或多個值的 splat (展開運算子)
返回any返回一個計算值

如何閱讀簽名

Stream.SKIP ​

一個特殊值,可以返回給 Stream 回呼以跳過下游 Stream 的執行

Stream["fantasy-land/of"] ​

此方法在功能上與 stream 相同。它的存在是為了符合 Fantasy Land 的 Applicative 規範。有關更多資訊,請參閱 什麼是 Fantasy Land 部分。

stream = Stream["fantasy-land/of"](value)

參數類型是否必填描述
valueany否如果此參數存在,Stream 的值將設定為該值
返回Stream返回一個 Stream

實例成員 ​

stream.map ​

創建一個相依 Stream,其值設定為回呼函數的結果。此方法是 stream["fantasy-land/map"] 的別名。

dependentStream = stream().map(callback)

參數類型是否必填描述
callbackany -> any是一個回呼,其返回值成為 Stream 的值
返回Stream返回一個 Stream

如何閱讀簽名

stream.end ​

一個共同相依的 Stream,當設定為 true 時,會取消註冊相依 Stream。請參閱 已結束狀態。

endStream = stream().end

stream["fantasy-land/of"] ​

此方法在功能上與 stream 相同。它的存在是為了符合 Fantasy Land 的 Applicative 規範。有關更多資訊,請參閱 什麼是 Fantasy Land 部分。

stream = stream()["fantasy-land/of"](value)

參數類型是否必填描述
valueany否如果此參數存在,Stream 的值將設定為該值
返回Stream返回一個 Stream

stream["fantasy-land/map"] ​

創建一個相依 Stream,其值設定為回呼函數的結果。請參閱 串鏈 Stream

此方法的存在是為了符合 Fantasy Land 的 Applicative 規範。有關更多資訊,請參閱 什麼是 Fantasy Land 部分。

dependentStream = stream()["fantasy-land/map"](callback)

參數類型是否必填描述
callbackany -> any是一個回呼,其返回值成為 Stream 的值
返回Stream返回一個 Stream

如何閱讀簽名

stream["fantasy-land/ap"] ​

此方法的名稱代表 apply (應用)。如果 Stream a 具有一個函式作為其值,則另一個 Stream b 可以將其用作 b.ap(a) 的參數。呼叫 ap 將會使用 Stream b 的值作為其參數來呼叫該函式,並且它將會返回另一個 Stream,其值是函式呼叫的結果。此方法的存在是為了符合 Fantasy Land 的 Applicative 規範。有關更多資訊,請參閱 什麼是 Fantasy Land 部分。

stream = stream()["fantasy-land/ap"](apply)

參數類型是否必填描述
applyStream是一個值為函式的 Stream
返回Stream返回一個 Stream

基本用法 ​

Stream 並非 Mithril.js 核心發布版本的一部分。若要將它們包含在專案中,請請求其模組:

javascript
var stream = require('mithril/stream');

Stream 作為變數 ​

stream() 返回一個 Stream。在其最基本的層面上,Stream 的運作方式類似於變數或 getter-setter 屬性:它可以保存狀態,而且該狀態可以被修改。

javascript
var username = stream('John');
console.log(username()); // 記錄 "John"

username('John Doe');
console.log(username()); // 記錄 "John Doe"

主要區別在於 Stream 是一個函式,因此可以組合成高階函式。

javascript
var users = stream();

// 使用 fetch API 從伺服器請求使用者
fetch('/api/users')
  .then(function (response) {
    return response.json();
  })
  .then(users);

在上面的範例中,當請求解析時,users Stream 會填充回應資料。

雙向綁定 ​

Stream 也可以從事件回呼等來填充。

javascript
// 一個 Stream
var user = stream('');

// 到 Stream 的雙向綁定
m('input', {
  oninput: function (e) {
    user(e.target.value);
  },
  value: user(),
});

在上面的範例中,當使用者在輸入中輸入時,user Stream 會更新為輸入欄位的值。

計算屬性 ​

Stream 對於實現計算屬性非常有用:

javascript
var title = stream('');
var slug = title.map(function (value) {
  return value.toLowerCase().replace(/\W/g, '-');
});

title('Hello world');
console.log(slug()); // 記錄 "hello-world"

在上面的範例中,slug 的值是在 title 更新時計算的,而不是在讀取 slug 時計算的。

當然,也可以根據多個 Stream 計算屬性:

javascript
var firstName = stream('John');
var lastName = stream('Doe');
var fullName = stream.merge([firstName, lastName]).map(function (values) {
  return values.join(' ');
});

console.log(fullName()); // 記錄 "John Doe"

firstName('Mary');

console.log(fullName()); // 記錄 "Mary Doe"

Mithril.js 中的計算屬性會以原子性更新:依賴於多個 Stream 的 Stream 對於每個值更新永遠不會被呼叫超過一次,無論計算屬性的依賴圖有多複雜。

串鏈 Stream ​

可以使用 map 方法串鏈 Stream。串鏈的 Stream 也稱為_相依 Stream_。

javascript
// 父 Stream
var value = stream(1);

// 相依 Stream
var doubled = value.map(function (value) {
  return value * 2;
});

console.log(doubled()); // 記錄 2

相依 Stream 是_響應式_的:它們的值會在父 Stream 的值更新時隨時更新。無論相依 Stream 是在父 Stream 的值設定之前還是之後建立的,都會發生這種情況。

您可以通過返回特殊值 stream.SKIP 來防止相依 Stream 被更新

javascript
var skipped = stream(1).map(function (value) {
  return stream.SKIP;
});

skipped.map(function () {
  // 永遠不會執行
});

組合 Stream ​

Stream 可以依賴於多個父 Stream。這些 Stream 可以透過 stream.merge() 建立。

javascript
var a = stream('hello');
var b = stream('world');

var greeting = stream.merge([a, b]).map(function (values) {
  return values.join(' ');
});

console.log(greeting()); // 記錄 "hello world"

或者您可以使用輔助函式 stream.lift()

javascript
var a = stream('hello');
var b = stream('world');

var greeting = stream.lift(
  function (_a, _b) {
    return _a + ' ' + _b;
  },
  a,
  b
);

console.log(greeting()); // 記錄 "hello world"

還有一個稱為 stream.combine() 的較低層級的方法,它在響應式計算中公開 Stream 本身,以用於更進階的使用案例

javascript
var a = stream(5);
var b = stream(7);

var added = stream.combine(
  function (a, b) {
    return a() + b();
  },
  [a, b]
);

console.log(added()); // 記錄 12

一個 Stream 可以依賴於任意數量的 Stream,並且保證以原子性更新。例如,如果 Stream A 有兩個相依 Stream B 和 C,並且第四個 Stream D 依賴於 B 和 C,則如果 A 的值變更,Stream D 將只更新一次。這保證了 Stream D 的回呼永遠不會以不穩定的值呼叫,例如當 B 有一個新值但 C 有舊值時。原子性也帶來了不必要地重新計算下游 Stream 的效能優勢。

您可以通過返回特殊值 stream.SKIP 來防止相依 Stream 被更新

javascript
var skipped = stream.combine(
  function (stream) {
    return stream.SKIP;
  },
  [stream(1)]
);

skipped.map(function () {
  // 永遠不會執行
});

Stream 狀態 ​

在任何給定時間,Stream 可以處於三種狀態之一:pending (暫停)、active (活動) 和 ended (已結束)。

暫停狀態 ​

可以透過呼叫不帶參數的 stream() 來建立暫停 Stream。

javascript
var pending = stream();

如果一個 Stream 依賴於多個 Stream,並且其任何父 Stream 處於暫停狀態,則相依 Stream 也處於暫停狀態,並且不會更新其值。

javascript
var a = stream(5);
var b = stream(); // 暫停 Stream

var added = stream.combine(
  function (a, b) {
    return a() + b();
  },
  [a, b]
);

console.log(added()); // 記錄 undefined

在上面的範例中,added 是一個暫停 Stream,因為其父 Stream b 也是暫停的。

這也適用於透過 stream.map 建立的相依 Stream:

javascript
var value = stream();
var doubled = value.map(function (value) {
  return value * 2;
});

console.log(doubled()); // 記錄 undefined,因為 `doubled` 是暫停的

活動狀態 ​

當 Stream 接收到一個值時,它會變成活動狀態 (除非該 Stream 已結束)。

javascript
var stream1 = stream('hello'); // stream1 是活動的

var stream2 = stream(); // stream2 從暫停開始
stream2('world'); // 然後變成活動的

如果一個具有多個父 Stream 的相依 Stream 的所有父 Stream 都是活動的,則該相依 Stream 變成活動的。

javascript
var a = stream('hello');
var b = stream();

var greeting = stream.merge([a, b]).map(function (values) {
  return values.join(' ');
});

在上面的範例中,a Stream 是活動的,但 b 是暫停的。設定 b("world") 會導致 b 變成活動的,因此 greeting 也會變成活動的,並且會更新為具有值 "hello world"

已結束狀態 ​

Stream 可以透過呼叫 stream.end(true) 來停止影響其相依 Stream。這有效地移除了 Stream 與其相依 Stream 之間的連接。

javascript
var value = stream();
var doubled = value.map(function (value) {
  return value * 2;
});

value.end(true); // 設定為已結束狀態

value(5);

console.log(doubled());
// 記錄 undefined,因為 `doubled` 不再依賴於 `value`

已結束的 Stream 仍然具有狀態容器語義,即,即使在它們結束之後,您仍然可以將它們用作 getter-setter。

javascript
var value = stream(1);
value.end(true); // 設定為已結束狀態

console.log(value(1)); // 記錄 1

value(2);
console.log(value()); // 記錄 2

在 Stream 具有有限生命週期的情況下,結束 Stream 可能很有用(例如,僅在拖曳 DOM 元素時才對 mousemove 事件做出反應,但在放下後則不反應)。

序列化 Stream ​

Stream 實現了一個 .toJSON() 方法。當 Stream 作為參數傳遞給 JSON.stringify() 時,Stream 的值會被序列化。

javascript
var value = stream(123);
var serialized = JSON.stringify(value);
console.log(serialized); // 記錄 123

Stream 不會觸發渲染 ​

與 Knockout 等函式庫不同,Mithril.js Stream 不會觸發模板的重新渲染。重新繪製發生在回應 Mithril.js 元件視圖中定義的事件處理程序、路由變更或在 m.request 呼叫解析之後。

如果希望回應其他非同步事件(例如 setTimeout/setInterval、websocket 訂閱、第三方函式庫事件處理程序等)進行重新繪製,則應手動呼叫 m.redraw()。

什麼是 Fantasy Land ​

Fantasy Land 指定了常見代數結構的互操作性。簡而言之,這意味著符合 Fantasy Land 規範的函式庫可用於編寫通用函數式風格的程式碼,無論這些函式庫如何實現這些結構。

例如,假設我們要建立一個名為 plusOne 的通用函式。基礎的實作看起來像這樣:

javascript
function plusOne(a) {
  return a + 1;
}

此實作的問題在於它只能與數字一起使用。但是,為 a 產生值的任何邏輯也可能產生錯誤狀態 (包裝在來自 Sanctuary 或 Ramda-Fantasy 等函式庫的 Maybe 或 Either 中),或者它可能是 Mithril.js Stream、Flyd Stream 等。理想情況下,我們不希望為 a 可能具有的每種類型編寫相同函式的類似版本,並且我們不希望重複編寫包裝/解包/錯誤處理程式碼。

這就是 Fantasy Land 可以提供幫助的地方。讓我們根據 Fantasy Land 代數重寫該函式:

javascript
var fl = require('fantasy-land');

function plusOne(a) {
  return a[fl.map](function (value) {
    return value + 1;
  });
}

現在,此方法適用於任何符合 Fantasy Land 的 Functor,例如 R.Maybe、S.Either、stream 等。

此範例可能看起來很複雜,但這是複雜性方面的取捨:如果您有一個簡單的系統並且只會遞增數字,那麼基礎的 plusOne 實作是有意義的,但是如果您有一個具有許多包裝器抽象和重用演算法的大型系統,那麼 Fantasy Land 實作會變得更加強大。

在決定是否應該採用 Fantasy Land 時,您應該考慮您的團隊對函數式程式設計的熟悉程度,並且對您的團隊可以承諾維護程式碼品質的自律程度 (相對於編寫新功能和滿足期限的壓力) 持現實態度。函數式風格的程式設計在很大程度上取決於編譯、策劃和掌握大量小的、精確定義的函式,因此它不適合沒有紮實的文件編寫實務和/或缺乏函數式導向語言經驗的團隊。

Pager
上一頁censor(object, extra)
下一頁指南

以 MIT 授權條款 發布。

版權所有 (c) 2024 Mithril Contributors

https://mithril.js.org/stream.html

以 MIT 授權條款 發布。

版權所有 (c) 2024 Mithril Contributors