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

اتخذ المؤلف حريته في عدم كتابة جزء تمهيدي حول ما تدور حوله مكتبة الملحمة المسترجعة. إنه يأمل أنه في حالة عدم كفاية البيانات ، فإن القارئ الكريم يستخدم بحث هبر أو البرنامج التعليمي الرسمي . هذه الأمثلة مبسطة إلى حد كبير لنقل الجوهر.
لذلك ، لماذا وضعت لكم جميعا معا. سيكون حول استخدام الملحمة المعادة في المساحات المفتوحة للعملاء القتالية. بشكل أكثر تحديدًا ، حول الحالات الأكثر تعقيدًا وإثارة للاهتمام من "قبول الإجراء => إرسال طلب API => إنشاء إجراء جديد".
آمل في تحفيز دراسة أعمق لهذه المكتبة من قبل زملائي المواطنين ، وكذلك المشاركة في سرور كيف تصبح الأشياء غير المتزامنة المعقدة أكثر قابلية للفهم والتعبير.
Websockets
استخدام الحالة: تلقي تحديثات لقائمة الشواغر المتاحة من الخادم في الوقت الحقيقي باستخدام نموذج الدفع.
إنه بطبيعة الحال يتعلق باستخدام مآخذ الويب. على سبيل المثال ، خذ socket.io ، ولكن في الواقع ، لا يهم واجهة برمجة تطبيقات مأخذ التوصيل هنا.
في sagas هناك شيء مثل القناة. هذا ناقل رسائل يمكن من خلاله لمصدر الأحداث التواصل مع مستهلكيه. الغرض الرئيسي من القنوات هو التواصل بين sagas وتحويل دفق الأحداث غير المتزامنة إلى هيكل مناسب للعمل.
بشكل افتراضي ، يعد المتجر هو قناة الأحداث الرئيسية لردع الملحمة. الأحداث تأتي في شكل عمل. تستخدم القنوات للعمل مع الأحداث وليس من المتجر.
اتضح أن القناة هي فقط ما تحتاجه للعمل مع الدفق غير المتزامن للرسائل من المقبس. لنقم بإنشاء القناة في أسرع وقت ممكن!
لكن أولاً ، قم بإنشاء مأخذ توصيل:
import io from 'socket.io-client'; export const socket = io.connect('/');
أعلن الآن قائمة متواضعة من الأحداث:
export const SocketEvents = { jobsFresh: 'jobs+fresh', };
التالي هو طريقة المصنع لإنشاء قناة. تنشئ الشفرة طريقة للاشتراك في الأحداث التي تهمنا من خلال المقبس ، وطريقة لإلغاء الاشتراك ، وقناة الحدث نفسها مباشرةً:
import { eventChannel } from 'redux-saga'; import { socket } from '../apis/socket'; import { SocketEvents } from '../constants/socket-events'; export function createFreshJobsChannel() { const subscribe = emitter => { socket.on(SocketEvents.jobsFresh, emitter); return () => socket.removeListener(SocketEvents.jobsFresh, emitter); }; return eventChannel(subscribe); }
دعنا نكتب قصة بسيطة إلى حد ما ، في انتظار التحديثات من المقبس وتحويلها إلى الإجراء المقابل:
import { take, call, put } from 'redux-saga/effects'; import { createFreshJobsChannel } from '../channels/fresh-jobs'; import { JobsActions } from '../actions/jobs'; export function * freshJobsSaga() { const channel = yield call(createFreshJobsChannel); while (true) { const jobs = yield take(channel); const action = JobsActions.fresh(jobs); yield put(action); } }
يبقى فقط ربطه بسجل الجذر:
import { fork } from 'redux-saga/effects'; import { freshJobsSaga } from './fresh-jobs'; export function * sagas() { yield fork(freshJobsSaga); }
أماكن جوجل الإكمال التلقائي
استخدام الحالة: إظهار النصائح عندما يدخل المستخدم موقعًا جغرافيًا للبحث اللاحق عن العقارات القريبة.
في الواقع ، نحن بحاجة إلى الإحداثيات ، ويحتاج المستخدم إلى الاسم القابل للقراءة في المجال المطلوب.
يبدو أن هذه المهمة تختلف عن الإجراء الممل "action => API =>"؟ في حالة الإكمال التلقائي ، نريد إجراء أقل عدد ممكن من المكالمات غير المجدية إلى الموارد الخارجية ، وإظهار التلميحات ذات الصلة للمستخدم فقط.
أولاً ، سنكتب طريقة API تستخدم خدمة الإكمال التلقائي لأماكن Google. من الأشياء المثيرة للاهتمام هنا هو الحد من المطالبات داخل بلد معين:
export function getPlaceSuggestions(autocompleteService, countryCode, query) { return new Promise(resolve => { autocompleteService.getPlacePredictions({ componentRestrictions: { country: countryCode }, input: query, }, resolve); }); }
هناك طريقة API سنقوم بسحبها ، يمكنك البدء في كتابة ملحمة. حان الوقت لتوضيح لطلبات عديمة الفائدة.
التنفيذ في الجبهة ، عندما يكتب المستخدم ، ونحن لكل تغيير ، نقرأ - لكل حرف ، إرسال طلب إلى API - يؤدي إلى أي مكان. أثناء قيام المستخدم بالكتابة ، لا يحتاج إلى تلميحات. ولكن عندما يتوقف ، حان الوقت لخدمته.
أيضًا ، لا نريد أن نكون في موقف حيث قام المستخدم بكتابة شيء ما ، أو إيقافه ، أو تم إنهاء طلب واجهة برمجة التطبيقات ، أو كتابة المستخدم لشيء ما ، أو طلب طلب آخر.
وبالتالي ، أنشأنا سباقًا بين طلبين. يمكن أن تتطور الأحداث بطريقتين وكلاهما غير ممتع للغاية.
على سبيل المثال ، سينتهي الطلب غير ذي صلة قبل الطلب الحالي ولحظة سيرى المستخدم مطالبات غير ذات صلة. غير سارة ، ولكن ليست حرجة.
أو ، ينتهي الطلب الحالي في وقت أبكر من الطلب غير ذي صلة وبعد وميض المستخدم سيبقى بنفس مطالبات غير ذات صلة. هذا أمر بالغ الأهمية بالفعل.
بالطبع ، لسنا أول من يواجه مثل هذه المشكلة ، وستساعدنا التقنية المعروفة باسم debounce - تنفيذ المهمة فقط بعد استلام وحدات N من الوقت منذ استلام الحدث الأخير. هنا القليل من المواد حول هذا الموضوع.
في الإعادة الملحمة ، يتم تطبيق هذه التقنية باستخدام اثنين من الآثار - التأخير و takeLatest . أول واحد يؤجل تنفيذ الملحمة من قبل عدد محدد من المللي ثانية. الثاني إنهاء تنفيذ ملحمة العمل بالفعل عند وصول حدث جديد.
مع العلم كل هذا ، سنكتب الملحمة:
import { delay } from 'redux-saga'; import { put, call, select } from 'redux-saga/effects'; import { PlaceActions } from '../actions/place'; import { MapsActions } from '../actions/maps'; import { getPlaceSuggestions } from '../api/get-place-suggestions'; export function placeSuggestionsSaga * ({ payload: query }) { const { maps: { isApiLoaded } } = yield select();
كما في المثال السابق ، يبقى فقط ربطه بقصة الجذر:
import { takeLatest } from 'redux-saga/effects'; import { PlaceActions } from '../actions/place'; import { placeSuggestionsSaga } from './place-suggestions'; export function * sagas() { yield takeLatest( PlaceActions.changeQuery, placeSuggestionsSaga, ); }
المنسدلة أقرب
استخدم الحالة: أغلق القوائم المنسدلة مكتوبة ذاتيًا عند النقر خارج منطقة التحكم.
في الواقع ، هذا هو محاكاة للسلوك المدمج في المتصفح المحدد. سيتم ترك الأسباب التي قد تحتاج إلى قائمة منسدلة مكتوبة على divs لخيال القارئ.
تتمثل الميزة الرئيسية للمهمة التي يتم حلها في تمرير حدث خارج عنصر التحكم ، على سبيل المثال ، عند النقر خارج القائمة.
خمنت؟ نعم ، سوف تساعدنا القنوات أيضًا هنا. باستخدامها ، سنحول أحداث النقرات التي تنبثق إلى الأعلى في الإجراء المقابل.
سيكون من الجميل أن يكون لديك طريقة مصنع تنشئ قنوات لحدث نافذة تعسفي. وهنا هو:
import { eventChannel } from 'redux-saga'; export function createWindowEventChannel(eventName) { const subscribe = emitter => { window.addEventListener(eventName, emitter); return () => window.removeEventListener(eventName, emitter); }; return eventChannel(subscribe); }
نقوم بإنشاء ملحمة مشابهة تمامًا للمثال الأول (يمكنك إنشاء طريقة مصنع لها إذا كنت ترغب في ذلك):
import { take, put, call } from 'redux-saga/effects'; import { createWindowEventChannel } from '../channels/window-event'; import { DropdownActions } from '../actions/dropdown'; export function * closeDropdownsSaga() { const channel = yield call(createWindowEventChannel, 'onClick'); while (true) { const event = yield take(channel); const action = DropdownActions.closeAll(event); yield put(action(event)); } }
المخفضون المهتمون سيضعون السيطرة في حالة مغلقة:
import { handleActions } from 'redux-actions'; import { DropdownActions } from '../actions/dropdown'; export const priceReducer = handleActions({ ..., [DropdownActions.closeAll]: state => ({ ...state, isOpen: false}), }, {});
يجب أن تتوقف القائمة المنسدلة نفسها عن نشر حدث النقر على أي أجزاء داخلية وأن ترسل الحدث الختامي إلى المتجر بمفرده. على سبيل المثال ، عند النقر فوق "فتح":
خلاف ذلك ، فإن القائمة ببساطة لن تفتح. الأمر نفسه ينطبق على نقرة عند تحديد خيار.
كلاسيكو ، جبل الملحمة:
import { fork } from 'redux-saga/effects'; import { closeDropdownsSaga } from './close-dropdowns'; export function * sagas() { yield fork(closeDropdownsSaga); }
الإخطارات
استخدام الحالة: إظهار إعلامات المتصفح حول توفر الوظائف الشاغرة الجديدة ، إذا كانت علامة التبويب في الخلفية.
في علامة التبويب النشطة ، سيرى المستخدم التغيير في عنصر تحكم خاص ، وبالتالي فإن الإخطارات ليست مناسبة. ولكن لعلامة التبويب الخلفية في متناول يدي. بالطبع ، بإذن من المستخدم!
أود أيضًا النقر فوق الإشعار للانتقال إلى علامة التبويب وإظهار الشواغر الجديدة. إذا لم يستجب المستخدم ، فأغلق الإشعار. للقيام بذلك ، نحن بحاجة إلى تأثير آخر مفيد - العرق . انها تسمح لك لترتيب سباق بين العديد من الآثار الأخرى. في معظم الحالات ، يستخدم السباق لتوفير مهلة لبعض العمليات.
لقد أغفلنا رمز تتبع نشاط علامة التبويب ، نظرًا للهوية التي بها رمز اعتراض النقرات من المثال السابق.
سنكتب طريقة المصنع التي ستنشئ قناة لطلب موافقة المستخدم لتلقي الإخطارات:
import { eventChannel, END } from 'redux-saga'; export function createRequestNotificationPermissionChannel() { const subscribe = emitter => { Notification.requestPermission(permission => { emitter(permission); emitter(END); }); return () => {}; }; return eventChannel(subscribe); }
سعياً وراء أسلوب مصنع آخر ، ولكن مع قناة لتلقي إشعار ، انقر فوق:
import { eventChannel, END } from 'redux-saga'; export function createNotificationClickChannel(notification) { const subscribe = emitter => { notification.onclick = event => { emitter(event); emitter(END); }; return () => notification.onclick = null; }; return eventChannel(subscribe); }
كلتا القناتين تستخدمان مرة واحدة وأقصى تبادل لإطلاق النار في حدث واحد ، وبعد ذلك يتم إغلاقهما.
يبقى المفتاح - الملحمة مع المنطق. تحقق مما إذا كانت علامة التبويب نشطة ، اطلب الإذن ، وقم بإنشاء إشعار ، وانتظر نقرة أو مهلة ، وأظهر الشواغر الجديدة ، وجعل علامة التبويب نشطة ، ثم أغلق الإشعار:
import { delay } from 'redux-saga'; import { call, select, race, take } from 'redux-saga/effects'; import { createRequestNotificationPermissionChannel } from '../channels/request-notification-permission'; import { createNotificationClickChannel } from '../channels/notification-click'; import { JobsActions } from '../actions/jobs'; export function * notificationsSaga(action) { const { inFocus } = yield select(); if (inFocus) return; const permissionChannel = yield call(createRequestNotificationPermissionChannel); const permission = yield take(permissionChannel); if (permission !== 'granted') return; const notification = new Notification( `You have ${action.payload.jobs.length} new job posts`, { icon: 'assets/new-jobs.png' } ); const clickChannel = yield call(createNotificationClickChannel, notification); const { click, timeout } = yield race({ click: take(clickChannel), timeout: call(delay, 5000), }); if (click) { yield put(JobsActions.show()); window.focus(); window.scrollTo(0, 0); } notification.close(); }
قم بتركيب الملحمة قبل القيام بهذا الكشف عن الميزات:
import { takeEvery } from 'redux-saga/effects'; import { JobsActions } from '../actions/jobs'; import { notificationsSaga } from './notifications'; export default function * sagas() { if ( 'Notification' in window && Notification.permission !== 'denied' ) { yield takeEvery(JobsActions.fresh, notificationsSaga); } }
الحافلة الحدث العالمي
حالة الاستخدام: نقل فئة الأحداث المحددة بين مخازن الإعادة.
مثل هذا الناقل ضروري في حالة وجود العديد من التطبيقات التي تحتوي على بيانات شائعة على الصفحة. في الوقت نفسه ، يمكن تنفيذ التطبيقات بشكل مستقل عن بعضها البعض.
على سبيل المثال ، سلسلة البحث ذات الفلاتر ونتائج البحث كتطبيقات تفاعل منفصلة. عند تغيير المرشحات ، أريد أن يعرف تطبيق النتائج ما إذا كان موجودًا على الصفحة أيضًا.
نستخدم باعث الحدث القياسي:
import EventEmmiter from 'events'; if (!window.GlobalEventBus) { window.GlobalEventBus = new EventEmmiter(); } export const globalEventBus = window.GlobalEventBus;
الحدث المحبوب بالفعل ChanChannel يحول باعث قياسي إلى قناة:
import { eventChannel } from 'redux-saga'; import { globalEventBus as bus } from '../utils/global-event-bus'; exports function createGlobalEventBusChannel() { const subscribe = emitter => { const handler = event => emitter({ ...event, external: true }); bus.on('global.event', handler); return bus.removeListener('global.event', handler); }; return eventChannel(subscribe); }
الملحمة بسيطة جدًا - فنحن ننشئ قناة ونقبل الأحداث بلا نهاية ، سواء داخلية أو خارجية. إذا تلقينا حدثًا داخليًا ، فأرسله إلى الحافلة ، إذا كان الحدث الخارجي - في المتجر:
import { take, put, race, call } from 'redux-saga/effects'; import { globalEventBus as bus } from '../utils/global-event-bus'; import { createGlobalEventBusChannel } from '../channels/global-event-bus'; export function * globalEventBusSaga(allowedActions) { allowedActions = allowedActions.map(x => x.toString()); const channel = yield call(createGlobalEventBusChannel); while (true) { const { local, external } = yield race({ local: take(), external: take(channel), }); if ( external && allowedActions.some(action => action === external.type) ) { yield put(external); } if ( local && !local.external && allowedActions.some(action => action === local.type) ) { bus.emit('global.event', local); } } };
والأخير - تصاعد الملحمة مع الأحداث الضرورية:
import { fork } from 'redux-saga/effects'; import { globalEventBusSaga } from './global-event-bus'; import { DropdownsActions } from '../actions/dropdowns'; import { AreasActions } from '../actions/areas'; export function * sagas() { yield fork(globalEventBusSaga, [ DropdownsActions.closeAll, AreasActions.add, AreasActions.remove, ... ]); }
آمل أن أكون قادرًا على إظهار أن الساجا تجعل وصف الآثار الجانبية المعقدة أسهل. استكشف واجهات برمجة التطبيقات الخاصة بالمكتبات ، وقم بنقلها إلى حالاتك ، وقم بتكوين أنماط معقدة من توقع الحدث وكن سعيدًا. نراكم في المساحات المفتوحة JS!