ReactJS הפך לספריית קוד מוצלחת לבניית ממשקי משתמש דינמיים ורספונסיביים. אך, עם הגידול של היישומים, ניהול זרמי נתונים אסינכרוניים הופך לאתגר גדול יותר. בא RxJS, ספריית עוצמתית לתכנות ריאקטיבי באמצעות observables. אופרטורי RxJS פשוטים את טיפול בזרמי נתונים אסינכרוניים מורכבים, משפרים את הניהול והיעילות של הרכיבים שלך בריאקט.
במאמר זה, נחקור את אופרטורי RxJS בהקשר של ריאקט. נלך בדוגמאות שלב אחרי שלב, ונדגים כיצד לשלב את RxJS ביישומי ריאקט שלך. עד סיום המדריך, תהיה לך הבנה טובה של אופרטורי RxJS וכיצד הם יכולים לשפר את פרויקטי ריאקט שלך.
מהו RxJS?
RxJS, או "ספריית השפעות ריאקטיביות ל-JavaScript", היא ספריית שמאפשרת לך לעבוד עם זרמי נתונים אסינכרוניים באמצעות observables. Observable הוא אוסף שמתקבל לאורך זמן, שמאפשר לך להגיב לשינויים בנתונים בצורה יעילה.
אך למה להשתמש ב-RxJS בריאקט? ריאקט היא בבסיסה מצבית ועוסקת בעיבוד ממשק משתמש. לשלב את RxJS מאפשר לך לטפל בפעולות אסינכרוניות מורכבות כמו קריאות API, טיפול באירועים, וניהול מצבים בצורה יותר קלה וצפויה.
למה להשתמש ב-RxJS בריאקט?
טיפול משופר בפעולות אסינכרוניות
בריאקט, טיפול בפעולות אסינכרוניות כמו קריאות 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. ניצור אפליקציה פשוטה שמביאה נתונים מAPI ומציגה אותם ברשימה. נשתמש באופרטורי 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;
רכיב זה מאתחל משתני מצב עבור נתונים ושגיאה. הוא מציג רשימת נתונים שנמשכו מAPI ומטפל בשגיאות בצורה נאותה.
שלב 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 כְּדֵי לַשְׁלִיפָה מֵ API. הָאוֹפֵרָטוֹר map מְעַבֵּר אֶת הַתְּגִיבוּת, וְ catchError טוֹפֵל בְּכָל שְׁגִיאוֹת, מַחֲזִיר Observable שֶׁאֲנַחְנוּ יְכוֹלִים לַרְשׁוֹם אֵלָיו.
שְׁלִיפָה שֶׁל הַ 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 אוֹפֶרָטוֹרִים. לְדֵי דוֹגְמָא, נוּכַל לְהוֹסִיף מַצָב טַעַן וְלַעֲשׂוֹת דֶמֶב הַקְרוֹאִים לְ API כְּדֵי לְשַׁפֵּר אֶת הַבִּיצוּעַ.
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
מַבְטִיחַ שֶׁהַקְרוֹא לְ API נַעֲשֶׂה רַק לְאַחֲרֵי 500 מ' שֶׁל לֹא פְעִילוּת, מַפְחִית אֶת הַבִּקְשׁוֹת הַמְיוּחֲדוֹת. הָאוֹפֵרָטוֹר tap מְגַדֵּר אֶת מַצְבֵם שֶׁל הַטַעַן לִפְנֵי וְאַחַר הַקְרוֹא לְ API, סוֹפֵג הַפְּעוּת חִזוּק וְמַסְפֵּק מֵשׁוּאָה חָזוּתִית לַמַשְׁתַׁמֵשׁ.
אוֹפֶרָטוֹרִים רַגִילִים שֶׁל RxJS וּשְׁימוּתָם בָּ ReactJS
RxJS מַצִּיעַ מֵגַמָּה רְחָבָה שֶׁל אוֹפֶרָטוֹרִים שֶׁיֵּכוּלִים לִהֱיוֹת בְּאוּפְן יִיעָה בְּאִפְלִיקָצִיוֹת ReactJS. הִנֵּה מְעַט מֵהַאוֹפֶרָטוֹרִים הַנָפוּצִים וְאֵיך נוּכַל לַהִשְׁתַׁמֵשׁ בָּהֶם:
map
האופרטור map
מעביר ערך כלשהו שנשלח על ידי Observable. ב-ReactJS, ניתן להשתמש בו כדי לעצב נתונים לפני הצגתם בממשק המשתמש.
const transformedData$ = fetchData().pipe(
map(data => data.map(item => ({ item, fullName: `${item.name} (${item.username})` })))
);
filter
האופרטור filter
מאפשר לך לסנן ערכים שאינם עונים על קריטריונים מסוימים. זה שימושי להצגת נתונים רלוונטיים למשתמש בלבד.
const filteredData$ = fetchData().pipe(
filter(item => item.isActive)
);
debounceTime
debounceTime
מאחר בשידור של ערכים מ-Observable, ולכן זה מושלם לטיפול באירועי קלט של המשתמש כמו שאילתות חיפוש.
const searchInput$ = fromEvent(searchInput, 'input').pipe(
debounceTime(300),
map(event => event.target.value);
);
switchMap
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 מתמקדת בניהול זרמי נתונים אסינכרוניים באמצעות observables, בעוד ש־Redux היא ספריית ניהול מצב. ניתן להשתמש ב־RxJS עם Redux כדי לטפל בלוגיקה אסינכרונית מורכבת, אך הן משרתות מטרות שונות.
האם אני יכול להשתמש ב־RxJS עם רכיבים פונקציונליים?
כן, RxJS עובד בצורה חלקה עם רכיבים פונקציונליים של React. ניתן להשתמש בהוקס כמו useEffect
כדי להרשם לאובזרוובלים ולנהל אפקטים צדדיים.
האם 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 לכך.
- בדוק את האובזרוובלים באופן עצמאי כדי להבטיח אמינות.
סיכום
RxJS היא כלי עוצמתי לניהול נתונים אסינכרוניים ביישומי ReactJS. באמצעות אופרטורים של RxJS, תוכל לכתוב קוד נקי, יעיל וניתן לתחזוקה יותר. הבנת והחילוט של RxJS בפרויקטי ReactJS שלך ישפרו משמעותית את יכולתך לטפל בזרימות נתונים אסינכרוניות מורכבות, ולעשות את היישומים שלך נתמכים יותר.
Source:
https://dzone.com/articles/reactive-programming-in-react-with-rxjs