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.

Аналогично, можно создать поток, зависящий от других потоков, чтобы изменение значения одного из них автоматически обновляло другой. Это полезно, когда у вас есть ресурсоемкие вычисления, и вы хотите запускать их только при необходимости, а не при каждой перерисовке.

Потоки не входят в основной дистрибутив Mithril.js. Чтобы использовать модуль Streams, подключите его:

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

Вы также можете загрузить модуль напрямую, если ваша среда не поддерживает инструменты сборки:

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

При прямой загрузке с помощью тега <script> (а не через require), библиотека потоков будет доступна как window.m.stream. Если window.m уже определен (например, потому что вы также используете основной скрипт Mithril.js), он будет присоединен к существующему объекту. В противном случае будет создан новый window.m. Если вы хотите использовать потоки вместе с Mithril.js через теги <script>, вам следует подключить Mithril.js перед mithril/stream, иначе mithril перезапишет объект window.m, определенный mithril/stream. Это не является проблемой, когда библиотеки используются как модули CommonJS (с использованием require(...)).

Сигнатура ​

Создает поток

stream = Stream(value)

АргументТипОбязательныйОписание
valueanyНетЕсли этот аргумент указан, значение потока устанавливается равным ему
возвращаетStreamВозвращает поток

Как читать сигнатуры

Статические члены ​

Stream.combine ​

Создает вычисляемый поток, который реактивно обновляется при изменении любого из его родительских потоков. См. Объединение потоков

stream = Stream.combine(combiner, streams)

АргументТипОбязательныйОписание
combiner(Stream..., Array) -> anyДаСм. аргумент combiner
streamsArray<Stream>ДаСписок потоков для объединения
возвращаетStreamВозвращает поток

Как читать сигнатуры

combiner ​

Определяет, как генерируется значение вычисляемого потока. См. Объединение потоков

any = combiner(streams..., changed)

АргументТипОбязательныйОписание
streams...splat of StreamsНетНабор из нуля или более потоков, соответствующих потокам, переданным в качестве второго аргумента в stream.combine
changedArray<Stream>ДаСписок потоков, которые были изменены
возвращаетanyВозвращает вычисленное значение

Как читать сигнатуры

Stream.merge ​

Создает поток, значение которого является массивом значений из массива потоков

stream = Stream.merge(streams)

АргументТипОбязательныйОписание
streamsArray<Stream>ДаСписок потоков
возвращаетStreamВозвращает поток, значение которого является массивом значений входных потоков

Как читать сигнатуры

Stream.scan ​

Создает новый поток с результатами вызова функции для каждого значения в потоке, используя аккумулятор и входящее значение.

Обратите внимание, что вы можете предотвратить обновление зависимых потоков, вернув специальное значение stream.SKIP внутри функции аккумулятора.

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

АргументТипОбязательныйОписание
fn(accumulator, value) -> result | SKIPДаФункция, которая принимает параметр аккумулятора и значения и возвращает новое значение аккумулятора того же типа
accumulatoranyДаНачальное значение для аккумулятора
streamStreamДаПоток, содержащий значения
возвращаетStreamВозвращает новый поток, содержащий результат

Как читать сигнатуры

Stream.scanMerge ​

Принимает массив пар потоков и функций сканирования и объединяет все эти потоки с использованием заданных функций в один поток.

stream = Stream.scanMerge(pairs, accumulator)

АргументТипОбязательныйОписание
pairsArray<[Stream, (accumulator, value) -> value]>ДаМассив кортежей потока и функций сканирования
accumulatoranyДаНачальное значение для аккумулятора
возвращаетStreamВозвращает новый поток, содержащий результат

Как читать сигнатуры

Stream.lift ​

Создает вычисляемый поток, который реактивно обновляется при изменении любого из его родительских потоков. См. Объединение потоков. В отличие от combine, входные потоки передаются как переменное число аргументов (а не массив), а функция обратного вызова получает значения потоков, а не сами потоки. Параметр changed отсутствует. Как правило, это более удобная функция для приложений, чем combine.

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

АргументТипОбязательныйОписание
lifter(any...) -> anyДаСм. аргумент lifter
streams...list of StreamsДаПотоки для lift
возвращаетStreamВозвращает поток

Как читать сигнатуры

lifter ​

Определяет, как генерируется значение вычисляемого потока. См. Объединение потоков

any = lifter(streams...)

АргументТипОбязательныйОписание
streams...splat of StreamsНетНабор из нуля или более значений, соответствующих значениям потоков, переданных в stream.lift
возвращаетanyВозвращает вычисленное значение

Как читать сигнатуры

Stream.SKIP ​

Специальное значение, которое можно вернуть в обратных вызовах потока, чтобы пропустить выполнение зависимых потоков

Stream["fantasy-land/of"] ​

Этот метод функционально идентичен stream. Он существует для соответствия спецификации Applicative Fantasy Land. Для получения дополнительной информации см. раздел Что такое Fantasy Land.

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

АргументТипОбязательныйОписание
valueanyНетЕсли этот аргумент указан, значение потока устанавливается равным ему
возвращаетStreamВозвращает поток

Члены экземпляра ​

stream.map ​

Создает зависимый поток, значение которого устанавливается равным результату функции обратного вызова. Этот метод является псевдонимом для stream["fantasy-land/map"].

dependentStream = stream().map(callback)

АргументТипОбязательныйОписание
callbackany -> anyДаОбратный вызов, возвращаемое значение которого становится значением потока
возвращаетStreamВозвращает поток

Как читать сигнатуры

stream.end ​

Связанный поток, который дерегистрирует зависимые потоки при установке значения true. См. Завершенное состояние.

endStream = stream().end

stream["fantasy-land/of"] ​

Этот метод функционально идентичен stream. Он существует для соответствия спецификации Applicative Fantasy Land. Для получения дополнительной информации см. раздел Что такое Fantasy Land.

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

АргументТипОбязательныйОписание
valueanyНетЕсли этот аргумент указан, значение потока устанавливается равным ему
возвращаетStreamВозвращает поток

stream["fantasy-land/map"] ​

Создает зависимый поток, значение которого устанавливается равным результату функции обратного вызова. См. Цепочка потоков

Этот метод существует для соответствия спецификации Applicative Fantasy Land. Для получения дополнительной информации см. раздел Что такое Fantasy Land.

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

АргументТипОбязательныйОписание
callbackany -> anyДаОбратный вызов, возвращаемое значение которого становится значением потока
возвращаетStreamВозвращает поток

Как читать сигнатуры

stream["fantasy-land/ap"] ​

Название этого метода означает apply (применить). Если поток a имеет функцию в качестве своего значения, другой поток b может использовать его в качестве аргумента для b.ap(a). Вызов ap вызовет функцию со значением потока b в качестве аргумента и вернет другой поток, значение которого является результатом вызова функции. Этот метод существует для соответствия спецификации Applicative Fantasy Land. Для получения дополнительной информации см. раздел Что такое Fantasy Land.

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

АргументТипОбязательныйОписание
applyStreamДаПоток, значение которого является функцией
возвращаетStreamВозвращает поток

Основное использование ​

Потоки не являются частью основного дистрибутива Mithril.js. Чтобы включить их в проект, подключите модуль:

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

Потоки как переменные ​

stream() возвращает поток. На базовом уровне поток работает аналогично переменной или свойству с геттером и сеттером: он может хранить состояние, которое можно изменять.

javascript
var username = stream('John');
console.log(username()); // выводит "John"

username('John Doe');
console.log(username()); // выводит "John Doe"

Основное отличие состоит в том, что поток является функцией и, следовательно, может быть скомпонован в функции более высокого порядка.

javascript
var users = stream();

// запросить пользователей с сервера с помощью fetch API
fetch('/api/users')
  .then(function (response) {
    return response.json();
  })
  .then(users);

В приведенном выше примере поток users заполняется данными ответа, когда запрос завершается успешно.

Двунаправленная привязка ​

Потоки также могут быть заполнены данными из функций обратного вызова событий и т.п.

javascript
// поток
var user = stream('');

// двунаправленная привязка к потоку
m('input', {
  oninput: function (e) {
    user(e.target.value);
  },
  value: user(),
});

В приведенном выше примере, когда пользователь вводит данные в поле ввода, значение потока user обновляется значением поля ввода.

Вычисляемые свойства ​

Потоки полезны для реализации вычисляемых свойств:

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.

Конечно, также возможно вычислять свойства на основе нескольких потоков:

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 обновляются атомарно: потоки, зависящие от нескольких потоков, никогда не будут вызываться более одного раза за обновление значения, независимо от сложности графа зависимостей вычисляемого свойства.

Цепочка потоков ​

Потоки можно связывать в цепочки с помощью метода map. Связанный поток также известен как зависимый поток.

javascript
// родительский поток
var value = stream(1);

// зависимый поток
var doubled = value.map(function (value) {
  return value * 2;
});

console.log(doubled()); // выводит 2

Зависимые потоки реактивны: их значения обновляются каждый раз, когда обновляется значение их родительского потока. Это происходит независимо от того, был ли зависимый поток создан до или после установки значения родительского потока.

Вы можете предотвратить обновление зависимых потоков, вернув специальное значение stream.SKIP

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

skipped.map(function () {
  // никогда не будет запущено
});

Объединение потоков ​

Потоки могут зависеть более чем от одного родительского потока. Такие потоки можно создать с помощью 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(), который предоставляет сами потоки в реактивных вычислениях для более продвинутых случаев использования.

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

Поток может зависеть от любого количества потоков, и гарантируется, что он будет обновляться атомарно. Например, если поток A имеет два зависимых потока B и C, а четвертый поток D зависит как от B, так и от C, поток D обновится только один раз при изменении значения A. Это гарантирует, что обратный вызов для потока D никогда не будет вызван с нестабильными значениями, например, когда B имеет новое значение, а C имеет старое значение. Атомарность также обеспечивает преимущества в производительности, заключающиеся в том, что зависимые потоки не пересчитываются без необходимости.

Вы можете предотвратить обновление зависимых потоков, вернув специальное значение stream.SKIP

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

skipped.map(function () {
  // никогда не будет запущено
});

Состояния потока ​

В любой момент времени поток может находиться в одном из трех состояний: в ожидании (pending), активен (active) и завершен (ended).

Состояние ожидания ​

Ожидающие потоки можно создать, вызвав stream() без параметров.

javascript
var pending = stream();

Если поток зависит более чем от одного потока, и какой-либо из его родительских потоков находится в состоянии ожидания, зависимый поток также находится в состоянии ожидания и не обновляет свое значение.

javascript
var a = stream(5);
var b = stream(); // ожидающий поток

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

console.log(added()); // выводит undefined

В приведенном выше примере added является ожидающим потоком, потому что его родительский поток b также находится в состоянии ожидания.

Это также относится к зависимым потокам, созданным с помощью stream.map:

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

console.log(doubled()); // выводит undefined, потому что `doubled` находится в состоянии ожидания

Активное состояние ​

Когда поток получает значение, он становится активным (если поток не завершен).

javascript
var stream1 = stream('hello'); // stream1 активен

var stream2 = stream(); // stream2 начинается в состоянии ожидания
stream2('world'); // затем становится активным

Зависимый поток с несколькими родителями становится активным, если все его родители активны.

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

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

В приведенном выше примере поток a активен, но b находится в состоянии ожидания. Установка b("world") приведет к тому, что b станет активным, и, следовательно, greeting также станет активным и будет обновлен до значения "hello world"

Завершенное состояние ​

Поток может перестать влиять на свои зависимые потоки, вызвав stream.end(true). Это эффективно удаляет связь между потоком и его зависимыми потоками.

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

value.end(true); // установить в завершенное состояние

value(5);

console.log(doubled());
// выводит undefined, потому что `doubled` больше не зависит от `value`

Завершенные потоки по-прежнему имеют семантику контейнера состояния, т.е. вы все еще можете использовать их в качестве геттера и сеттера, даже после их завершения.

javascript
var value = stream(1);
value.end(true); // установить в завершенное состояние

console.log(value(1)); // выводит 1

value(2);
console.log(value()); // выводит 2

Завершение потока может быть полезно в случаях, когда поток имеет ограниченный срок службы (например, реагирование на события mousemove только во время перетаскивания элемента DOM, но не после его отпускания).

Сериализация потоков ​

Потоки реализуют метод .toJSON(). Когда поток передается в качестве аргумента в JSON.stringify(), значение потока сериализуется.

javascript
var value = stream(123);
var serialized = JSON.stringify(value);
console.log(serialized); // выводит 123

Потоки не запускают рендеринг ​

В отличие от таких библиотек, как Knockout, потоки Mithril.js не запускают повторный рендеринг шаблонов. Перерисовка происходит в ответ на обработчики событий, определенные в представлениях компонентов Mithril.js, изменения маршрута или после разрешения вызовов m.request.

Если требуется перерисовка в ответ на другие асинхронные события (например, setTimeout/setInterval, подписка на websocket, обработчик событий сторонней библиотеки и т. д.), вам следует вручную вызвать m.redraw().

Что такое Fantasy Land ​

Fantasy Land определяет совместимость общих алгебраических структур. Проще говоря, это означает, что библиотеки, соответствующие спецификациям Fantasy Land, можно использовать для написания универсального кода в функциональном стиле, независимо от того, как эти библиотеки реализуют конструкции.

Например, скажем, мы хотим создать универсальную функцию под названием plusOne. Наивная реализация будет выглядеть так:

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

Проблема с этой реализацией заключается в том, что ее можно использовать только с числом. Однако возможно, что любая логика, которая создает значение для a, может также создавать состояние ошибки (завернутое в Maybe или Either из такой библиотеки, как Sanctuary или Ramda-Fantasy), или это может быть поток Mithril.js, поток Flyd и т. д. В идеале, мы не хотели бы писать аналогичную версию одной и той же функции для каждого возможного типа, который может иметь a, и мы не хотели бы писать код для обертывания/развертывания/обработки ошибок повторно.

Здесь может помочь Fantasy Land. Давайте перепишем эту функцию с точки зрения алгебры Fantasy Land:

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

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

Теперь этот метод работает с любым Functor, совместимым с Fantasy Land, таким как 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