私は今2週間RxJSを実験していますが、原則としてそれを愛していますが、状態を管理するための正しいパターンを見つけて実装できないようです。すべての記事と質問に同意するように見えます。簡単なRxJSの例題で、件名や必須の操作を使わずに状態を管理する方法は?
Subject
可能な場合は、変換を経由して状態をプッシュすることをお勧めします。.getValue()
は完全に非推奨です。.do
DOM操作を除いて、おそらく避けるべきですか?
このようなすべての提案の問題は、「あなたはRxの方法を学び、Subjectを使うのをやめる」以外に、あなたが何を使うべきかを直接指摘するような文献はないということです。
しかし、ステートレスで機能的な方法で、複数のストリーム入力の結果として、単一のストリーム/オブジェクトへの追加と削除の両方を実行する正しい方法を具体的に示すどこにも直接の例は見つかりません。私は再び同じ方向に指摘を受ける前に
は、覆われていない文献の問題点は以下のとおりです。
- はじめは、あなたが行方不明になってきたプログラミングに反応する:偉大な出発テキストを、が、特にこれらの質問には対応していません。
- RxJSのTODOの例にはReactが付属しており、React Storesのプロキシとして
Subject
を明示的に操作する必要があります。 - http://blog.edanschwartz.com/2015/09/18/dead-simple-rxjs-todo-list/:アイテムの追加と削除に明示的に
state
オブジェクトを使用します。
標準TODOのマイおそらく第十リライトは、次の - カバー私の前の反復は、次のとおりです。
- 変更可能な「アイテムの配列で始まる - 状態は、明示的および命令的
scan
を使用して管理され、悪いとして新しいアイテムをaddedItems$
ストリームに連結し、削除されたアイテムが削除された別のストリームを分岐します。addedItems$
ストリームが無期限に増加するため、悪いです。BehaviorSubject
を発見し、それを使用しています。新しくupdatedList$.next()
の発光のたびに、これまでの値を反復する必要があります。つまり、Subject.getValue()
が必須です。inputEnter$
追加イベントの結果をフィルタリングされた削除イベントにストリーミングしようとすると、新しいストリームがすべて新しいリストを作成し、toggleItem$
ストリームとtoggleAll$
ストリームに供給すると、新しいストリームはそれぞれ以前のものに依存します。 4つのアクションの1つ(アイテムの追加、削除、トグル、またはトグル)のいずれかが発生すると、チェーン全体が不必要に再実行される必要があります。今
私は戻って、以下のショーとして、Subject
(そしてどれだけそれが連続してgetValue()
を使用せずに、どのような方法で時に反復されることになっている?)とdo
の両方を使用することにしています完全な円を、来ています。自分自身と私の同僚は、これが最も明白な方法だと考えていますが、もちろんそれは最も反応が少なく、最も不可欠です。このための正しい方法に関する明確な提案は非常に高く評価されます。
import Rx from 'rxjs/Rx';
import h from 'virtual-dom/h';
import diff from 'virtual-dom/diff';
import patch from 'virtual-dom/patch';
const todoListContainer = document.querySelector('#todo-items-container');
const newTodoInput = document.querySelector('#new-todo');
const todoMain = document.querySelector('#main');
const todoFooter = document.querySelector('#footer');
const inputToggleAll = document.querySelector('#toggle-all');
const ENTER_KEY = 13;
// INTENTS
const inputEnter$ = Rx.Observable.fromEvent(newTodoInput, 'keyup')
.filter(event => event.keyCode === ENTER_KEY)
.map(event => event.target.value)
.filter(value => value.trim().length)
.map(value => {
return { label: value, completed: false };
});
const inputItemClick$ = Rx.Observable.fromEvent(todoListContainer, 'click');
const inputToggleAll$ = Rx.Observable.fromEvent(inputToggleAll, 'click')
.map(event => event.target.checked);
const inputToggleItem$ = inputItemClick$
.filter(event => event.target.classList.contains('toggle'))
.map((event) => {
return {
label: event.target.nextElementSibling.innerText.trim(),
completed: event.target.checked,
};
})
const inputDoubleClick$ = Rx.Observable.fromEvent(todoListContainer, 'dblclick')
.filter(event => event.target.tagName === 'LABEL')
.do((event) => {
event.target.parentElement.classList.toggle('editing');
})
.map(event => event.target.innerText.trim());
const inputClickDelete$ = inputItemClick$
.filter(event => event.target.classList.contains('destroy'))
.map((event) => {
return { label: event.target.previousElementSibling.innerText.trim(), completed: false };
});
const list$ = new Rx.BehaviorSubject([]);
// MODEL/OPERATIONS
const addItem$ = inputEnter$
.do((item) => {
inputToggleAll.checked = false;
list$.next(list$.getValue().concat(item));
});
const removeItem$ = inputClickDelete$
.do((removeItem) => {
list$.next(list$.getValue().filter(item => item.label !== removeItem.label));
});
const toggleAll$ = inputToggleAll$
.do((allComplete) => {
list$.next(toggleAllComplete(list$.getValue(), allComplete));
});
function toggleAllComplete(arr, allComplete) {
inputToggleAll.checked = allComplete;
return arr.map((item) =>
({ label: item.label, completed: allComplete }));
}
const toggleItem$ = inputToggleItem$
.do((toggleItem) => {
let allComplete = toggleItem.completed;
let noneComplete = !toggleItem.completed;
const list = list$.getValue().map(item => {
if (item.label === toggleItem.label) {
item.completed = toggleItem.completed;
}
if (allComplete && !item.completed) {
allComplete = false;
}
if (noneComplete && item.completed) {
noneComplete = false;
}
return item;
});
if (allComplete) {
list$.next(toggleAllComplete(list, true));
return;
}
if (noneComplete) {
list$.next(toggleAllComplete(list, false));
return;
}
list$.next(list);
});
// subscribe to all the events that cause the proxy list$ subject array to be updated
Rx.Observable.merge(addItem$, removeItem$, toggleAll$, toggleItem$).subscribe();
list$.subscribe((list) => {
// DOM side-effects based on list size
todoFooter.style.visibility = todoMain.style.visibility =
(list.length) ? 'visible' : 'hidden';
newTodoInput.value = '';
});
// RENDERING
const tree$ = list$
.map(newList => renderList(newList));
const patches$ = tree$
.bufferCount(2, 1)
.map(([oldTree, newTree]) => diff(oldTree, newTree));
const todoList$ = patches$.startWith(document.querySelector('#todo-list'))
.scan((rootNode, patches) => patch(rootNode, patches));
todoList$.subscribe();
function renderList(arr, allComplete) {
return h('ul#todo-list', arr.map(val =>
h('li', {
className: (val.completed) ? 'completed' : null,
}, [h('input', {
className: 'toggle',
type: 'checkbox',
checked: val.completed,
}), h('label', val.label),
h('button', { className: 'destroy' }),
])));
}
編集
@ user3743222非常に有用な答えに関連して、私は追加の入力としての状態を表現することは関数が純粋な作ることができますどのように見ることができますので、scan
は時間をかけて進化してコレクションを表現するための最良の方法ですその時点までの以前の状態のスナップショットを追加の関数パラメータとして保持します。私は入力のスキャン流れているaddedItems$
で、私の第二の試みに近づく方法
しかし、これはすでにだった:
// this list will now grow infinitely, because nothing is ever removed from it at the same time as concatenation?
const listWithItemsAdded$ = inputEnter$
.startWith([])
.scan((list, addItem) => list.concat(addItem));
const listWithItemsAddedAndRemoved$ = inputClickDelete$.withLatestFrom(listWithItemsAdded$)
.scan((list, removeItem) => list.filter(item => item !== removeItem));
// Now I have to always work from the previous list, to get the incorporated amendments...
const listWithItemsAddedAndRemovedAndToggled$ = inputToggleItem$.withLatestFrom(listWithItemsAddedAndRemoved$)
.map((item, list) => {
if (item.checked === true) {
//etc
}
})
// ... and have the event triggering a bunch of previous inputs it may have nothing to do with.
// and so if I have 400 inputs it appears at this stage to still run all the previous functions every time -any- input
// changes, even if I just want to change one small part of state
const n$ = nminus1$.scan...
明白な解決策は、またはconst items = new BehaviorSubject([])
だけitems = []
を持っており、それを直接操作することです - しかし、それを反復する唯一の方法は、getValue
を使用して前の状態を公開しているように見えます。Andre Stalz(CycleJS)はRxJSの問題に、実際には公開すべきではないものとしてコメントしていますどのように使えますか?)。
私はちょうどストリームでは、あなたは主題を使ったり、何かを州のミートボールで表現したりしないという考えを持っていたと思います。最初の答えでは、孤立している/無限に成長している/互いに正確な順序で構築されなければならない連鎖したストリーム。
これは本当に役に立ちますが、それでも2つの特定の問題に関してはまだありません...私の元の質問を改訂するだけです。あなたの更新に関する – brokenalarms
、あなたの実装は正常です。実装したときに 'Current_Todos = Accumulated_Added_Todos - Removed_Todos'を持つことができますが、' Current_Todos'は有限であるが、Accumulated_Added_Todosは無限に大きくなる可能性があります。最も効率的な方法は、実際には、典型的なCRUD操作の1つで、Todos_n + 1 = Operations_n + 1(Todos_n)と書くことです。この場合、g(In + 1、Sn)= In + 1(Sn)となる。しかし、どちらの実装もステートレスです。 – user3743222
アイデアを頭で囲むのに時間がかかるかもしれませんが、 'scan'の入力ストリームは現在の状態に適用して更新された状態を取得する関数(操作)のストリームです。 – user3743222