javascript - كيفية إرسال إجراء Redux مع انتهاء المهلة؟




timeout (10)

لا تقع في فخ الاعتقاد بأن المكتبة يجب أن تصف كيفية القيام بكل شيء . إذا كنت تريد عمل شيء ما مع مهلة في 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)

حتى الآن لم نستخدم أي وسيط أو أي مفهوم متقدم آخر.

استخراج Async Action Creator

النهج أعلاه يعمل بشكل جيد في حالات بسيطة ولكنك قد تجد أن لديها بعض المشاكل:

  • يفرض عليك تكرار هذا المنطق في أي مكان تريد فيه عرض إشعار.
  • لا تحتوي الإشعارات على معرّفات ، لذلك سيكون لديك حالة سباق إذا كنت تعرض إشارتين بسرعة كافية. عند انتهاء المهلة الأولى ، سيتم إرسال HIDE_NOTIFICATION ، وإخفاء الإشعار الثاني بشكلٍ أسرع بعد انتهاء المهلة.

لحل هذه المشاكل ، ستحتاج إلى استخراج دالة تقوم بتركيز منطق انتهاء المهلة وترسل هذين الإجراءين. قد يبدو كالتالي:

// 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 interval ID and call
  // clearInterval(), 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.')    

هذا يحل المشاكل مع ازدواجية المنطق ويوفر لنا من ظروف السباق.

thunk الوسيطة

بالنسبة للتطبيقات البسيطة ، يجب أن يكون النهج كافياً. لا تقلق بشأن الوسيطة إذا كنت راضيًا عنها.

ومع ذلك ، في التطبيقات الأكبر حجمًا ، قد تجد بعض المضايقات المحيطة بها.

على سبيل المثال ، يبدو من المؤسف أنه يتعين علينا أن نمرر في كل مكان. هذا يجعل الأمر أكثر صعوبة لفصل مكونات الحاوية والعرض لأن أي مكون يقوم بإرسال تصرفات Redux بشكل غير متزامن بالطريقة أعلاه يجب أن يقبل dispatch كدعم حتى يمكنه تمريره بشكل أكبر. لا يمكنك فقط ربط منشئي العمل connect() بعد الآن لأن showNotificationWithTimeout() ليس منشئ فعل حقيقي. لا يقوم بإرجاع إجراء Redux.

بالإضافة إلى ذلك ، قد يكون من الغريب أن تتذكر أي من المهام عبارة عن showNotification() عمل متزامنة مثل 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 للتعرف على منشئي الأعمال "الخاصين" (نحن نسميهم thunk إجراءات العمل) ، يمكننا الآن استخدامهم في أي مكان نستخدم فيه منشئي إجراءات منتظمين. على سبيل المثال ، يمكننا استخدامها مع 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)

قراءة الدولة في Thunks

عادة ما تحتوي المخفضات الخاصة بك على منطق الأعمال لتحديد الحالة التالية. ومع ذلك ، المخفّضات تبدأ فقط بعد إرسال الإجراءات. ماذا لو كان لديك تأثير جانبي (مثل استدعاء واجهة برمجة التطبيقات) في مُنشئ إجراء من نوع thunk ، وتريد منعه في بعض الحالات؟

بدون استخدام الوسيطة الضعيفة ، ما عليك سوى القيام بهذا الفحص داخل المكون:

// component.js
if (this.props.areNotificationsEnabled) {
  showNotificationWithTimeout(this.props.dispatch, 'You just logged in.')
}

ومع ذلك ، كانت نقطة استخراج مبدع عمل هي جعل هذا المنطق التكراري مركزيًا عبر العديد من المكونات. لحسن الحظ ، يوفر لك Redux Thunk طريقة لقراءة الحالة الحالية لمتجر Redux. بالإضافة إلى dispatch ، يمرر أيضًا getState الثانية إلى الدالة التي getState من منشئ الإجراء الخاص thunk. يتيح هذا thunk قراءة الحالة الحالية للمخزن.

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)
  }
}

لا تسيء استخدام هذا النمط. من الجيد الإنطلاق من مكالمات واجهة برمجة التطبيقات عند توفر بيانات مخزنة مؤقتًا ، ولكنه ليس أساسًا جيدًا لبناء منطق أعمالك. إذا كنت تستخدم getState() فقط لإرسال إجراءات مختلفة بشروط ، getState() في اعتبارك وضع منطق العمل في المخفضات بدلاً من ذلك.

الخطوات التالية

الآن بعد أن لديك الحدس الأساسي حول كيفية عمل thunks ، تحقق من مثال غير متزامن Redux الذي يستخدمها.

قد تجد العديد من الأمثلة التي تعيد فيها thunks الوعود. هذا ليس مطلوبا ولكن يمكن أن تكون مريحة للغاية. لا يهمك Redux ما ترجع من thunk ولكنه يمنحك قيمة الإرجاع من dispatch() . هذا هو السبب في أنه يمكنك إرجاع "وعد" من thunk و انتظر حتى إكمال عن طريق استدعاء dispatch(someThunkReturningPromise()).then(...) .

يمكنك أيضًا تقسيم منشئي أحداث معقدة وثقيلة إلى عدة منشئي أحداث أصغر. يمكن أن تقبل طريقة dispatch التي توفرها thunks thunks نفسه ، بحيث يمكنك تطبيق النمط بشكل متكرر. مرة أخرى ، يعمل هذا بشكل أفضل مع Promises لأنه يمكنك تنفيذ تدفق التحكم غير المتزامن فوق ذلك.

بالنسبة لبعض التطبيقات ، قد تجد نفسك في موقف تكون فيه متطلبات تدفق التحكم غير المتزامنة معقدة للغاية بحيث لا يمكن التعبير عنها بظهور. على سبيل المثال ، يمكن أن تكون إعادة محاولة الطلبات الفاشلة أو إعادة تفويض التدفقات مع الرموز المميزة أو الإعداد خطوة بخطوة عملية مطولة للغاية وعرضة للخطأ عند كتابتها بهذه الطريقة. في هذه الحالة ، قد ترغب في النظر إلى حلول تدفق تحكم غير متزامن أكثر تقدمًا مثل Redux Saga أو Redux Loop . قيّمها ، وقارن الأمثلة ذات الصلة باحتياجاتك ، واختر النموذج الذي تفضله.

وأخيرًا ، لا تستخدم أي شيء (بما في ذلك الأجزاء السفلية) إذا لم تكن لديك الحاجة الحقيقية إليها. تذكر أنه حسب المتطلبات ، قد يبدو الحل بسيطًا مثل

store.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' })
setTimeout(() => {
  store.dispatch({ type: 'HIDE_NOTIFICATION' })
}, 5000)

لا تتعرق إلا إذا كنت تعرف لماذا تفعل هذا.

لدي إجراء يقوم بتحديث حالة الإعلام الخاصة بالتطبيق الخاص بي. عادة ، سيكون هذا الإشعار خطأ أو معلومات من نوع ما. أحتاج بعد ذلك إلى إرسال إجراء آخر بعد 5 ثوانٍ من شأنه إرجاع حالة الإعلام إلى حالة واحدة ، لذلك لا يوجد إعلام. يتمثل السبب الرئيسي وراء ذلك في توفير وظائف تختفي فيها الإشعارات تلقائيًا بعد 5 ثوانٍ.

لم يكن لدي أي حظ في استخدام setTimeout وإرجاع إجراء آخر ولا أستطيع العثور على كيفية القيام بذلك عبر الإنترنت. لذلك فإن أي نصيحة هي موضع ترحيب.


باستخدام Redux-saga

وكما قال دان أبراموف ، إذا كنت تريد تحكمًا أكثر تقدمًا في شفرة برنامجك المتزامن ، فيمكنك إلقاء نظرة على ملحمة الإعادة .

هذه الإجابة هي مثال بسيط ، إذا كنت تريد تفسيرات أفضل حول سبب فائدة برنامج redux-saga لتطبيقك ، فتحقق من هذه الإجابة الأخرى .

والفكرة العامة هي أن Redux-saga توفر مترجمًا لمولدات ES6 يسمح لك بكتابة رمز المتزامن الذي يشبه رمزًا متزامنًا (وهذا هو السبب في أنك غالبًا ما تجد اللانهائي أثناء الحلقات في Redux-saga). بطريقة ما ، تقوم Redux-saga ببناء لغتها الخاصة مباشرة داخل Javascript. يمكن أن تشعر حملة Redux-Saga ببعض الصعوبة في تعلمها في البداية ، لأنك تحتاج إلى فهم أساسي للمولدات ، ولكنك تفهم أيضًا اللغة التي تقدمها Redux-saga.

سأحاول هنا أن أصف هنا نظام الإخطار الذي قمت ببنائه على قمة ملحمة الإعادة. هذا المثال يعمل حاليا في الإنتاج.

مواصفات نظام الإخطار المتقدم

  • يمكنك طلب إشعار ليتم عرضها
  • يمكنك طلب إشعار للاختباء
  • يجب ألا يتم عرض الإشعار أكثر من 4 ثوانٍ
  • يمكن عرض إشعارات متعددة في نفس الوقت
  • لا يمكن عرض أكثر من 3 إشعارات في نفس الوقت
  • في حالة طلب إعلام أثناء وجود 3 إشعارات معروضة بالفعل ، فقم بقائمة الانتظار / تأجيلها.

نتيجة

لقطة شاشة لتطبيق الإنتاج الخاص بي Stample.co

الشفرة

رمز من التطبيق الإنتاج Stample.co .

هنا سمعت الإخطار 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 من TOAST_DISPLAY_REQUESTED . يمكنك بدلاً من ذلك إضافة قصة أخرى تستمع إلى أحداث التطبيق الموجودة بالفعل ، ثم إرسال TOAST_DISPLAY_REQUESTED : المكون الذي يشغِّل الإشعار ، لا يلزم أن يكون مقترنًا بشدة بنظام الإشعارات.

استنتاج

الكود الخاص بي ليس مثالياً ولكنه يعمل في الإنتاج مع وجود أخطاء لمدة شهر. إن عملية إعادة إنتاج الملحمة والمولدات صعبة بعض الشيء في البداية ، لكن بمجرد فهمك لها فإن هذا النوع من النظام سهل جدًا.

من السهل جدًا تنفيذ قواعد أكثر تعقيدًا ، مثل:

  • عندما تكون "الإخطارات" أكثر من اللازم ، تعطي وقتًا أقل للعرض لكل إشعار بحيث يمكن تقليل حجم قائمة الانتظار بشكل أسرع.
  • اكتشاف التغييرات في حجم النافذة وتغيير الحد الأقصى لعدد الإعلامات المعروضة وفقًا لذلك (على سبيل المثال ، سطح المكتب = 3 ، صورة الهاتف = 2 ، منظر الهاتف = 1)

بصراحة ، حسن الحظ في تنفيذ هذا النوع من الأشياء بشكل صحيح مع thunks.

لاحظ أنه بإمكانك فعل نفس نوع الشيء مع إمكانية redux-observable وهو أمر مشابه للغاية لملحمة الإعادة. انها تقريبا نفس وهي مسألة الذوق بين المولدات و RxJS.


If you want timeout handling on selective actions, you can try the middleware approach. I faced a similar problem for handling promise based actions selectively and this solution was more flexible.

Lets say you your action creator looks like this:

//action creator
buildAction = (actionData) => ({
    ...actionData,
    timeout: 500
})

timeout can hold multiple values in the above action

  • number in ms - for a specific timeout duration
  • true - for a constant timeout duration. (handled in the middleware)
  • undefined - for immediate dispatch

Your middleware implementation would look like this:

//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)
}

You can now route all your actions through this middleware layer using redux.

createStore(reducer, applyMiddleware(timeoutMiddleware))

You can find some similar examples middleware


The appropriate way to do this is using Redux Thunk which is a popular middleware for Redux, as per Redux Thunk documentation:

"Redux Thunk middleware allows you to write action creators that return a function instead of an action. The thunk can be used to delay the dispatch of an action, or to dispatch only if a certain condition is met. The inner function receives the store methods dispatch and getState as parameters".

So basically it returns a function, and you can delay your dispatch or put it in a condition state.

So something like this is going to do the job for you:

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);
  };
}

بعد تجربة الطرق الشعبية المختلفة (المبدعين ، الثّلاث ، الملاحم ، الملاحم ، التأثيرات ، الوسيطة المخصّصة) ، ما زلت أحس أنّ هناك مجال للتحسين لذا قمت بتوثيق رحلتي في هذا المقال ، أين أضع منطق عملي في تطبيق React / Redux؟

مثل الكثير من المناقشات هنا ، حاولت المقارنة بين المقاربات المختلفة ومقارنتها. وفي النهاية ، قادتني إلى تقديم redux-logic جديد لإعادة redux-logic المكتبة يستلهم من الملاحم والمخطوطات والوسائط الوسيطة المخصصة.

يسمح لك باعتراض الإجراءات للتحقق من صحة والتحقق من ، تخويل ، بالإضافة إلى توفير طريقة لتنفيذ IOc المتزامن.

يمكن ببساطة أن يتم الإعلان عن بعض الوظائف الشائعة مثل الاستدلال ، الاختناق ، الإلغاء ، واستخدام الرد فقط من أحدث طلب (takeLatest). يزيل redux-logic التعليمة البرمجية التي توفر هذه الوظيفة لك.

هذا يحررك لتنفيذ منطق الأعمال الأساسية الخاصة بك كيفما تشاء. لا يتعين عليك استخدام الملاحظات أو المولدات إلا إذا كنت تريد ذلك. استخدام الدالات وعمليات الاسترجاعات والوعود ووظائف المتزامن (المتزامن / الانتظار) وما إلى ذلك.

سيكون رمز إجراء إشعار بسيط بخمس ثوان شيئًا مثل:

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);
  }
});
    

I have a more advanced notification example in my repo that works similar to what Sebastian Lorber described where you could limit the display to N items and rotate through any that queued up. redux-logic notification example

I have a variety of redux-logic jsfiddle live examples as well as full examples . I'm continuing to work on docs and examples.

I'd love to hear your feedback.


I understand that this question is a bit old but I'm going to introduce another solution using redux-observable aka. Epic.

Quoting the official documentation:

What is redux-observable?

RxJS 5-based middleware for Redux. Compose and cancel async actions to create side effects and more.

An Epic is the core primitive of redux-observable.

It is a function which takes a stream of actions and returns a stream of actions. Actions in, actions out.

In more or less words, you can create a function that receives actions through a Stream and then return a new stream of actions (using common side effects such as timeouts, delays, intervals, and requests).

Let me post the code and then explain a bit more about it

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)

The key code to solve this problem is as easy as pie as you can see, the only thing that appears different from the other answers is the function rootEpic.

Point 1. As with sagas, you have to combine the epics in order to get a top level function that receives a stream of actions and returns a stream of actions, so you can use it with the middleware factory createEpicMiddleware . In our case we only need one so we only have our rootEpic so we don't have to combine anything but it's a good to know fact.

Point 2. Our rootEpic which takes care about the side effects logic only takes about 5 lines of code which is awesome! Including the fact that is pretty much declarative!

Point 3. Line by line rootEpic explanation (in comments)

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;
}

I hope it helps!


أوصي أيضا بإلقاء نظرة على نمط SAM .

ويدافع نمط SAM عن تضمين "دالة التقييم التالية" حيث يتم تشغيل الإجراءات (التلقائية) مثل "الإخطارات تختفي تلقائيًا بعد 5 ثوانٍ" بمجرد تحديث الطراز (طراز SAM ~ حالة المخزن + المخزن).

ويدافع هذا النمط عن إجراءات التسلسل ونماذج الطفرات في وقت واحد ، لأن "حالة التحكم" في نموذج "عناصر التحكم" التي يتم تمكين الإجراءات و / أو تنفيذها تلقائيًا بواسطة دالة التقييم التالية. أنت ببساطة لا تستطيع أن تتوقع (بشكل عام) ما هي الحالة التي سيتبعها النظام قبل معالجة الإجراء ، وبالتالي ما إذا كان سيتم السماح / ممكن بالإجراء المتوقع التالي.

على سبيل المثال الرمز ،

export function showNotificationWithTimeout(dispatch, text) {
  const id = nextNotificationId++
  dispatch(showNotification(id, text))

  setTimeout(() => {
    dispatch(hideNotification(id))
  }, 5000)
}

لن يتم السماح باستخدام SAM ، لأن حقيقة أن عملية hideNotification يمكن أن يتم إرسالها تعتمد على النموذج بنجاح قبول القيمة "showNotication: true". يمكن أن تكون هناك أجزاء أخرى من النموذج تمنعها من قبولها ، وبالتالي ، لن يكون هناك سبب لتحريك الإجراء hideNotification.

نوصي بشدة بتنفيذ دالة تقييم صحيحة للعملية التالية بعد تحديثات المتجر ويمكن معرفة حالة التحكم الجديدة في النموذج. هذه هي الطريقة الأسلم لتنفيذ السلوك الذي تبحث عنه.

يمكنك الانضمام إلينا على Gitter إذا كنت ترغب في ذلك. هناك أيضا دليل بدء SAM متاح هنا .


Why should it be so hard? It's just UI logic. Use a dedicated action to set notification data:

dispatch({ notificationData: { message: 'message', expire: +new Date() + 5*1000 } })

and a dedicated component to display it:

const Notifications = ({ notificationData }) => {
    if(notificationData.expire > this.state.currentTime) {
      return <div>{notificationData.message}</div>
    } else return null;
}

In this case the questions should be "how do you clean up old state?", "how to notify a component that time has changed"

You can implement some TIMEOUT action which is dispatched on setTimeout from a component.

Maybe it's just fine to clean it whenever a new notification is shown.

Anyway, there should be some setTimeout somewhere, right? Why not to do it in a component

setTimeout(() => this.setState({ currentTime: +new Date()}), 
           this.props.notificationData.expire-(+new Date()) )

The motivation is that the "notification fade out" functionality is really a UI concern. So it simplifies testing for your business logic.

It doesn't seem to make sense to test how it's implemented. It only makes sense to verify when the notification should time out. Thus less code to stub, faster tests, cleaner code.


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],
});

لذلك نحن نؤلف إجراءات مزامنة لعرض الإشعارات داخل إجراء متزامن ، والتي يمكن أن تطلب بعض المعلومات في الخلفية ، أو التحقق لاحقًا مما إذا كان الإشعار قد تم إغلاقه يدويًا.


جافا سكريبت:

window.location.href='www.your_url.com';
window.top.location.href='www.your_url.com';
window.location.replace('www.your_url.com');

مسج:

var url='www.your_url.com';
$(location).attr('href',url);
$(location).prop('href',url);//instead of location you can use window




javascript timeout redux