stream()
Leírás
A Stream egy reaktív adatszerkezet, amely a táblázatkezelő alkalmazások celláihoz hasonlóan működik.
Egy táblázatban, ha A1 = B1 + C1
, akkor a B1
vagy C1
értékének megváltoztatása automatikusan megváltoztatja az A1
értékét.
Hasonlóképpen, a streamek függhetnek más streamektől, így az egyik stream értékének megváltoztatása automatikusan frissíti a másik stream értékét. Ez akkor hasznos, ha költséges számításaid vannak, és csak akkor szeretnéd futtatni őket, amikor feltétlenül szükséges, például nem minden újrarajzoláskor.
A streamek nem részei a Mithril.js alapvető terjesztésének. A Streams modul hozzáadásához használd:
var Stream = require('mithril/stream');
A modult közvetlenül is letöltheted, ha a környezeted nem támogatja a csomagkezelő eszközláncot:
<script src="https://unpkg.com/mithril/stream/stream.js"></script>
Ha közvetlenül <script>
taggel töltöd be (nem pedig require
-rel), a stream könyvtár window.m.stream
néven lesz elérhető. Ha a window.m
már definiálva van (pl. mert a fő Mithril.js scriptet is használod), akkor a meglévő objektumhoz fog kapcsolódni. Ellenkező esetben létrehoz egy új window.m
-et. Ha a streameket a Mithril.js-sel együtt szeretnéd használni nyers script tagekként, akkor a Mithril.js-t a mithril/stream
elé kell illesztened az oldalba, mert a mithril
egyébként felülírja a mithril/stream
által definiált window.m
objektumot. Ez nem okoz problémát, ha a könyvtárak CommonJS modulokként vannak felhasználva (require(...)
használatával).
Szignatúra
Stream létrehozása
stream = Stream(value)
Argumentum | Típus | Kötelező | Leírás |
---|---|---|---|
value | any | Nem | Ha meg van adva, a stream értéke erre lesz beállítva. |
visszatér | Stream | Visszaad egy streamet. |
Hogyan kell olvasni a szignatúrákat
Statikus tagok
Stream.combine
Létrehoz egy számított streamet, amely reaktívan frissül, ha bármelyik forrásstreamje frissül. Lásd a streamek kombinálása részt.
stream = Stream.combine(combiner, streams)
Argumentum | Típus | Kötelező | Leírás |
---|---|---|---|
combiner | (Stream..., Array) -> any | Igen | Lásd a combiner argumentum leírását. |
streams | Array<Stream> | Igen | A kombinálandó streamek listája. |
visszatér | Stream | Visszaad egy streamet. |
Hogyan kell olvasni a szignatúrákat
combiner
Meghatározza, hogyan jön létre egy számított stream értéke. Lásd a streamek kombinálása részt.
any = combiner(streams..., changed)
Argumentum | Típus | Kötelező | Leírás |
---|---|---|---|
streams... | Streams felsorolása | Nem | Nulla vagy több stream felsorolása, amelyek a stream.combine második argumentumaként átadott streameknek felelnek meg. |
changed | Array<Stream> | Igen | A frissítés által érintett streamek listája. |
visszatér | any | Visszaad egy számított értéket. |
Hogyan kell olvasni a szignatúrákat
Stream.merge
Létrehoz egy streamet, amelynek értéke egy tömb, ami a bemeneti streamek értékeit tartalmazza.
stream = Stream.merge(streams)
Argumentum | Típus | Kötelező | Leírás |
---|---|---|---|
streams | Array<Stream> | Igen | Streamek listája. |
visszatér | Stream | Visszaad egy streamet, amelynek értéke a bemeneti streamek értékeinek tömbje. |
Hogyan kell olvasni a szignatúrákat
Stream.scan
Létrehoz egy új streamet, amely meghívja a függvényt a stream minden értékére egy akkumulátorral és a bejövő értékkel.
Ne feledd, hogy megakadályozhatod a függő streamek frissítését, ha a stream.SKIP
speciális értéket adod vissza az akkumulátor függvényen belül.
stream = Stream.scan(fn, accumulator, stream)
Argumentum | Típus | Kötelező | Leírás |
---|---|---|---|
fn | (accumulator, value) -> result |SKIP | Igen | Egy függvény, amely egy akkumulátor és egy érték paramétert vesz fel, és egy azonos típusú új akkumulátor értéket ad vissza. |
accumulator | any | Igen | Az akkumulátor kezdőértéke. |
stream | Stream | Igen | A stream, amely az értékeket tartalmazza. |
visszatér | Stream | Visszaad egy új streamet, amely az eredményt tartalmazza. |
Hogyan kell olvasni a szignatúrákat
Stream.scanMerge
Egy streamek és scan függvények párjainak tömbjét veszi fel, és az adott függvényekkel egyesíti az összes streamet egyetlen streammé.
stream = Stream.scanMerge(pairs, accumulator)
Argumentum | Típus | Kötelező | Leírás |
---|---|---|---|
pairs | Array<[Stream, (accumulator, value) -> value]> | Igen | Egy streamek és scan függvények párjainak tömbje. |
accumulator | any | Igen | Az akkumulátor kezdőértéke. |
visszatér | Stream | Visszaad egy új streamet, amely az eredményt tartalmazza. |
Hogyan kell olvasni a szignatúrákat
Stream.lift
Létrehoz egy számított streamet, amely reaktívan frissül, ha bármelyik forrásstreamje frissül. Lásd a streamek kombinálása részt. A combine
-tól eltérően itt a bemeneti streamek argumentumként vannak felsorolva (tömb helyett), és a callback függvény a streamek értékeit kapja meg, nem magukat a streameket. Ez általában könnyebben kezelhető az alkalmazások számára, mint a combine
.
stream = Stream.lift(lifter, stream1, stream2, ...)
Argumentum | Típus | Kötelező | Leírás |
---|---|---|---|
lifter | (any...) -> any | Igen | Lásd a lifter argumentum leírását. |
streams... | Streams listája | Igen | A liftelendő streamek. |
visszatér | Stream | Visszaad egy streamet. |
Hogyan kell olvasni a szignatúrákat
lifter
Meghatározza, hogyan jön létre egy számított stream értéke. Lásd a streamek kombinálása részt.
any = lifter(streams...)
Argumentum | Típus | Kötelező | Leírás |
---|---|---|---|
streams... | Streams felsorolása | Nem | Nulla vagy több érték felsorolása, amelyek a stream.lift számára átadott streamek értékeinek felelnek meg. |
visszatér | any | Visszaad egy számított értéket. |
Hogyan kell olvasni a szignatúrákat
Stream.SKIP
Egy speciális érték, amely visszaadható a stream callbackeknek a downstream-ek végrehajtásának kihagyásához.
Stream["fantasy-land/of"]
Ez a metódus funkcionálisan azonos a stream
-mel. A Fantasy Land Applicative specifikációjának való megfelelés érdekében létezik. További információkért lásd a Mi az a Fantasy Land részt.
stream = Stream["fantasy-land/of"](value)
Argumentum | Típus | Kötelező | Leírás |
---|---|---|---|
value | any | Nem | Ha meg van adva, a stream értéke erre lesz beállítva. |
visszatér | Stream | Visszaad egy streamet. |
Példány tagok
stream.map
Létrehoz egy függő streamet, amelynek értéke a callback függvény eredményére van állítva. Ez a metódus a stream["fantasy-land/map"] aliasa.
dependentStream = stream().map(callback)
Argumentum | Típus | Kötelező | Leírás |
---|---|---|---|
callback | any -> any | Igen | Egy callback, amelynek visszatérési értéke a stream értéke lesz. |
visszatér | Stream | Visszaad egy streamet. |
Hogyan kell olvasni a szignatúrákat
stream.end
Egy ko-függő stream, amely megszünteti a függő streamek regisztrációját, ha igaz értékre van állítva. Lásd a lezárt állapot részt.
endStream = stream().end
stream["fantasy-land/of"]
Ez a metódus funkcionálisan azonos a stream
-mel. A Fantasy Land Applicative specifikációjának való megfelelés érdekében létezik. További információkért lásd a Mi az a Fantasy Land részt.
stream = stream()["fantasy-land/of"](value)
Argumentum | Típus | Kötelező | Leírás |
---|---|---|---|
value | any | Nem | Ha meg van adva, a stream értéke erre lesz beállítva. |
visszatér | Stream | Visszaad egy streamet. |
stream["fantasy-land/map"]
Létrehoz egy függő streamet, amelynek értéke a callback függvény eredményére van állítva. Lásd a streamek láncolása részt.
Ez a metódus a Fantasy Land Applicative specifikációjának való megfelelés érdekében létezik. További információkért lásd a Mi az a Fantasy Land részt.
dependentStream = stream()["fantasy-land/map"](callback)
Argumentum | Típus | Kötelező | Leírás |
---|---|---|---|
callback | any -> any | Igen | Egy callback, amelynek visszatérési értéke a stream értéke lesz. |
visszatér | Stream | Visszaad egy streamet. |
Hogyan kell olvasni a szignatúrákat
stream["fantasy-land/ap"]
Ennek a metódusnak a neve apply
-t jelent. Ha egy a
streamnek van egy függvénye értékként, akkor egy másik b
stream használhatja azt a b.ap(a)
argumentumaként. Az ap
meghívása meghívja a függvényt a b
stream értékével argumentumként, és egy másik streamet ad vissza, amelynek értéke a függvényhívás eredménye. Ez a metódus a Fantasy Land Applicative specifikációjának való megfelelés érdekében létezik. További információkért lásd a Mi az a Fantasy Land részt.
stream = stream()["fantasy-land/ap"](apply)
Argumentum | Típus | Kötelező | Leírás |
---|---|---|---|
apply | Stream | Igen | Egy stream, amelynek értéke egy függvény. |
visszatér | Stream | Visszaad egy streamet. |
Alapvető használat
A streamek nem részei a Mithril.js alapvető terjesztésének. Ahhoz, hogy egy projektben használd őket, importáld a modult:
var stream = require('mithril/stream');
Streamek változókként
A stream()
egy streamet ad vissza. A legalapvetőbb szinten egy stream hasonlóan működik, mint egy változó vagy egy getter-setter tulajdonság: állapotot tárolhat, amely módosítható.
var username = stream('John');
console.log(username()); // a konzolra kerül: "John"
username('John Doe');
console.log(username()); // a konzolra kerül: "John Doe"
A fő különbség az, hogy egy stream egy függvény, és ezért magasabb rendű függvényekbe komponálható.
var users = stream();
// felhasználók lekérése egy szerverről a fetch API használatával
fetch('/api/users')
.then(function (response) {
return response.json();
})
.then(users);
A fenti példában a users
stream a válaszadatokkal van feltöltve, amikor a kérés teljesül.
Bidirekcionális kötések
A streamek esemény callbackekből és hasonlókból is feltölthetők.
// a stream
var user = stream('');
// egy kétirányú kötés a streamhez
m('input', {
oninput: function (e) {
user(e.target.value);
},
value: user(),
});
A fenti példában, amikor a felhasználó beírja az input mezőbe, a user
stream frissül az input mezőjének értékére.
Számított tulajdonságok
A streamek hasznosak a számított tulajdonságok megvalósításához:
var title = stream('');
var slug = title.map(function (value) {
return value.toLowerCase().replace(/\W/g, '-');
});
title('Hello world');
console.log(slug()); // a konzolra kerül: "hello-world"
A fenti példában a slug
értéke akkor van kiszámítva, amikor a title
frissül, nem pedig akkor, amikor a slug
lekérdezésre kerül.
Természetesen az is lehetséges, hogy a tulajdonságokat több stream alapján számítsuk ki:
var firstName = stream('John');
var lastName = stream('Doe');
var fullName = stream.merge([firstName, lastName]).map(function (values) {
return values.join(' ');
});
console.log(fullName()); // a konzolra kerül: "John Doe"
firstName('Mary');
console.log(fullName()); // a konzolra kerül: "Mary Doe"
A Mithril.js-ben a számított tulajdonságok atomikusan frissülnek: a több streamtől függő streamek soha nem lesznek többször meghívva értékfrissítésenként, függetlenül attól, hogy a számított tulajdonság függőségi gráfja mennyire összetett.
Streamek láncolása
A streamek a map
metódussal láncolhatók. A láncolt stream más néven függő stream.
// szülő stream
var value = stream(1);
// függő stream
var doubled = value.map(function (value) {
return value * 2;
});
console.log(doubled()); // a konzolra kerül: 2
A függő streamek reaktívak: az értékeik frissülnek, amikor a szülő streamje értéke frissül. Ez attól függetlenül történik, hogy a függő stream a szülő stream értékének beállítása előtt vagy után jött létre.
Megakadályozhatod a függő streamek frissítését, ha a stream.SKIP
speciális értéket adod vissza.
var skipped = stream(1).map(function (value) {
return stream.SKIP;
});
skipped.map(function () {
// soha nem fut le
});
Streamek kombinálása
A streamek több szülő streamtől is függhetnek. Ezek a streamek a stream.merge()
segítségével hozhatók létre.
var a = stream('hello');
var b = stream('world');
var greeting = stream.merge([a, b]).map(function (values) {
return values.join(' ');
});
console.log(greeting()); // a konzolra kerül: "hello world"
Vagy használhatod a stream.lift()
segédfüggvényt.
var a = stream('hello');
var b = stream('world');
var greeting = stream.lift(
function (_a, _b) {
return _a + ' ' + _b;
},
a,
b
);
console.log(greeting()); // a konzolra kerül: "hello world"
A stream.combine()
egy alacsonyabb szintű metódus, amely a reaktív számítások során közvetlen hozzáférést biztosít a streamekhez, ami komplexebb felhasználási módokat tesz lehetővé.
var a = stream(5);
var b = stream(7);
var added = stream.combine(
function (a, b) {
return a() + b();
},
[a, b]
);
console.log(added()); // a konzolra kerül: 12
Egy stream bármennyi streamtől függhet, és garantáltan atomikusan frissül. Például, ha egy A streamnek van két függő streamje, B és C, és egy negyedik D stream függ mind B-től, mind C-től, akkor a D stream csak egyszer frissül, ha az A értéke megváltozik. Ez garantálja, hogy a D stream callbackje soha nem lesz meghívva inkonzisztens értékekkel, például amikor B-nek van új értéke, de C-nek a régi értéke van. Az atomiság a teljesítmény előnyeit is magával hozza, mivel nem számolja újra a downstream-eket szükségtelenül.
Megakadályozhatod a függő streamek frissítését, ha a stream.SKIP
speciális értéket adod vissza.
var skipped = stream.combine(
function (stream) {
return stream.SKIP;
},
[stream(1)]
);
skipped.map(function () {
// soha nem fut le
});
Stream állapotok
Egy stream bármely adott időpontban a következő három állapot egyikében lehet: függőben, aktív vagy lezárt.
Függőben állapot
A függőben lévő streamek a stream()
meghívásával hozhatók létre paraméterek nélkül.
var pending = stream();
Ha egy stream több streamtől függ, és bármelyik szülő streamje függőben van, akkor a függő stream is függőben van, és nem frissíti az értékét.
var a = stream(5);
var b = stream(); // függőben lévő stream
var added = stream.combine(
function (a, b) {
return a() + b();
},
[a, b]
);
console.log(added()); // a konzolra kerül: undefined
A fenti példában az added
egy függőben lévő stream, mivel a b
is függőben van.
Ez a stream.map
segítségével létrehozott függő streamekre is vonatkozik:
var value = stream();
var doubled = value.map(function (value) {
return value * 2;
});
console.log(doubled()); // a konzolra kerül: undefined, mert a `doubled` függőben van
Aktív állapot
Amikor egy stream értéket kap, aktívvá válik (hacsak a stream nincs lezárva).
var stream1 = stream('hello'); // stream1 aktív
var stream2 = stream(); // stream2 függőben indul
stream2('world'); // majd aktívvá válik
Egy több szülővel rendelkező függő stream akkor válik aktívvá, ha az összes szülője aktív.
var a = stream('hello');
var b = stream();
var greeting = stream.merge([a, b]).map(function (values) {
return values.join(' ');
});
A fenti példában az a
stream aktív, de a b
függőben van. A b("world")
beállítása azt eredményezné, hogy a b
aktívvá válna, és ezért a greeting
is aktívvá válna, és frissülne a "hello world"
értékre.
Lezárt állapot
Egy stream leállíthatja a függő streamjeinek befolyásolását a stream().end(true)
meghívásával. Ez hatékonyan eltávolítja a kapcsolatot egy stream és a függő streamjei között.
var value = stream();
var doubled = value.map(function (value) {
return value * 2;
});
value.end(true); // lezárt állapotba állítva
value(5);
console.log(doubled());
// a konzolra kerül: undefined, mert a `doubled` már nem függ a `value`-tól
A lezárt streamek továbbra is rendelkeznek állapotkezelő szemantikával, azaz továbbra is használhatod őket getter-setterként, még a lezárásuk után is.
var value = stream(1);
value.end(true); // lezárt állapotba állítva
console.log(value(1)); // a konzolra kerül: 1
value(2);
console.log(value()); // a konzolra kerül: 2
Egy stream lezárása hasznos lehet olyan esetekben, amikor egy stream korlátozott élettartammal rendelkezik (például a mousemove
eseményekre reagál csak addig, amíg egy DOM elem húzva van, de utána nem).
Streamek szerializálása
A streamek megvalósítják a .toJSON()
metódust. Amikor egy stream argumentumként van átadva a JSON.stringify()
-nak, a stream értéke szerializálva van.
var value = stream(123);
var serialized = JSON.stringify(value);
console.log(serialized); // a konzolra kerül: 123
A streamek nem indítják el a renderelést
A Knockout-hoz hasonló könyvtáraktól eltérően a Mithril.js streamek nem indítják el a sablonok újrarajzolását. Az újrarajzolás a Mithril.js komponens nézeteiben definiált eseménykezelőkre, útvonalváltozásokra vagy a m.request
hívások feloldása után történik.
Ha az újrarajzolás más aszinkron eseményekre (pl. setTimeout
/setInterval
, websocket előfizetés, harmadik féltől származó könyvtár eseménykezelője stb.) válaszul kívánatos, akkor manuálisan kell meghívnod a m.redraw()
függvényt.
Mi az a Fantasy Land
A Fantasy Land a közös algebrai struktúrák együttműködési képességét (interoperabilitását) specifikálja. Egyszerűen fogalmazva ez azt jelenti, hogy a Fantasy Land specifikációknak megfelelő könyvtárak felhasználhatók olyan általános funkcionális stílusú kód írására, amely a könyvtárak konstrukcióinak megvalósításától függetlenül működik.
Például tegyük fel, hogy létre akarunk hozni egy plusOne
nevű általános függvényt. A naiv implementáció így nézne ki:
function plusOne(a) {
return a + 1;
}
Ennek az implementációnak az a problémája, hogy csak számmal használható. Azonban lehetséges, hogy bármilyen logika is állítja elő az a
értékét, az hibát is eredményezhet (egy Maybe-be vagy egy Either-be csomagolva egy olyan könyvtárból, mint a Sanctuary vagy a Ramda-Fantasy), vagy lehet egy Mithril.js stream, egy Flyd stream stb. Ideális esetben nem szeretnénk minden lehetséges típushoz külön verziót írni ugyanabból a függvényből, és nem szeretnénk ismétlődő csomagolási/kicsomagolási/hibakezelési kódot írni.
Itt segíthet a Fantasy Land. Írjuk át ezt a függvényt egy Fantasy Land algebra szempontjából:
var fl = require('fantasy-land');
function plusOne(a) {
return a[fl.map](function (value) {
return value + 1;
});
}
Most ez a metódus bármely Fantasy Land kompatibilis Functor-ral működik, mint például a R.Maybe
, a S.Either
, a stream
stb.
Ez a példa bonyolultnak tűnhet, de ez egy kompromisszum a bonyolultságban: a naiv plusOne
implementáció akkor ésszerű, ha egyszerű rendszered van, és csak számokat növelsz, de a Fantasy Land implementáció erősebbé válik, ha nagy rendszered van sok csomagoló absztrakcióval és újrahasznosított algoritmussal.
Amikor eldöntöd, hogy be kell-e vezetned a Fantasy Land-et, figyelembe kell venned a csapatod funkcionális programozással kapcsolatos ismereteit, és reálisnak kell lenned a csapatod által a kódminőség fenntartására vállalt fegyelem szintjével kapcsolatban (szemben az új funkciók írásának és a határidők betartásának nyomásával). A funkcionális stílusú programozás nagymértékben függ a kis, pontosan definiált függvények nagy halmazának létrehozásától, karbantartásától és elsajátításától, ezért nem alkalmas olyan csapatok számára, amelyek nem rendelkeznek szilárd dokumentációs gyakorlatokkal, és/vagy hiányzik a tapasztalatuk a funkcionális orientált nyelvekben.