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 值的展开
changedArray<Stream>是受更新影响的 stream 列表
返回any返回一个计算值。

如何阅读签名

Stream.merge ​

创建一个 stream,其值是由 stream 数组中每个 stream 的值组成的数组。

stream = Stream.merge(streams)

参数类型必需描述
streamsArray<Stream>是stream 列表
返回Stream返回一个 stream,其值是输入 stream 值的数组。

如何阅读签名

Stream.scan ​

创建一个新的 stream,该 stream 的每个值都是对原始 stream 中的每个值调用函数的结果,该函数接收一个累加器和当前值作为参数。

注意,你可以通过在累加器函数中返回特殊值 stream.SKIP 来阻止依赖 stream 被更新。

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

参数类型必需描述
fn(accumulator, value) -> result | SKIP是一个接受累加器和值参数并返回相同类型的新累加器值的函数。
accumulatorany是累加器的起始值。
streamStream是包含值的 Stream。
返回Stream返回一个包含结果的新 stream。

如何阅读签名

Stream.scanMerge ​

接受由 stream 和 scan 函数组成的 pair 数组,并使用给定的函数将所有这些 stream 合并到一个 stream 中。

stream = Stream.scanMerge(pairs, accumulator)

参数类型必需描述
pairsArray<[Stream, (accumulator, value) -> value]>是stream 和 scan 函数的元组数组。
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 的零个或多个值的展开
返回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。请参阅 ended state。

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,其值是回调函数的返回值。请参阅 chaining streams

此方法的存在是为了符合 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 的工作方式类似于变量或具有 getter 和 setter 的属性:它可以保存状态,并且可以修改。

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

username('John Doe');
console.log(username()); // logs "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()); // logs "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()); // logs "John Doe"

firstName('Mary');

console.log(fullName()); // logs "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()); // logs 2

依赖 stream 是反应式的:它们的 value 会在父 stream 的 value 更新时更新。无论依赖 stream 是在父 stream 的 value 设置之前还是之后创建的,都会发生这种情况。

你可以通过返回特殊值 stream.SKIP 来阻止依赖 stream 被更新。

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

skipped.map(function () {
  // 永远不会运行
});

组合 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()); // logs "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()); // logs "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()); // logs 12

一个 stream 可以依赖于任意数量的 stream,并且保证以原子方式更新。例如,如果 stream A 有两个依赖 stream B 和 C,并且第四个 stream D 依赖于 B 和 C,则如果 A 的 value 发生变化,stream D 只会更新一次。这保证了 stream D 的回调永远不会使用不稳定值调用,例如当 B 具有新 value 但 C 具有旧 value 时。原子性还带来了不必要地重新计算下游 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 也处于挂起状态,并且不会更新其 value。

javascript
var a = stream(5);
var b = stream(); // 挂起的 stream

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

console.log(added()); // logs undefined

在上面的示例中,added 是一个挂起的 stream,因为它的父 stream b 也是挂起的。

这也适用于通过 stream.map 创建的依赖 stream:

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

console.log(doubled()); // logs undefined because `doubled` is pending

活动状态 ​

当 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 也会变为活动的,并更新为具有 value "hello world"。

结束状态 ​

可以通过调用 stream.end(true) 来阻止 stream 影响其依赖 stream。这有效地删除了 stream 与其依赖 stream 之间的连接。

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

value.end(true); // 设置为结束状态

value(5);

console.log(doubled());
// logs undefined because `doubled` no longer depends on `value`

已结束的 stream 仍然具有状态容器的特性,也就是说,即使它们已经结束,你仍然可以将它们用作 getter 和 setter。

javascript
var value = stream(1);
value.end(true); // 设置为结束状态

console.log(value(1)); // logs 1

value(2);
console.log(value()); // logs 2

在 stream 具有有限生命周期的情况下,结束 stream 可能很有用(例如,仅在拖动 DOM 元素时才对 mousemove 事件做出反应,但在放下 DOM 元素后则不做出反应)。

序列化 stream ​

Stream 实现了一个 .toJSON() 方法。当 stream 作为参数传递给 JSON.stringify() 函数时,stream 的值会被序列化。

javascript
var value = stream(123);
var serialized = JSON.stringify(value);
console.log(serialized); // logs 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