アプリケーションの通知状態を更新するアクションがあります。通常、この通知はエラーまたは何らかの情報です。その後、通知状態を最初の状態に戻す5秒後に別のアクションをディスパッチする必要があるため、通知はありません。これの主な理由は、通知が5秒後に自動的に消える機能を提供することです。
私はsetTimeout
別のアクションを使用したり返したりすることができず、オンラインでこれを行う方法を見つけることができません。ですから、アドバイスは大歓迎です。
アプリケーションの通知状態を更新するアクションがあります。通常、この通知はエラーまたは何らかの情報です。その後、通知状態を最初の状態に戻す5秒後に別のアクションをディスパッチする必要があるため、通知はありません。これの主な理由は、通知が5秒後に自動的に消える機能を提供することです。
私はsetTimeout
別のアクションを使用したり返したりすることができず、オンラインでこれを行う方法を見つけることができません。ですから、アドバイスは大歓迎です。
回答:
図書館がすべてを行う方法を規定すべきだと考える罠に陥らないでください。JavaScriptでタイムアウトを使って何かを実行したい場合は、を使用する必要がありますsetTimeout
。Reduxのアクションが異なる理由はありません。
Reduxの人はする非同期のものを処理するいくつかの代替方法を提供しますが、コードを繰り返しすぎていることに気付いた場合にのみそれらを使用してください。この問題がない限り、言語が提供するものを使用して、最も簡単な解決策を探してください。
これは、最も簡単な方法です。そして、ここではReduxに固有のものはありません。
store.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' })
setTimeout(() => {
store.dispatch({ type: 'HIDE_NOTIFICATION' })
}, 5000)
同様に、接続されたコンポーネントの内部から:
this.props.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' })
setTimeout(() => {
this.props.dispatch({ type: 'HIDE_NOTIFICATION' })
}, 5000)
唯一の違いは、接続されたコンポーネントでは通常、ストア自体にはアクセスできないが、いずれかdispatch()
または特定のアクション作成者が小道具として挿入されることです。しかし、これは私たちにとって何の違いもありません。
異なるコンポーネントから同じアクションをディスパッチするときにタイプミスをしたくない場合は、アクションオブジェクトをインラインでディスパッチするのではなく、アクションの作成者を抽出できます。
// actions.js
export function showNotification(text) {
return { type: 'SHOW_NOTIFICATION', text }
}
export function hideNotification() {
return { type: 'HIDE_NOTIFICATION' }
}
// component.js
import { showNotification, hideNotification } from '../actions'
this.props.dispatch(showNotification('You just logged in.'))
setTimeout(() => {
this.props.dispatch(hideNotification())
}, 5000)
または、以前に次のようにバインドしている場合connect()
:
this.props.showNotification('You just logged in.')
setTimeout(() => {
this.props.hideNotification()
}, 5000)
これまでのところ、ミドルウェアやその他の高度な概念は使用していません。
上記のアプローチは単純なケースではうまく機能しますが、いくつかの問題があることに気付くでしょう:
HIDE_NOTIFICATION
、タイムアウト後よりも早く2番目の通知が誤って非表示になります。これらの問題を解決するには、タイムアウトロジックを集中化し、これら2つのアクションをディスパッチする関数を抽出する必要があります。次のようになります。
// actions.js
function showNotification(id, text) {
return { type: 'SHOW_NOTIFICATION', id, text }
}
function hideNotification(id) {
return { type: 'HIDE_NOTIFICATION', id }
}
let nextNotificationId = 0
export function showNotificationWithTimeout(dispatch, text) {
// Assigning IDs to notifications lets reducer ignore HIDE_NOTIFICATION
// for the notification that is not currently visible.
// Alternatively, we could store the timeout ID and call
// clearTimeout(), but we’d still want to do it in a single place.
const id = nextNotificationId++
dispatch(showNotification(id, text))
setTimeout(() => {
dispatch(hideNotification(id))
}, 5000)
}
コンポーネントは、showNotificationWithTimeout
このロジックを複製したり、さまざまな通知で競合状態を発生させたりすることなく使用できるようになりました。
// component.js
showNotificationWithTimeout(this.props.dispatch, 'You just logged in.')
// otherComponent.js
showNotificationWithTimeout(this.props.dispatch, 'You just logged out.')
なぜ最初の引数としてshowNotificationWithTimeout()
受け入れるのdispatch
ですか?ストアにアクションをディスパッチする必要があるためです。通常、コンポーネントはアクセス権を持っていますdispatch
が、外部関数がディスパッチを制御できるようにしたいので、ディスパッチを制御できるようにする必要があります。
あるモジュールからシングルトンストアをエクスポートしdispatch
た場合、代わりにそれを直接インポートすることができます。
// store.js
export default createStore(reducer)
// actions.js
import store from './store'
// ...
let nextNotificationId = 0
export function showNotificationWithTimeout(text) {
const id = nextNotificationId++
store.dispatch(showNotification(id, text))
setTimeout(() => {
store.dispatch(hideNotification(id))
}, 5000)
}
// component.js
showNotificationWithTimeout('You just logged in.')
// otherComponent.js
showNotificationWithTimeout('You just logged out.')
これは単純に見えますが、このアプローチはお勧めしません。私たちがそれを嫌う主な理由は、ストアをシングルトンにすることを強制するためです。これにより、サーバーレンダリングの実装が非常に困難になります。。サーバーでは、各リクエストに独自のストアを設定して、異なるユーザーが異なるプリロードデータを取得できるようにします。
シングルトンストアもテストを難しくします。特定のモジュールからエクスポートされた特定の実際のストアを参照するため、アクションクリエーターをテストするときにストアを模擬できなくなりました。外部から状態をリセットすることもできません。
したがって、技術的にはモジュールからシングルトンストアをエクスポートできますが、お勧めしません。アプリがサーバーレンダリングを追加しないことが確実でない限り、これを行わないでください。
以前のバージョンに戻す:
// actions.js
// ...
let nextNotificationId = 0
export function showNotificationWithTimeout(dispatch, text) {
const id = nextNotificationId++
dispatch(showNotification(id, text))
setTimeout(() => {
dispatch(hideNotification(id))
}, 5000)
}
// component.js
showNotificationWithTimeout(this.props.dispatch, 'You just logged in.')
// otherComponent.js
showNotificationWithTimeout(this.props.dispatch, 'You just logged out.')
これにより、ロジックの重複に関する問題が解決され、競合状態から解放されます。
単純なアプリの場合は、このアプローチで十分です。満足できればミドルウェアについて心配する必要はありません。
ただし、大規模なアプリでは、その周りに不便を感じる場合があります。
たとえば、私たちはdispatch
周りを回らなければならないのは残念です。これにより、上記の方法で非同期にReduxアクションをディスパッチするコンポーネントは、それをさらに渡すことができるようにプロップとして受け入れる必要があるため、コンテナコンポーネントとプレゼンテーションコンポーネントを分離するのが難しくなりますdispatch
。は実際にはアクションクリエーターではないconnect()
ため、アクションクリエーターをバインドすることshowNotificationWithTimeout()
はできません。Reduxアクションは返しません。
さらに、どの関数が同期アクションの作成者であり、どの関数showNotification()
が非同期のヘルパーであるかを覚えておくのは面倒ですshowNotificationWithTimeout()
。それらを異なる方法で使用し、それらを互いに間違えないように注意する必要があります。
これが、ヘルパー関数へのこの提供パターンを「正当化」dispatch
し、Reduxが完全に異なる関数ではなく、通常のアクションクリエーターの特殊なケースとしてこのような非同期アクションクリエーターを「見る」ための方法を見つける動機でした。
私たちと一緒にいて、アプリの問題としても認識している場合は、Redux Thunkミドルウェアを使用することができます。
要点として、Redux Thunkは、実際に機能する特別な種類のアクションを認識するようReduxに教えています。
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
const store = createStore(
reducer,
applyMiddleware(thunk)
)
// It still recognizes plain object actions
store.dispatch({ type: 'INCREMENT' })
// But with thunk middleware, it also recognizes functions
store.dispatch(function (dispatch) {
// ... which themselves may dispatch many times
dispatch({ type: 'INCREMENT' })
dispatch({ type: 'INCREMENT' })
dispatch({ type: 'INCREMENT' })
setTimeout(() => {
// ... even asynchronously!
dispatch({ type: 'DECREMENT' })
}, 1000)
})
このミドルウェアが有効な場合、関数をディスパッチすると、Redux Thunkミドルウェアがそれdispatch
を引数として提供します。また、そのようなアクションを「飲み込む」ので、リデューサーが奇妙な関数引数を受け取ることを心配する必要はありません。レデューサーは、直接送信されるか、先ほど説明した関数によって発行される、プレーンオブジェクトアクションのみを受け取ります。
これはあまり役に立たないように見えますか?この特定の状況ではありません。ただしshowNotificationWithTimeout()
、通常のReduxアクション作成者として宣言できます。
// actions.js
function showNotification(id, text) {
return { type: 'SHOW_NOTIFICATION', id, text }
}
function hideNotification(id) {
return { type: 'HIDE_NOTIFICATION', id }
}
let nextNotificationId = 0
export function showNotificationWithTimeout(text) {
return function (dispatch) {
const id = nextNotificationId++
dispatch(showNotification(id, text))
setTimeout(() => {
dispatch(hideNotification(id))
}, 5000)
}
}
関数が前のセクションで記述したものとほとんど同じであることに注意してください。ただしdispatch
、最初の引数として受け入れません。代わりに、最初の引数として受け入れる関数を返しdispatch
ます。
コンポーネントでどのように使用しますか?間違いなく、これを書くことができます:
// component.js
showNotificationWithTimeout('You just logged in.')(this.props.dispatch)
非同期アクションの作成者を呼び出して、必要なだけの内部関数を取得してから、dispatch
渡しdispatch
ます。
ただし、これは元のバージョンよりもさらに厄介です!なぜ私たちはそのように行ったのですか?
前に言ったので。Redux Thunkミドルウェアが有効になっている場合、アクションオブジェクトではなく関数をディスパッチしようとすると、ミドルウェアはdispatch
最初にメソッド自体を使用してその関数を呼び出します。
代わりにこれを行うことができます:
// component.js
this.props.dispatch(showNotificationWithTimeout('You just logged in.'))
最後に、非同期アクション(実際には一連のアクション)のディスパッチは、単一のアクションをコンポーネントに同期的にディスパッチすることと同じように見えます。コンポーネントが何かが同期的または非同期的に発生するかどうかを気にする必要がないため、これは良いことです。それを抽象化しました。
Reduxにそのような「特別な」アクションクリエーター(サンクアクションクリエーターと呼ぶ)を認識するように「教えた」ため、通常のアクションクリエーターを使用する場所ならどこでも使用できることに注意してください。たとえば、次のように使用できますconnect()
。
// actions.js
function showNotification(id, text) {
return { type: 'SHOW_NOTIFICATION', id, text }
}
function hideNotification(id) {
return { type: 'HIDE_NOTIFICATION', id }
}
let nextNotificationId = 0
export function showNotificationWithTimeout(text) {
return function (dispatch) {
const id = nextNotificationId++
dispatch(showNotification(id, text))
setTimeout(() => {
dispatch(hideNotification(id))
}, 5000)
}
}
// component.js
import { connect } from 'react-redux'
// ...
this.props.showNotificationWithTimeout('You just logged in.')
// ...
export default connect(
mapStateToProps,
{ showNotificationWithTimeout }
)(MyComponent)
通常、レデューサーには、次の状態を決定するためのビジネスロジックが含まれています。ただし、レデューサーはアクションがディスパッチされた後でのみ起動します。サンクアクションクリエーターに副作用(APIの呼び出しなど)があり、ある条件下でそれを防ぎたい場合はどうしますか?
サンクミドルウェアを使用せずに、コンポーネント内でこのチェックを行うだけです。
// component.js
if (this.props.areNotificationsEnabled) {
showNotificationWithTimeout(this.props.dispatch, 'You just logged in.')
}
ただし、アクション作成者を抽出する目的は、この反復的なロジックを多くのコンポーネントに集中させることでした。さいわい、Redux Thunkを使用すると、Reduxストアの現在の状態を読み取ることができます。に加えてdispatch
、getState
サンクアクションの作成者から返す関数の2番目の引数としても渡されます。これにより、サンクはストアの現在の状態を読み取ることができます。
let nextNotificationId = 0
export function showNotificationWithTimeout(text) {
return function (dispatch, getState) {
// Unlike in a regular action creator, we can exit early in a thunk
// Redux doesn’t care about its return value (or lack of it)
if (!getState().areNotificationsEnabled) {
return
}
const id = nextNotificationId++
dispatch(showNotification(id, text))
setTimeout(() => {
dispatch(hideNotification(id))
}, 5000)
}
}
このパターンを乱用しないでください。キャッシュされたデータが利用可能な場合にAPI呼び出しを回避するのに適していますが、ビジネスロジックを構築するための適切な基盤ではありません。を使用getState()
して条件付きで異なるアクションをディスパッチする場合は、代わりにビジネスロジックをリデューサーに配置することを検討してください。
サンクがどのように機能するかについて基本的な直感を得たので、サンクを使用するRedux 非同期の例を確認してください。
サンクがプロミスを返す例はたくさんあります。これは必須ではありませんが、非常に便利です。Reduxは、サンクから何を返すかは関係ありませんが、からの戻り値を提供しますdispatch()
。これが、サンクからPromiseを返し、を呼び出すことでPromiseが完了するのを待つことができる理由ですdispatch(someThunkReturningPromise()).then(...)
。
複雑なサンクアクションクリエーターをいくつかの小さなサンクアクションクリエーターに分割することもできます。dispatch
あなたは再帰的にパターンを適用することができるようにサンクによって提供される方法は、自体サンク受け入れることができます。繰り返しになりますが、これに加えて非同期制御フローを実装できるため、これはPromiseで最適に機能します。
アプリによっては、非同期制御フローの要件が複雑すぎて、サンクで表現できない場合があります。たとえば、失敗したリクエストの再試行、トークンを使用した再認証フロー、または段階的なオンボーディングは、この方法で記述した場合、冗長すぎてエラーが発生しやすくなります。この場合、Redux SagaやRedux Loopなどのより高度な非同期制御フローソリューションを検討する必要があります。それらを評価し、ニーズに関連する例を比較して、最も好きなものを選びます。
最後に、本当に必要なものがない場合は(サンクを含む)何も使用しないでください。要件によっては、ソリューションが次のように単純に見える場合があることに注意してください
store.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' })
setTimeout(() => {
store.dispatch({ type: 'HIDE_NOTIFICATION' })
}, 5000)
なぜこれをしているのかわからない限り、汗をかいてはいけません。
if (cond) dispatch({ type: 'A' }) else dispatch({ type: 'B' })
多分書いているなら、現在の状態dispatch({ type: 'C', something: cond })
に応じてaction.something
、代わりにレデューサーのアクションを無視することを選択すべきです。
Dan Abramovが言ったように、非同期コードのより高度な制御が必要な場合は、redux-sagaをご覧ください。
この回答は簡単な例です。redux-sagaがアプリケーションに役立つ理由を詳しく説明したい場合は、この別の回答を確認してください。
一般的な考え方は、Redux-sagaが、同期コードのように見える非同期コードを簡単に作成できるES6ジェネレーターインタープリターを提供しているということです(これがRedux-sagaで無限のwhileループを頻繁に見つける理由です)。どういうわけか、Redux-sagaはJavascript内で直接独自の言語を構築しています。ジェネレーターの基本的な理解が必要であるだけでなく、Redux-sagaが提供する言語も理解しているため、Redux-sagaは最初は少し難しいと感じるかもしれません。
ここで、私がredux-sagaの上に構築した通知システムについて説明します。この例は現在本番環境で実行されています。
私の制作アプリStample.coのスクリーンショット
ここでは通知にaという名前を付けましたtoast
が、これは名前の詳細です。
function* toastSaga() {
// Some config constants
const MaxToasts = 3;
const ToastDisplayTime = 4000;
// Local generator state: you can put this state in Redux store
// if it's really important to you, in my case it's not really
let pendingToasts = []; // A queue of toasts waiting to be displayed
let activeToasts = []; // Toasts currently displayed
// Trigger the display of a toast for 4 seconds
function* displayToast(toast) {
if ( activeToasts.length >= MaxToasts ) {
throw new Error("can't display more than " + MaxToasts + " at the same time");
}
activeToasts = [...activeToasts,toast]; // Add to active toasts
yield put(events.toastDisplayed(toast)); // Display the toast (put means dispatch)
yield call(delay,ToastDisplayTime); // Wait 4 seconds
yield put(events.toastHidden(toast)); // Hide the toast
activeToasts = _.without(activeToasts,toast); // Remove from active toasts
}
// Everytime we receive a toast display request, we put that request in the queue
function* toastRequestsWatcher() {
while ( true ) {
// Take means the saga will block until TOAST_DISPLAY_REQUESTED action is dispatched
const event = yield take(Names.TOAST_DISPLAY_REQUESTED);
const newToast = event.data.toastData;
pendingToasts = [...pendingToasts,newToast];
}
}
// We try to read the queued toasts periodically and display a toast if it's a good time to do so...
function* toastScheduler() {
while ( true ) {
const canDisplayToast = activeToasts.length < MaxToasts && pendingToasts.length > 0;
if ( canDisplayToast ) {
// We display the first pending toast of the queue
const [firstToast,...remainingToasts] = pendingToasts;
pendingToasts = remainingToasts;
// Fork means we are creating a subprocess that will handle the display of a single toast
yield fork(displayToast,firstToast);
// Add little delay so that 2 concurrent toast requests aren't display at the same time
yield call(delay,300);
}
else {
yield call(delay,50);
}
}
}
// This toast saga is a composition of 2 smaller "sub-sagas" (we could also have used fork/spawn effects here, the difference is quite subtile: it depends if you want toastSaga to block)
yield [
call(toastRequestsWatcher),
call(toastScheduler)
]
}
そしてレデューサー:
const reducer = (state = [],event) => {
switch (event.name) {
case Names.TOAST_DISPLAYED:
return [...state,event.data.toastData];
case Names.TOAST_HIDDEN:
return _.without(state,event.data.toastData);
default:
return state;
}
};
単純にTOAST_DISPLAY_REQUESTED
イベントをディスパッチできます。4つのリクエストをディスパッチすると、3つの通知のみが表示され、最初の通知が消えると、4番目のリクエストは少し遅れて表示されます。
TOAST_DISPLAY_REQUESTED
JSXからのディスパッチは特にお勧めしません。代わりに、既存のアプリイベントをリッスンする別のサガを追加してからTOAST_DISPLAY_REQUESTED
、通知をトリガーするコンポーネントを通知システムに結合する必要はありません。
私のコードは完璧ではありませんが、何ヶ月もバグなしで運用環境で実行されます。Redux-sagaとジェネレーターは最初は少し難しいですが、いったん理解すれば、この種のシステムを構築するのはかなり簡単です。
次のように、より複雑なルールを実装することも非常に簡単です。
正直なところ、サンクでこの種のものを適切に実装できるように頑張ってください。
redux-sagaと非常によく似ているredux- observableを使用して、まったく同じようなことができることに注意してください。それはほとんど同じであり、ジェネレーターとRxJSの間の好みの問題です。
yield call(delay,timeoutValue);
。これは同じAPIではありませんが、同じ効果があります
現在、4つのサンプルプロジェクトがあります。
受け入れられた答えは素晴らしいです。
ただし、不足しているものがあります。
そこで、不足しているものを追加するために、Hello Asyncリポジトリを作成しました。
承認された回答には、非同期コードインライン、非同期アクションジェネレーター、およびReduxサンクのサンプルコードスニペットがすでに含まれています。完全を期すために、Redux Sagaのコードスニペットを提供します。
// actions.js
export const showNotification = (id, text) => {
return { type: 'SHOW_NOTIFICATION', id, text }
}
export const hideNotification = (id) => {
return { type: 'HIDE_NOTIFICATION', id }
}
export const showNotificationWithTimeout = (text) => {
return { type: 'SHOW_NOTIFICATION_WITH_TIMEOUT', text }
}
アクションはシンプルで純粋です。
// component.js
import { connect } from 'react-redux'
// ...
this.props.showNotificationWithTimeout('You just logged in.')
// ...
export default connect(
mapStateToProps,
{ showNotificationWithTimeout }
)(MyComponent)
コンポーネントには特別なものはありません。
// sagas.js
import { takeEvery, delay } from 'redux-saga'
import { put } from 'redux-saga/effects'
import { showNotification, hideNotification } from './actions'
// Worker saga
let nextNotificationId = 0
function* showNotificationWithTimeout (action) {
const id = nextNotificationId++
yield put(showNotification(id, action.text))
yield delay(5000)
yield put(hideNotification(id))
}
// Watcher saga, will invoke worker saga above upon action 'SHOW_NOTIFICATION_WITH_TIMEOUT'
function* notificationSaga () {
yield takeEvery('SHOW_NOTIFICATION_WITH_TIMEOUT', showNotificationWithTimeout)
}
export default notificationSaga
SagasはES6ジェネレーターに基づいています
// index.js
import createSagaMiddleware from 'redux-saga'
import saga from './sagas'
const sagaMiddleware = createSagaMiddleware()
const store = createStore(
reducer,
applyMiddleware(sagaMiddleware)
)
sagaMiddleware.run(saga)
上記のコードスニペットですべての質問に答えられない場合は、実行可能なプロジェクトを参照してください。
これはredux-thunkで実行できます。setTimeoutなどの非同期アクションについては、reduxドキュメントにガイドがあります。
applyMiddleware(ReduxPromise, thunk)(createStore)
する場合、これでいくつかのミドルウェア(コマ区切り?)を追加できます。サンクが機能していないようです。
const store = createStore(reducer, applyMiddleware([ReduxPromise, thunk]));
SAMパターンは、「next-action-predicate」を含めることを提唱しており、モデルが更新されると(「5秒後に自動的に通知が自動的に消える」など)アクションがトリガーされます(SAMモデル〜レデューサーの状態+ストア)。
モデルの「制御状態」は、次のアクションの述語によって有効化および/または自動的に実行されるアクションを「制御」するため、パターンは、アクションとモデルの変更を1つずつシーケンス処理することを推奨します。アクションを処理する前にシステムがどのような状態になるか、したがって次の予期されるアクションが許可/可能かどうかを(一般的に)予測することはできません。
したがって、たとえばコード
export function showNotificationWithTimeout(dispatch, text) {
const id = nextNotificationId++
dispatch(showNotification(id, text))
setTimeout(() => {
dispatch(hideNotification(id))
}, 5000)
}
hideNotificationアクションをディスパッチできるという事実は、モデルが値「showNotication:true」を正常に受け入れることに依存しているため、SAMでは許可されません。モデルの他の部分が原因でモデルが受け入れられない可能性があるため、hideNotificationアクションをトリガーする理由はありません。
ストアの更新後に適切な次のアクションの述語を実装し、モデルの新しい制御状態を把握できるようにすることを強くお勧めします。これが、探している動作を実装する最も安全な方法です。
よろしければ、Gitterに参加してください。ここには、SAM入門ガイドもあります。
V = S( vm( M.present( A(data) ) ), nap(M))
ただ美しいです。あなたの考えと経験を共有してくれてありがとう。深く掘り下げます。
さまざまな人気のあるアプローチ(アクションクリエーター、サンク、サガ、エピック、エフェクト、カスタムミドルウェア)を試した後でも、まだ改善の余地があると感じたので、このブログ記事に自分の旅を記録しました。 React / Reduxアプリケーション?
ここでの議論のように、私はさまざまなアプローチを対比して比較しようとしました。結局、それは私に叙事詩、サガ、カスタムミドルウェアからインスピレーションを得る新しいライブラリredux-logicを導入することを導きました。
これにより、アクションをインターセプトして、検証、検証、承認を行い、非同期IOを実行する方法を提供できます。
一部の一般的な機能は、デバウンス、スロットル、キャンセルのように、そして最新のリクエスト(takeLatest)からの応答のみを使用して宣言することができます。redux-logicは、この機能を提供するコードをラップします。
これにより、コアビジネスロジックを自由に実装できます。必要がない限り、オブザーバブルやジェネレーターを使用する必要はありません。関数とコールバック、promise、非同期関数(async / await)などを使用します。
簡単な5秒通知を行うコードは次のようになります。
const notificationHide = createLogic({
// the action type that will trigger this logic
type: 'NOTIFICATION_DISPLAY',
// your business logic can be applied in several
// execution hooks: validate, transform, process
// We are defining our code in the process hook below
// so it runs after the action hit reducers, hide 5s later
process({ getState, action }, dispatch) {
setTimeout(() => {
dispatch({ type: 'NOTIFICATION_CLEAR' });
}, 5000);
}
});
リポジトリに、より高度な通知の例があります。これは、表示をNアイテムに制限し、キューに入れられたアイテムを順番に表示できるSebastian Lorberの説明と同様に機能します。redux-logic通知の例
さまざまなredux-logic jsfiddleライブの例と完全な例がありますます。私はドキュメントと例に取り組んでいます。
私はあなたのフィードバックを聞いてみたいです。
この質問は少し古いことを理解していますが、redux-observable aka を使用した別のソリューションを紹介します。大作。
公式ドキュメントを引用:
再観測可能とは何ですか?
Redux用のRxJS 5ベースのミドルウェア。非同期アクションを作成およびキャンセルして、副作用などを作成します。
エピックは、reduxで観測可能なコアプリミティブです。
これは、一連のアクションを取り、一連のアクションを返す関数です。アクションはイン、アクションはアウト。
多かれ少なかれ、ストリームを介してアクションを受信し、アクションの新しいストリームを返す関数を作成できます(タイムアウト、遅延、間隔、リクエストなどの一般的な副作用を使用)。
コードを投稿して、それについてもう少し説明しましょう
store.js
import {createStore, applyMiddleware} from 'redux'
import {createEpicMiddleware} from 'redux-observable'
import {Observable} from 'rxjs'
const NEW_NOTIFICATION = 'NEW_NOTIFICATION'
const QUIT_NOTIFICATION = 'QUIT_NOTIFICATION'
const NOTIFICATION_TIMEOUT = 2000
const initialState = ''
const rootReducer = (state = initialState, action) => {
const {type, message} = action
console.log(type)
switch(type) {
case NEW_NOTIFICATION:
return message
break
case QUIT_NOTIFICATION:
return initialState
break
}
return state
}
const rootEpic = (action$) => {
const incoming = action$.ofType(NEW_NOTIFICATION)
const outgoing = incoming.switchMap((action) => {
return Observable.of(quitNotification())
.delay(NOTIFICATION_TIMEOUT)
//.takeUntil(action$.ofType(NEW_NOTIFICATION))
});
return outgoing;
}
export function newNotification(message) {
return ({type: NEW_NOTIFICATION, message})
}
export function quitNotification(message) {
return ({type: QUIT_NOTIFICATION, message});
}
export const configureStore = () => createStore(
rootReducer,
applyMiddleware(createEpicMiddleware(rootEpic))
)
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import {configureStore} from './store.js'
import {Provider} from 'react-redux'
const store = configureStore()
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
App.js
import React, { Component } from 'react';
import {connect} from 'react-redux'
import {newNotification} from './store.js'
class App extends Component {
render() {
return (
<div className="App">
{this.props.notificationExistance ? (<p>{this.props.notificationMessage}</p>) : ''}
<button onClick={this.props.onNotificationRequest}>Click!</button>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
notificationExistance : state.length > 0,
notificationMessage : state
}
}
const mapDispatchToProps = (dispatch) => {
return {
onNotificationRequest: () => dispatch(newNotification(new Date().toDateString()))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
この問題を解決するための重要なコードは、見た目と同じくらい簡単です。他の答えと異なるのは、関数rootEpicだけです。
ポイント1. sagasと同様に、アクションのストリームを受け取ってアクションのストリームを返すトップレベルの関数を取得するには、エピックを組み合わせて、ミドルウェアファクトリcreateEpicMiddlewareで使用できるようにする必要があります。私たちの場合、必要なのは1つだけなので、rootEpicだけを持っているので、何も組み合わせる必要はありませんが、事実を知るのは良いことです。
ポイント2.私たちのrootEpic副作用ロジックを処理するは約5行のコードしか必要ないので、すばらしいです。かなり宣言的であるという事実を含みます!
ポイント3.行ごとのrootEpic説明(コメント内)
const rootEpic = (action$) => {
// sets the incoming constant as a stream
// of actions with type NEW_NOTIFICATION
const incoming = action$.ofType(NEW_NOTIFICATION)
// Merges the "incoming" stream with the stream resulting for each call
// This functionality is similar to flatMap (or Promise.all in some way)
// It creates a new stream with the values of incoming and
// the resulting values of the stream generated by the function passed
// but it stops the merge when incoming gets a new value SO!,
// in result: no quitNotification action is set in the resulting stream
// in case there is a new alert
const outgoing = incoming.switchMap((action) => {
// creates of observable with the value passed
// (a stream with only one node)
return Observable.of(quitNotification())
// it waits before sending the nodes
// from the Observable.of(...) statement
.delay(NOTIFICATION_TIMEOUT)
});
// we return the resulting stream
return outgoing;
}
お役に立てば幸いです。
switchMap
ますか?
なぜそんなに難しいのですか?それは単なるUIロジックです。専用のアクションを使用して通知データを設定します。
dispatch({ notificationData: { message: 'message', expire: +new Date() + 5*1000 } })
そしてそれを表示するための専用コンポーネント:
const Notifications = ({ notificationData }) => {
if(notificationData.expire > this.state.currentTime) {
return <div>{notificationData.message}</div>
} else return null;
}
この場合、質問は「古い状態をどのようにクリーンアップするか」、「時間が変更されたことをコンポーネントに通知する方法」です。
コンポーネントからsetTimeoutでディスパッチされるTIMEOUTアクションを実装できます。
たぶん、新しい通知が表示されたときにそれをクリーンアップしても問題ないでしょう。
とにかく、setTimeout
どこかにあるはずですよね?コンポーネントでそれをしないのはなぜですか
setTimeout(() => this.setState({ currentTime: +new Date()}),
this.props.notificationData.expire-(+new Date()) )
その動機は、「通知のフェードアウト」機能は本当にUIの懸念事項であるということです。したがって、ビジネスロジックのテストが簡略化されます。
それがどのように実装されているかをテストすることは意味をなさないようです。通知がタイムアウトするタイミングを確認するだけで意味があります。したがって、スタブするコードが減り、テストが速くなり、コードがすっきりします。
選択的なアクションのタイムアウト処理が必要な場合は、ミドルウェアを試すことができますアプローチを。私はプロミスベースのアクションを選択的に処理するために同様の問題に直面し、このソリューションはより柔軟でした。
アクションクリエーターが次のようになっているとします。
//action creator
buildAction = (actionData) => ({
...actionData,
timeout: 500
})
タイムアウトは上記のアクションで複数の値を保持できます
ミドルウェアの実装は次のようになります。
//timeoutMiddleware.js
const timeoutMiddleware = store => next => action => {
//If your action doesn't have any timeout attribute, fallback to the default handler
if(!action.timeout) {
return next (action)
}
const defaultTimeoutDuration = 1000;
const timeoutDuration = Number.isInteger(action.timeout) ? action.timeout || defaultTimeoutDuration;
//timeout here is called based on the duration defined in the action.
setTimeout(() => {
next (action)
}, timeoutDuration)
}
これで、reduxを使用して、すべてのアクションをこのミドルウェアレイヤーにルーティングできます。
createStore(reducer, applyMiddleware(timeoutMiddleware))
あなたはここにいくつかの同様の例を見つけることができます
これを行う適切な方法は、Redux Thunkのドキュメントに従って、Reduxの一般的なミドルウェアであるRedux Thunkを使用することです。
「Redux Thunkミドルウェアを使用すると、アクションの代わりに関数を返すアクションクリエーターを作成できます。このサンクを使用して、アクションのディスパッチを遅らせたり、特定の条件が満たされた場合にのみディスパッチしたりできます。内部関数はストアメソッドを受け取りますパラメータとしてのディスパッチとgetState」。
したがって、基本的には関数を返します。ディスパッチを遅らせたり、条件状態にすることができます。
したがって、このようなものがあなたのために仕事をします:
import ReduxThunk from 'redux-thunk';
const INCREMENT_COUNTER = 'INCREMENT_COUNTER';
function increment() {
return {
type: INCREMENT_COUNTER
};
}
function incrementAsync() {
return dispatch => {
setTimeout(() => {
// Yay! Can invoke sync or async actions with `dispatch`
dispatch(increment());
}, 5000);
};
}
簡単です。trim-reduxパッケージを使用して、componentDidMount
以下のようにこのように記述し、でkillしcomponentWillUnmount
ます。
componentDidMount() {
this.tm = setTimeout(function() {
setStore({ age: 20 });
}, 3000);
}
componentWillUnmount() {
clearTimeout(this.tm);
}
Redux自体はかなり冗長なライブラリであり、そのようなものについては、関数を提供するRedux-thunkのようなものを使用dispatch
する必要があるため、数秒後に通知のクローズをディスパッチできます。
冗長性や構成可能性などの問題に対処するためにライブラリを作成しました。あなたの例は次のようになります。
import { createTile, createSyncTile } from 'redux-tiles';
import { sleep } from 'delounce';
const notifications = createSyncTile({
type: ['ui', 'notifications'],
fn: ({ params }) => params.data,
// to have only one tile for all notifications
nesting: ({ type }) => [type],
});
const notificationsManager = createTile({
type: ['ui', 'notificationManager'],
fn: ({ params, dispatch, actions }) => {
dispatch(actions.ui.notifications({ type: params.type, data: params.data }));
await sleep(params.timeout || 5000);
dispatch(actions.ui.notifications({ type: params.type, data: null }));
return { closed: true };
},
nesting: ({ type }) => [type],
});
そのため、バックグラウンドで一部の情報を要求したり、後で通知が手動で閉じられたかどうかを確認できる非同期アクション内に通知を表示するための同期アクションを作成します。
redux-saga
サンクより良いものが欲しいなら、私の答えをチェックすることを忘れないでください。答えが遅いので、表示される前に長い間スクロールする必要があります。これがショートカットです:stackoverflow.com/a/38574266/82609