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




لا يمكن تحميل الملف بسبب انتهاء المهلة (8)

باستخدام الإعادة-الملحمة

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

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

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

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

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

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

نتيجة

لقطة شاشة لتطبيق إنتاجي 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 إعلامات فقط ، وسيظهر الإخطارات الرابعة قليلاً بعد اختفاء الإشعار الأول.

لاحظ أنني لا أوصي بالتحديد بإرسال TOAST_DISPLAY_REQUESTED من TOAST_DISPLAY_REQUESTED . تفضل إضافة ملحمة أخرى تستمع إلى أحداث التطبيق الموجودة لديك بالفعل ، ثم ترسل TOAST_DISPLAY_REQUESTED : المكون الخاص بك الذي يقوم بتشغيل الإشعار ، ليس من الضروري أن يقترن بإحكام بنظام الإشعارات.

خاتمة

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

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

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

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

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

https://code.i-harness.com

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

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


مستودع مع مشاريع العينة

الحالية هناك أربعة مشاريع عينة:

  1. كتابة رمز المتزامن مضمنة
  2. استخراج خالق العمل المتزامن
  3. استخدم Redux Thunk
  4. استخدم Redux Saga

الجواب المقبول رائع.

ولكن هناك شيء مفقود:

  1. لا مشاريع نموذجية قابلة للتشغيل ، فقط بعض مقتطفات الكود.
  2. لا رمز عينة للبدائل الأخرى ، مثل:
    1. مسترجع الملحمة

لذلك قمت بإنشاء مستودع Hello Async لإضافة الأشياء المفقودة:

  1. مشاريع رنابل. يمكنك تنزيلها وتشغيلها دون تعديل.
  2. توفير رمز عينة لمزيد من البدائل:

مسترجع الملحمة

توفر الإجابة المقبولة بالفعل مقتطفات من نماذج التعليمات البرمجية لـ Async Code Inline و Async Action Generator و Redux Thunk. من أجل الاكتمال ، أقدم مقتطفات برمجية لـ 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

الايجابيات

  • أنت لا ينتهي في الجحيم رد الاتصال.
  • يمكنك اختبار التدفقات غير المتزامنة بسهولة.
  • أفعالك تبقى نقية.

سلبيات

  • ذلك يعتمد على المولدات ES6 الجديدة نسبيا.

يرجى الرجوع إلى مشروع runnable إذا لم تجب مقتطفات الشفرة أعلاه على جميع أسئلتك.


أتفهم أن هذا السؤال قديم بعض الشيء ، لكنني سأقدم حلًا آخر باستخدام الملقب الذي يمكن ملاحظته . الملحم.

نقلا عن الوثائق الرسمية:

ما هو التراجع يمكن ملاحظتها؟

RxJS الوسيطة المستندة إلى 5 لـ Redux. قم بإنشاء وإلغاء إجراءات المزامنة لإنشاء تأثيرات جانبية والمزيد.

الملحمة هي جوهر البدائية التي يمكن ملاحظتها.

إنها وظيفة تأخذ دفقًا من الإجراءات وتعيد دفقًا من الإجراءات. الإجراءات في ، الإجراءات خارج.

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

اسمح لي بنشر الكود ثم اشرح المزيد عنه

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 ، يجب عليك الجمع بين الملاحم من أجل الحصول على وظيفة المستوى الأعلى التي تتلقى دفق من الإجراءات وإرجاع دفق من الإجراءات ، حتى تتمكن من استخدامها مع createware factorymedware مصنع الوسيطة . في حالتنا ، نحن نحتاج إلى واحدة فقط ، لذلك لا يوجد لدينا سوى الجذر الخاص بنا ، لذلك لا يتعين علينا الجمع بين أي شيء ولكن من الجيد معرفة الحقيقة.

النقطة 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;
}

اتمني ان يكون مفيدا!


إذا كنت ترغب في التعامل مع مهلة محددة على الإجراءات الانتقائية ، يمكنك تجربة نهج middleware . واجهت مشكلة مماثلة للتعامل مع الإجراءات القائمة على الوعد بشكل انتقائي وكان هذا الحل أكثر مرونة.

دعنا نقول لك أن منشئ الإجراء الخاص بك يبدو كالتالي:

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

يمكنك الآن توجيه جميع الإجراءات الخاصة بك من خلال طبقة الوسيطة هذه باستخدام الإعادة.

createStore(reducer, applyMiddleware(timeoutMiddleware))

يمكنك العثور على بعض الأمثلة المشابهة middleware


تتمثل الطريقة المناسبة للقيام بذلك في استخدام Redux Thunk وهو برنامج وسيط شائع لـ Redux ، وفقًا لوثائق Redux Thunk:

"تسمح لك الوسيطة Redux Thunk بكتابة منشئي الإجراءات الذين يعيدون وظيفة بدلاً من إجراء. يمكن استخدام 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);
  };
}

لماذا يجب أن يكون من الصعب جدا؟ انها مجرد منطق واجهة المستخدم. استخدم إجراء مخصصًا لضبط بيانات الإشعارات:

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

في هذه الحالة ، يجب أن تكون الأسئلة هي "كيف تقوم بتنظيف الحالة القديمة؟"

يمكنك تنفيذ بعض إجراءات TIMEOUT التي يتم إرسالها على setTimeout من أحد المكونات.

ربما يكون الأمر جيدًا لتنظيفه عند ظهور إشعار جديد.

على أي حال ، يجب أن يكون هناك بعض في setTimeout مكان ما ، أليس كذلك؟ لماذا لا تفعل ذلك في مكون

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

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

لا يبدو من المنطقي اختبار كيفية تنفيذه. من المنطقي فقط التحقق من وقت الإخطار. وبالتالي أقل رمز كعب ، اختبارات أسرع ، رمز نظافة.


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

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

يسمح لك باعتراض الإجراءات للتحقق من صحة ، التحقق من ، تفويض ، وكذلك توفير وسيلة لأداء المتزامن IO.

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

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

سيكون رمز القيام بإعلام 5s بسيط شيء مثل:

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 وتدويرها في أي قائمة انتظار. مثال إخطار إعادة منطق

لدي مجموعة متنوعة من الأمثلة الحية jsfiddle المسترجعة المنطق وكذلك أمثلة كاملة . ما زلت أعمل على مستندات وأمثلة.

أحب أن أسمع ملاحظاتك.


لا تقع في فخ التفكير يجب على المكتبة أن تصف كيفية القيام بكل شيء . إذا كنت ترغب في القيام بشيء ما مع انتهاء مهلة في 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 ، 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 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() ليس منشئ showNotificationWithTimeout() بالفعل. لا يُرجع إجراء Redux.

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

هذا لا يبدو مفيدا للغاية ، أليس كذلك؟ ليس في هذا الموقف بالذات. ومع ذلك ، فإنه يتيح لنا إعلان 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 ، وتريد منعه تحت بعض الظروف؟

دون استخدام الوسيطة thunk ، يمكنك فقط القيام بهذا الاختيار داخل المكون:

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

ومع ذلك ، فإن الهدف من استخراج مؤلف الحركة هو تركيز هذا المنطق المتكرر على العديد من المكونات. لحسن الحظ ، يوفر لك Redux Thunk طريقة لقراءة الحالة الحالية لمتجر Redux. بالإضافة إلى dispatch ، فإنه يمرر أيضًا getState الثانية إلى الوظيفة التي getState من مُنشئ الإجراءات 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)
  }
}

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

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

الآن بعد أن أصبح لديك حدس أساسي حول كيفية عمل الأشواك ، تحقق من مثال Redux async الذي يستخدمه.

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

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

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

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

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

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







redux