استكشف البرمجة التفاعلية في JavaScript باستخدام RxJS. تعلم تدفقات قابلة للملاحظة وأنماط وتطبيقات عملية لبناء تطبيقات مستجيبة وقابلة للتطوير.
برمجة JavaScript التفاعلية: أنماط RxJS وتدفقات قابلة للملاحظة
في المشهد المتطور باستمرار لتطوير الويب الحديث، يعد بناء تطبيقات مستجيبة وقابلة للتطوير والصيانة أمرًا بالغ الأهمية. توفر البرمجة التفاعلية (RP) نموذجًا قويًا للتعامل مع تدفقات البيانات غير المتزامنة ونشر التغييرات في جميع أنحاء تطبيقك. من بين المكتبات الشائعة لتنفيذ RP في JavaScript، تبرز RxJS (Reactive Extensions for JavaScript) كأداة قوية ومتعددة الاستخدامات.
ما هي البرمجة التفاعلية؟
في جوهرها، تدور البرمجة التفاعلية حول التعامل مع تدفقات البيانات غير المتزامنة ونشر التغيير. تخيل جدول بيانات حيث يؤدي تحديث خلية واحدة إلى إعادة حساب الصيغ ذات الصلة تلقائيًا. هذا هو جوهر RP - التفاعل مع تغييرات البيانات بطريقة تعريفية وفعالة.
غالبًا ما تتضمن البرمجة الإجرائية التقليدية إدارة الحالة وتحديث المكونات يدويًا استجابةً للأحداث. يمكن أن يؤدي ذلك إلى تعليمات برمجية معقدة وعرضة للأخطاء، خاصة عند التعامل مع العمليات غير المتزامنة مثل طلبات الشبكة أو تفاعلات المستخدم. تعمل RP على تبسيط ذلك من خلال التعامل مع كل شيء كتدفق من البيانات وتوفير عوامل لت transform وتصفية ودمج هذه التدفقات.
تقديم RxJS: Reactive Extensions for JavaScript
RxJS هي مكتبة لتأليف البرامج غير المتزامنة والقائمة على الأحداث باستخدام تسلسلات قابلة للملاحظة. يوفر مجموعة من العوامل القوية التي تتيح لك معالجة تدفقات البيانات بسهولة. تعتمد RxJS على نمط المراقب ونمط المكرر ومفاهيم البرمجة الوظيفية لإدارة تسلسلات الأحداث أو البيانات بكفاءة.
المفاهيم الأساسية في RxJS:
- Observables: تمثل دفقًا من البيانات التي يمكن ملاحظتها بواسطة مراقب واحد أو أكثر. إنها كسولة وتبدأ فقط في إصدار القيم عند الاشتراك فيها.
- Observers: تستهلك البيانات الصادرة من Observables. لديهم ثلاث طرق:
next()
لاستقبال القيم، وerror()
للتعامل مع الأخطاء، وcomplete()
للإشارة إلى نهاية الدفق. - Operators: الوظائف التي تحول أو تصفية أو تجمع أو تعالج Observables. توفر RxJS مجموعة كبيرة من العوامل لأغراض مختلفة.
- Subjects: تعمل كـ Observables و Observers، مما يسمح لك بإرسال البيانات المتعددة إلى العديد من المشتركين وأيضًا دفع البيانات إلى الدفق.
- Schedulers: التحكم في تزامن Observables، مما يسمح لك بتنفيذ التعليمات البرمجية بشكل متزامن أو غير متزامن، على سلاسل رسائل مختلفة، أو مع تأخيرات محددة.
تدفقات قابلة للملاحظة بالتفصيل
تعد Observables هي أساس RxJS. إنها تمثل دفقًا من البيانات التي يمكن ملاحظتها بمرور الوقت. تصدر Observable قيمًا لمشتركيها، والذين يمكنهم بعد ذلك معالجة هذه القيم أو التفاعل معها. فكر في الأمر على أنه خط أنابيب حيث تتدفق البيانات من مصدر إلى مستهلك واحد أو أكثر.
إنشاء Observables:
توفر RxJS عدة طرق لإنشاء Observables:
Observable.create()
: طريقة منخفضة المستوى تمنحك تحكمًا كاملاً في سلوك Observable.from()
: يحول مصفوفة أو وعد أو تكرار أو كائن يشبه Observable إلى Observable.of()
: ينشئ Observable يصدر تسلسلًا من القيم.interval()
: ينشئ Observable يصدر تسلسلًا من الأرقام في فاصل زمني محدد.timer()
: ينشئ Observable يصدر قيمة واحدة بعد تأخير محدد، أو يصدر تسلسلًا من الأرقام في فاصل زمني ثابت بعد التأخير.fromEvent()
: ينشئ Observable يصدر أحداثًا من عنصر DOM أو مصدر حدث آخر.
مثال: إنشاء Observable من مصفوفة
```javascript import { from } from 'rxjs'; const myArray = [1, 2, 3, 4, 5]; const myObservable = from(myArray); myObservable.subscribe( value => console.log('Received:', value), error => console.error('Error:', error), () => console.log('Completed') ); // Output: // Received: 1 // Received: 2 // Received: 3 // Received: 4 // Received: 5 // Completed ```
مثال: إنشاء Observable من حدث
```javascript import { fromEvent } from 'rxjs'; const button = document.getElementById('myButton'); const clickObservable = fromEvent(button, 'click'); clickObservable.subscribe( event => console.log('Button clicked!', event) ); ```
الاشتراك في Observables:
لبدء استقبال القيم من Observable، تحتاج إلى الاشتراك فيه باستخدام طريقة subscribe()
. تقبل طريقة subscribe()
ما يصل إلى ثلاثة وسائط:
next
: دالة سيتم استدعاؤها لكل قيمة تصدرها Observable.error
: دالة سيتم استدعاؤها إذا أصدر Observable خطأ.complete
: دالة سيتم استدعاؤها عند اكتمال Observable (يشير إلى نهاية الدفق).
تعيد طريقة subscribe()
كائن Subscription، الذي يمثل الاتصال بين Observable و Observer. يمكنك استخدام كائن Subscription لإلغاء الاشتراك في Observable، مما يمنع إصدار المزيد من القيم.
إلغاء الاشتراك من Observables:
يعد إلغاء الاشتراك أمرًا بالغ الأهمية لمنع تسرب الذاكرة، خاصة عند التعامل مع Observables طويلة الأمد أو Observables التي تصدر قيمًا بشكل متكرر. يمكنك إلغاء الاشتراك من Observable عن طريق استدعاء طريقة unsubscribe()
على كائن Subscription.
```javascript import { interval } from 'rxjs'; const myInterval = interval(1000); const subscription = myInterval.subscribe( value => console.log('Interval:', value) ); // After 5 seconds, unsubscribe setTimeout(() => { subscription.unsubscribe(); console.log('Unsubscribed!'); }, 5000); // Output (approximately): // Interval: 0 // Interval: 1 // Interval: 2 // Interval: 3 // Interval: 4 // Unsubscribed! ```
عوامل RxJS: تحويل وتصفية تدفقات البيانات
تعتبر عوامل RxJS هي جوهر المكتبة. إنها تتيح لك تحويل وتصفية ودمج ومعالجة Observables بطريقة تعريفية وقابلة للتركيب. هناك العديد من العوامل المتاحة، لكل منها غرض محدد. فيما يلي بعض العوامل الأكثر استخدامًا:
عوامل التحويل:
map()
: يطبق دالة على كل قيمة تصدرها Observable ويصدر النتيجة. على غرار طريقةmap()
في المصفوفات.pluck()
: يستخرج خاصية محددة من كل قيمة تصدرها Observable.scan()
: يطبق دالة تجميع على Observable المصدر ويعيد كل نتيجة وسيطة.buffer()
: يجمع القيم من Observable المصدر في مصفوفة ويصدر المصفوفة عند استيفاء شرط معين.window()
: على غرارbuffer()
، ولكن بدلاً من إصدار مصفوفة، فإنه يصدر Observable يمثل نافذة من القيم.
مثال: استخدام عامل map()
```javascript import { from } from 'rxjs'; import { map } from 'rxjs/operators'; const numbers = from([1, 2, 3, 4, 5]); const squaredNumbers = numbers.pipe( map(x => x * x) ); squaredNumbers.subscribe(value => console.log('Squared:', value)); // Output: // Squared: 1 // Squared: 4 // Squared: 9 // Squared: 16 // Squared: 25 ```
عوامل التصفية:
filter()
: يصدر فقط القيم التي تستوفي شرطًا معينًا.debounceTime()
: يؤخر إصدار القيم حتى مرور فترة زمنية معينة دون إصدار أي قيم جديدة. مفيد للتعامل مع إدخال المستخدم ومنع الطلبات المفرطة.distinctUntilChanged()
: يصدر فقط القيم المختلفة عن القيمة السابقة.take()
: يصدر فقط أول N قيمة من Observable.skip()
: يتخطى أول N قيمة من Observable ويصدر القيم المتبقية.
مثال: استخدام عامل filter()
```javascript import { from } from 'rxjs'; import { filter } from 'rxjs/operators'; const numbers = from([1, 2, 3, 4, 5, 6]); const evenNumbers = numbers.pipe( filter(x => x % 2 === 0) ); evenNumbers.subscribe(value => console.log('Even:', value)); // Output: // Even: 2 // Even: 4 // Even: 6 ```
عوامل الدمج:
merge()
: يدمج Observables متعددة في Observable واحد.concat()
: يربط Observables متعددة، ويصدر القيم من كل Observable بالتسلسل.combineLatest()
: يجمع أحدث القيم من Observables متعددة ويصدر قيمة جديدة كلما أصدر أي من Observables المصدر قيمة.zip()
: يجمع القيم من Observables متعددة بناءً على الفهرس الخاص بها ويصدر قيمة جديدة لكل مجموعة.withLatestFrom()
: يجمع أحدث قيمة من Observable آخر مع القيمة الحالية من Observable المصدر.
مثال: استخدام عامل combineLatest()
```javascript import { interval, combineLatest } from 'rxjs'; import { map } from 'rxjs/operators'; const interval1 = interval(1000); const interval2 = interval(2000); const combinedIntervals = combineLatest( interval1, interval2, (x, y) => `Interval 1: ${x}, Interval 2: ${y}` ); combinedIntervals.subscribe(value => console.log(value)); // Output (approximately): // Interval 1: 0, Interval 2: 0 // Interval 1: 1, Interval 2: 0 // Interval 1: 1, Interval 2: 1 // Interval 1: 2, Interval 2: 1 // Interval 1: 2, Interval 2: 2 // ... ```
أنماط RxJS الشائعة
توفر RxJS العديد من الأنماط القوية التي يمكن أن تبسط مهام البرمجة غير المتزامنة الشائعة:
Debouncing:
يتم استخدام عامل debounceTime()
لتأخير إصدار القيم حتى مرور فترة زمنية معينة دون إصدار أي قيم جديدة. هذا مفيد بشكل خاص للتعامل مع إدخال المستخدم، مثل استعلامات البحث أو عمليات إرسال النماذج، حيث تريد منع الطلبات المفرطة إلى الخادم.
مثال: Debouncing إدخال البحث
```javascript import { fromEvent } from 'rxjs'; import { map, debounceTime, distinctUntilChanged } from 'rxjs/operators'; const searchInput = document.getElementById('searchInput'); const searchObservable = fromEvent(searchInput, 'keyup').pipe( map((event: any) => event.target.value), debounceTime(300), // Wait 300ms after each key press distinctUntilChanged() // Only emit if the value has changed ); searchObservable.subscribe(searchTerm => { console.log('Searching for:', searchTerm); // Make an API request to search for the term }); ```
Throttling:
يحد عامل throttleTime()
من المعدل الذي يتم به إصدار القيم من Observable. يصدر القيمة الأولى الصادرة خلال نافذة زمنية محددة ويتجاهل القيم اللاحقة حتى يتم إغلاق النافذة. هذا مفيد للحد من تكرار الأحداث، مثل أحداث التمرير أو أحداث تغيير الحجم.
Switching:
يتم استخدام عامل switchMap()
للتبديل إلى Observable جديد كلما تم إصدار قيمة جديدة من Observable المصدر. هذا مفيد لإلغاء الطلبات المعلقة عند بدء طلب جديد. على سبيل المثال، يمكنك استخدام switchMap()
لإلغاء طلب بحث سابق عندما يكتب المستخدم حرفًا جديدًا في إدخال البحث.
مثال: استخدام switchMap()
للبحث بالإكمال التلقائي
```javascript import { fromEvent, of } from 'rxjs'; import { map, debounceTime, distinctUntilChanged, switchMap, catchError } from 'rxjs/operators'; const searchInput = document.getElementById('searchInput'); const searchObservable = fromEvent(searchInput, 'keyup').pipe( map((event: any) => event.target.value), debounceTime(300), distinctUntilChanged(), switchMap(searchTerm => { // Make an API request to search for the term return searchAPI(searchTerm).pipe( catchError(error => { console.error('Error searching:', error); return of([]); // Return an empty array on error }) ); }) ); searchObservable.subscribe(results => { console.log('Search results:', results); // Update the UI with the search results }); function searchAPI(searchTerm: string) { // Simulate an API request return of([`Result for ${searchTerm} 1`, `Result for ${searchTerm} 2`]); } ```
تطبيقات عملية لـ RxJS
RxJS هي مكتبة متعددة الاستخدامات يمكن استخدامها في مجموعة واسعة من التطبيقات. فيما يلي بعض حالات الاستخدام الشائعة:
- التعامل مع إدخال المستخدم: يمكن استخدام RxJS للتعامل مع أحداث إدخال المستخدم، مثل ضغطات المفاتيح ونقرات الماوس وعمليات إرسال النماذج. يمكن استخدام عوامل مثل
debounceTime()
وthrottleTime()
لتحسين الأداء ومنع الطلبات المفرطة. - إدارة العمليات غير المتزامنة: توفر RxJS طريقة قوية لإدارة العمليات غير المتزامنة، مثل طلبات الشبكة والمؤقتات. يمكن استخدام عوامل مثل
switchMap()
وmergeMap()
للتعامل مع الطلبات المتزامنة وإلغاء الطلبات المعلقة. - بناء تطبيقات في الوقت الفعلي: RxJS مناسب تمامًا لبناء تطبيقات في الوقت الفعلي، مثل تطبيقات الدردشة ولوحات المعلومات. يمكن استخدام Observables لتمثيل تدفقات البيانات من WebSockets أو Server-Sent Events (SSE).
- إدارة الحالة: يمكن استخدام RxJS كحل لإدارة الحالة في أطر عمل مثل Angular و React و Vue.js. يمكن استخدام Observables لتمثيل حالة التطبيق، ويمكن استخدام العوامل لتحويل وتحديث الحالة استجابةً لإجراءات المستخدم أو الأحداث.
RxJS مع الأطر الشائعة
Angular:
تعتمد Angular بشكل كبير على RxJS للتعامل مع العمليات غير المتزامنة وإدارة تدفقات البيانات. تُرجع خدمة HttpClient
في Angular Observables، وتُستخدم عوامل RxJS على نطاق واسع لتحويل وتصفية البيانات التي تم إرجاعها من طلبات API. تعتمد آلية اكتشاف التغييرات في Angular أيضًا على RxJS لتحديث واجهة المستخدم بكفاءة استجابةً لتغييرات البيانات.
مثال: استخدام RxJS مع HttpClient الخاص بـ Angular
```typescript
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class DataService {
private apiUrl = 'https://api.example.com/data';
constructor(private http: HttpClient) { }
getData(): Observable
React:
على الرغم من أن React لا تحتوي على دعم مضمن لـ RxJS، إلا أنه يمكن دمجها بسهولة باستخدام مكتبات مثل rxjs-hooks
أو use-rx
. توفر هذه المكتبات خطافات مخصصة تسمح لك بالاشتراك في Observables وإدارة الاشتراكات داخل مكونات React. يمكن استخدام RxJS في React للتعامل مع جلب البيانات غير المتزامن وإدارة حالة المكونات وبناء واجهات مستخدم تفاعلية.
مثال: استخدام RxJS مع React Hooks
```javascript import React, { useState, useEffect } from 'react'; import { Subject } from 'rxjs'; import { scan } from 'rxjs/operators'; function Counter() { const [count, setCount] = useState(0); const increment$ = new Subject(); useEffect(() => { const subscription = increment$.pipe( scan(acc => acc + 1, 0) ).subscribe(setCount); return () => subscription.unsubscribe(); }, []); return (
Count: {count}
Vue.js:
Vue.js أيضًا لا تحتوي على تكامل RxJS أصلي، ولكن يمكن استخدامها مع مكتبات مثل vue-rx
أو عن طريق إدارة الاشتراكات يدويًا داخل مكونات Vue. يمكن استخدام RxJS في Vue.js لأغراض مماثلة كما في React، مثل التعامل مع جلب البيانات غير المتزامن وإدارة حالة المكونات.
أفضل الممارسات لاستخدام RxJS
- إلغاء الاشتراك من Observables: قم دائمًا بإلغاء الاشتراك من Observables عندما لم تعد هناك حاجة إليها لمنع تسرب الذاكرة. استخدم كائن الاشتراك الذي يتم إرجاعه بواسطة طريقة
subscribe()
لإلغاء الاشتراك. - استخدم طريقة
pipe()
: استخدم طريقةpipe()
لربط العوامل معًا بطريقة قابلة للقراءة والصيانة. - التعامل مع الأخطاء بأمان: استخدم عامل
catchError()
للتعامل مع الأخطاء ومنعها من الانتشار في سلسلة Observable. - اختر العوامل المناسبة: حدد العوامل المناسبة لحالة الاستخدام المحددة الخاصة بك. توفر RxJS مجموعة كبيرة من العوامل، لذلك من المهم فهم الغرض والسلوك منها.
- حافظ على Observables بسيطة: تجنب إنشاء Observables معقدة للغاية. قسّم العمليات المعقدة إلى Observables أصغر وأكثر قابلية للإدارة.
مفاهيم RxJS المتقدمة
Subjects:
تعمل Subjects كـ Observables و Observers. إنها تتيح لك إرسال البيانات المتعددة إلى العديد من المشتركين وأيضًا دفع البيانات إلى الدفق. هناك أنواع مختلفة من Subjects، بما في ذلك:
- Subject: Subject أساسي يرسل قيمًا متعددة لجميع المشتركين.
- BehaviorSubject: يتطلب قيمة أولية ويصدر القيمة الحالية للمشتركين الجدد.
- ReplaySubject: يخزن عددًا محددًا من القيم ويعيد تشغيلها للمشتركين الجدد.
- AsyncSubject: يصدر القيمة الأخيرة فقط عند اكتمال Observable.
Schedulers:
تتحكم Schedulers في تزامن Observables. إنها تتيح لك تنفيذ التعليمات البرمجية بشكل متزامن أو غير متزامن، على سلاسل رسائل مختلفة، أو مع تأخيرات محددة. توفر RxJS العديد من المجدولات المضمنة، بما في ذلك:
queueScheduler
: يجدول المهام ليتم تنفيذها على سلسلة رسائل JavaScript الحالية، بعد سياق التنفيذ الحالي.asapScheduler
: يجدول المهام ليتم تنفيذها على سلسلة رسائل JavaScript الحالية، في أقرب وقت ممكن بعد سياق التنفيذ الحالي.asyncScheduler
: يجدول المهام ليتم تنفيذها بشكل غير متزامن، باستخدامsetTimeout
أوsetInterval
.animationFrameScheduler
: يجدول المهام ليتم تنفيذها في إطار الرسوم المتحركة التالي.
استنتاج
RxJS هي مكتبة قوية لبناء تطبيقات تفاعلية في JavaScript. من خلال إتقان Observables والعوامل والأنماط الشائعة، يمكنك إنشاء تطبيقات أكثر استجابة وقابلية للتطوير والصيانة. سواء كنت تعمل مع Angular أو React أو Vue.js أو JavaScript عادي، يمكن لـ RxJS تحسين قدرتك بشكل كبير على التعامل مع تدفقات البيانات غير المتزامنة وبناء واجهات مستخدم معقدة.
احتضن قوة البرمجة التفاعلية مع RxJS واطلق العنان لإمكانيات جديدة لتطبيقات JavaScript الخاصة بك!