ReactJS أصبحت مكتبة رئيسية لبناء واجهات مستخدم ديناميكية ومتجاوبة. ومع ذلك، كلما نمت التطبيقات، كلما أصبح إدارة تيارات البيانات اللازمة تصبح أكثر تحديًا. يأتي RxJS، وهي مكتبة قوية للبرمجة الردية باستخدام observables. تبسط مشغلات RxJS التعامل مع تدفقات البيانات اللازمة، مما يجعل مكونات React الخاصة بك أكثر إدارة وكفاءة.
في هذه المقالة، سنستكشف مشغلات RxJS في سياق ReactJS. سنقدم أمثلة خطوة بخطوة، توضح كيفية دمج RxJS في تطبيقات React الخاصة بك. بحلول نهاية هذا الدليل، ستكون قد فهمت تمامًا مشغلات RxJS وكيف يمكن أن تعزز مشاريع ReactJS الخاصة بك.
ما هو RxJS؟
RxJS، أو ملحقات البرمجة الردية لـ JavaScript، هي مكتبة تسمح لك بالعمل مع تيارات البيانات اللازمة باستخدام observables. Observables هي مجموعة تصل مع مرور الوقت، مما يمكنك من رد الفعل على التغييرات في البيانات بكفاءة.
لكن لماذا استخدام RxJS في ReactJS؟ ReactJS هو عرضي بطبيعته ويتعامل مع عرض واجهة المستخدم. مع RxJS، يمكنك التعامل بسهولة وتنبؤية أكبر مع العمليات اللازمة الردية مثل استدعاءات API، ومعالجة الأحداث، وإدارة الحالة.
لماذا يجب عليك استخدام RxJS في ReactJS؟
تحسين التعامل الردي
في ReactJS، التعامل مع العمليات الردية مثل استدعاءات API أو أحداث المستخدم يمكن أن يصبح صعبًا. تسمح لك مشغلات RxJS مثل map وfilter وdebounceTime بإدارة هذه العمليات بشكل أنيق، وتحويل تدفقات البيانات أثناء تدفقها من خلال تطبيقك.
رمز نظيف وأكثر قراءة
يعزز RxJS نهج البرمجة الوظيفية، مما يجعل رمزك أكثر إيضاحًا. بدلاً من إدارة تغييرات الحالة والآثار الجانبية يدويًا، يمكنك الاستفادة من المشغلات في RxJS للتعامل مع هذه المهام بإيجاز.
تحسين معالجة الأخطاء
يوفر RxJS آليات قوية لمعالجة الأخطاء، مما يتيح لك إدارة الأخطاء بسلاسة في العمليات الغير متزامنة. يمكن للمشغلات مثل catchError و retry استعادة الأخطاء تلقائيًا دون إثقال رمزك بكتل try-catch.
إعداد RxJS في مشروع ReactJS
قبل الانغماس في الرمز، دعنا نقوم بإعداد مشروع ReactJS أساسي مع RxJS مثبت.
npx create-react-app rxjs-react-example
cd rxjs-react-example
npm install rxjs
بمجرد تثبيت RxJS، أنت الآن جاهز لبدء دمجه في مكونات React الخاصة بك.
مثال خطوة بخطوة
لنلخص مثالًا مفصلاً عن استخدام RxJS في تطبيق ReactJS. سنقوم بإنشاء تطبيق بسيط يستعين ببيانات من واجهة برمجة تطبيقات ويعرضها في قائمة. سنستخدم المشغلات في RxJS للتعامل بكفاءة مع تيار البيانات غير المتزامن.
الخطوة 1: إنشاء مكون React بسيط
أولاً، قم بإنشاء مكون جديد يسمى DataFetcher.js
:
import React, { useEffect, useState } from 'react';
const DataFetcher = () => {
const [data, setData] = useState([]);
const [error, setError] = useState(null);
return (
<div>
<h1>Data Fetcher</h1>
{error && <p>Error: {error}</p>}
<ul>
{data.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
};
export default DataFetcher;
يهيئ هذا المكون متغيرات الحالة للبيانات والأخطاء. يقوم بعرض قائمة البيانات المحملة من واجهة برمجة التطبيقات ويتعامل بسلاسة مع الأخطاء.
الخطوة 2: استيراد RxJS وإنشاء Observable
الخطوة التالية هي استيراد RxJS وإنشاء Observable لجلب البيانات. في نفس الملف DataFetcher.js
، قم بتعديل المكون ليتضمن ما يلي:
import { of, from } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { ajax } from 'rxjs/ajax';
const fetchData = () => {
return ajax.getJSON('https://jsonplaceholder.typicode.com/users').pipe(
map(response => response),
catchError(error => of({ error: true, message: error.message }))
);
};
هنا، نستخدم الطريقة ajax.getJSON
من RxJS لجلب البيانات من واجهة برمجة التطبيقات. يقوم المشغل map بتحويل الاستجابة، ويتعامل catchError مع أي أخطاء، مما يعيد Observable يمكننا الاشتراك به.
الخطوة 3: الاشتراك في Observable في useEffect
الآن، سنستخدم خطاف useEffect للاشتراك في Observable وتحديث حالة المكون وفقًا لذلك:
useEffect(() => {
const subscription = fetchData().subscribe({
next: (result) => {
if (result.error) {
setError(result.message);
} else {
setData(result);
}
},
error: (err) => setError(err.message),
});
return () => subscription.unsubscribe();
}, []);
يقوم هذا الكود بالاشتراك في Observable fetchData
. إذا أدى Observable إلى خطأ، يقوم بتحديث حالة الخطأ؛ وإلا، يقوم بتحديث حالة البيانات. يتم تنظيف الاشتراك عند تفكيك المكون لمنع تسرب الذاكرة.
الخطوة 4: تعزيز عملية جلب البيانات
الآن بعد أن لدينا تنفيذ أساسي، دعنا نعززه باستخدام مشغلات RxJS إضافية. على سبيل المثال، يمكننا إضافة حالة تحميل وتأخير مكالمات واجهة برمجة التطبيقات لتحسين الأداء.
import {
debounceTime,
tap
} from 'rxjs/operators';
const fetchData = () => {
return ajax.getJSON('https://jsonplaceholder.typicode.com/users').pipe(
debounceTime(500),
tap(() => setLoading(true)),
map(response => response),
catchError(error => of({
error: true,
message: error.message
})),
tap(() => setLoading(false))
);
};
في هذا الإصدار المحسن، يضمن debounceTime
أن مكالمة واجهة البرمجة تتم فقط بعد 500 مللي ثانية من الخمول، مما يقلل من الطلبات غير الضرورية. المشغل tap يضبط حالة التحميل قبل وبعد مكالمة واجهة البرمجة، مما يوفر ردود فعل بصرية للمستخدم.
المشغلات الشائعة في RxJS واستخدامها في ReactJS
يقدم RxJS مجموعة واسعة من المشغلات التي يمكن أن تكون مفيدة بشكل لا يصدق في تطبيقات ReactJS. إليك بعض المشغلات الشائعة وكيف يمكن استخدامها:
map
يُحول مشغل map
كل قيمة يتم إرسالها بواسطة observable. في ReactJS، يمكن استخدامه لتنسيق البيانات قبل عرضها في واجهة المستخدم.
const transformedData$ = fetchData().pipe(
map(data => data.map(item => ({ item, fullName: `${item.name} (${item.username})` })))
);
التصفية
يتيح مشغل filter
لك تصفية القيم التي لا تفي بمعايير معينة. هذا مفيد لعرض البيانات ذات الصلة فقط للمستخدم.
const filteredData$ = fetchData().pipe(
filter(item => item.isActive)
);
تأخير الوقت
debounceTime
يؤخر إرسال القيم من observable، مما يجعله مثاليًا لمعالجة أحداث إدخال المستخدم مثل طلبات البحث.
const searchInput$ = fromEvent(searchInput, 'input').pipe(
debounceTime(300),
map(event => event.target.value);
);
تبديل الخريطة
switchMap
مثالي لمعالجة السيناريوهات التي يهم فقط آخر نتيجة من observable، مثل اقتراحات الإكمال التلقائي.
const autocomplete$ = searchInput$.pipe(
switchMap(query => ajax.getJSON(`/api/search?q=${query}`))
);
التكامل المتقدم بين RxJS وReactJS: استغلال المزيد من المشغلين والأنماط
دمج Observables بواسطة merge
في بعض الأحيان، تحتاج إلى معالجة تدفقات متزامنة متعددة. يسمح لك مشغل merge
بدمج متعدد من observables في observable واحد، مرسلًا القيم من كل منها بمجرد وصولها.
import {
merge,
of,
interval
} from 'rxjs';
import {
map
} from 'rxjs/operators';
const observable1 = interval(1000).pipe(map(val => `Stream 1: ${val}`));
const observable2 = interval(1500).pipe(map(val => `Stream 2: ${val}`));
const combined$ = merge(observable1, observable2);
useEffect(() => {
const subscription = combined$.subscribe(value => {
console.log(value); // Logs values from both streams as they arrive
});
return () => subscription.unsubscribe();
}, []);
في تطبيق React، يمكنك استخدام merge للاستماع بشكل متزامن إلى أحداث متعددة أو استدعاءات API ومعالجتها بطريقة موحدة.
تيارات البيانات الحية بالوقت الحقيقي باستخدام interval و scan
بالنسبة للتطبيقات التي تتطلب تحديثات في الوقت الحقيقي، مثل أسعار الأسهم أو لوحات المعلومات المباشرة، يمكن ل RxJS إنشاء ومعالجة التدفقات بفعالية.
import { interval } from 'rxjs';
import { scan } from 'rxjs/operators';
const ticker$ = interval(1000).pipe(
scan(count => count + 1, 0)
);
useEffect(() => {
const subscription = ticker$.subscribe(count => {
console.log(`Tick: ${count}`); // Logs ticks every second
});
return () => subscription.unsubscribe();
}, []);
في هذا المثال، يعمل scan
مثل مقلل الحجم، يحتفظ بحالة تراكمية عبر الإرسالات.
معالجة متقدمة لإدخال المستخدم باستخدام combineLatest
بالنسبة للنماذج المعقدة أو السيناريوهات حيث تتفاعل حقول الإدخال المتعددة، فإن عامل combineLatest
لا يقدر بثمن.
import {
fromEvent,
combineLatest
} from 'rxjs';
import {
map
} from 'rxjs/operators';
const emailInput = document.getElementById('email');
const passwordInput = document.getElementById('password');
const email$ = fromEvent(emailInput, 'input').pipe(
map(event => event.target.value)
);
const password$ = fromEvent(passwordInput, 'input').pipe(
map(event => event.target.value)
);
const form$ = combineLatest([email$, password$]).pipe(
map(([email, password]) => ({
email,
password
}))
);
useEffect(() => {
const subscription = form$.subscribe(formData => {
console.log('Form Data:', formData);
});
return () => subscription.unsubscribe();
}, []);
هذا المثال يستمع إلى حقول إدخال متعددة ويصدر أحدث القيم معًا، مما يبسط إدارة حالة النموذج.
منطق إعادة المحاولة مع retryWhen و delay
في السيناريوهات التي تكون فيها موثوقية الشبكة مشكلة، يمكن أن تساعد RxJS في تنفيذ آليات إعادة المحاولة مع تأخير أسي.
import {
ajax
} from 'rxjs/ajax';
import {
retryWhen,
delay,
scan
} from 'rxjs/operators';
const fetchData = () => {
return ajax.getJSON('https://api.example.com/data').pipe(
retryWhen(errors =>
errors.pipe(
scan((retryCount, err) => {
if (retryCount >= 3) throw err;
return retryCount + 1;
}, 0),
delay(2000)
)
)
);
};
useEffect(() => {
const subscription = fetchData().subscribe({
next: data => setData(data),
error: err => setError(err.message)
});
return () => subscription.unsubscribe();
}, []);
تقوم هذه الطريقة بإعادة محاولة استدعاء API حتى ثلاث مرات، مع تأخير بين المحاولات، مما يحسن تجربة المستخدم أثناء الفشل المؤقت.
مؤشرات التحميل مع startWith
لتوفير تجربة مستخدم سلسة، يمكنك عرض مؤشر تحميل حتى تتوفر البيانات باستخدام عامل startWith
.
import {
ajax
} from 'rxjs/ajax';
import {
startWith
} from 'rxjs/operators';
const fetchData = () => {
return ajax.getJSON('https://jsonplaceholder.typicode.com/users').pipe(
startWith([]) // Emit an empty array initially
);
};
useEffect(() => {
const subscription = fetchData().subscribe(data => {
setData(data);
});
return () => subscription.unsubscribe();
}, []);
يضمن ذلك أن واجهة المستخدم تعرض عنصر نائب أو دوّار حتى يتم تحميل البيانات.
طلبات قابلة للإلغاء مع takeUntil
يعد التعامل مع تنظيف العمليات غير المتزامنة أمرًا حاسمًا، خاصةً في الاستعلامات الديناميكية أو البحث. يساعد عامل takeUntil
في إلغاء الملاحظات.
import {
Subject
} from 'rxjs';
import {
ajax
} from 'rxjs/ajax';
import {
debounceTime,
switchMap,
takeUntil
} from 'rxjs/operators';
const search$ = new Subject();
const cancel$ = new Subject();
const searchObservable = search$.pipe(
debounceTime(300),
switchMap(query =>
ajax.getJSON(`https://api.example.com/search?q=${query}`).pipe(
takeUntil(cancel$)
)
)
);
useEffect(() => {
const subscription = searchObservable.subscribe(data => {
setData(data);
});
return () => cancel$.next(); // Cancel ongoing requests on unmount
}, []);
const handleSearch = (query) => search$.next(query);
هنا، يضمن takeUntil إلغاء أي استدعاءات API جارية عندما يتم إدخال استعلام جديد، أو عند إزالة المكون.
الأسئلة الشائعة
ما الفرق بين RxJS و Redux؟
ترتكز RxJS على إدارة تدفقات البيانات غير المتزامنة باستخدام الملاحظات، بينما Redux هو مكتبة لإدارة الحالة. يمكن استخدام RxJS مع Redux للتعامل مع المنطق غير المتزامن المعقد، لكنهما يخدمان أغراضًا مختلفة.
هل يمكنني استخدام RxJS مع المكونات الوظيفية؟
نعم، يعمل RxJS بسلاسة مع المكونات الوظيفية في React. يمكنك استخدام الهوكس مثل useEffect
للاشتراك في observables وإدارة الآثار الجانبية.
هل RxJS زيادة غير ضرورية لمشاريع React الصغيرة؟
قد يبدو RxJS زيادة غير ضرورية للمشاريع الصغيرة. ومع ذلك، عندما تنمو مشروعك وتحتاج إلى التعامل مع تدفقات بيانات غير متزامنة معقدة، يمكن لـ RxJS تبسيط كودك وجعله أكثر صيانة.
كيف يمكنني تصحيح RxJS في ReactJS؟
يمكن تصحيح رمز RxJS باستخدام أدوات مثل Redux DevTools أو عوامل التسجيل الخاصة بـ RxJS مثل tap
لفحص القيم المرسلة في مراحل مختلفة.
كيف يمكنني تحسين لأحداث عالية التردد؟
العوامل مثل throttleTime
و auditTime
مثالية للتعامل مع أحداث عالية التردد مثل التمرير أو التغيير في الحجم.
هل يمكن لـ RxJS استبدال مكتبات إدارة الحالة في React؟
RxJS ليس حلاً لإدارة الحالة ولكن يمكن أن يكمل مكتبات مثل Redux للتعامل مع منطق الأسنك المعقد. للمشاريع الأصغر، يمكن لـ RxJS بـ BehaviorSubject
أحيانًا استبدال مكتبات إدارة الحالة.
ما هي أفضل الممارسات لـ RxJS في ReactJS؟
- استخدم
takeUntil
للتنظيف فيuseEffect
لتجنب تسرب الذاكرة. - تجنب استخدام RxJS بشكل مفرط لتحديثات الحالة المتزامنة البسيطة؛ تفضل بأدوات React المدمجة لذلك.
- اختبر الobservables بشكل مستقل لضمان الموثوقية.
الختام
RxJS هي أداة قوية لإدارة البيانات الغير متزامنة في تطبيقات ReactJS. من خلال مشغلات RxJS، يمكنك كتابة كود أنظف وأكثر كفاءة وصيانة. فهم وتطبيق RxJS في مشاريع ReactJS الخاصة بك سيعزز بشكل كبير قدرتك على التعامل مع تدفقات البيانات الغير متزامنة المعقدة، مما يجعل تطبيقاتك أكثر قابلية للتوسيع.
Source:
https://dzone.com/articles/reactive-programming-in-react-with-rxjs