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: 2A 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: 12Egy 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: undefinedA 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 vanAktí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álikEgy 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ólA 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: 2Egy 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: 123A 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.