برنامهنویسی واکنشی در جاوا اسکریپت را با RxJS کاوش کنید. جریانهای Observable، الگوها و کاربردهای عملی برای ساخت اپلیکیشنهای واکنشگرا و مقیاسپذیر را بیاموزید.
برنامهنویسی واکنشی جاوا اسکریپت: الگوهای RxJS و جریانهای Observable
در چشمانداز همواره در حال تحول توسعه وب مدرن، ساخت اپلیکیشنهای واکنشگرا، مقیاسپذیر و قابل نگهداری از اهمیت بالایی برخوردار است. برنامهنویسی واکنشی (RP) پارادایم قدرتمندی برای مدیریت جریانهای داده غیرهمزمان و انتشار تغییرات در سراسر اپلیکیشن شما فراهم میکند. در میان کتابخانههای محبوب برای پیادهسازی RP در جاوا اسکریپت، RxJS (Reactive Extensions for JavaScript) به عنوان یک ابزار قوی و همهکاره برجسته است.
برنامهنویسی واکنشی چیست؟
در هسته خود، برنامهنویسی واکنشی به مدیریت جریانهای داده غیرهمزمان و انتشار تغییرات میپردازد. یک صفحه گسترده (spreadsheet) را تصور کنید که در آن با بهروزرسانی یک سلول، فرمولهای مرتبط به طور خودکار دوباره محاسبه میشوند. این جوهره RP است – واکنش به تغییرات داده به شیوهای اعلانی و کارآمد.
برنامهنویسی دستوری سنتی اغلب شامل مدیریت وضعیت و بهروزرسانی دستی کامپوننتها در پاسخ به رویدادها است. این امر میتواند منجر به کدی پیچیده و مستعد خطا شود، به خصوص هنگام کار با عملیات غیرهمزمان مانند درخواستهای شبکه یا تعاملات کاربر. RP این فرآیند را با در نظر گرفتن همه چیز به عنوان یک جریان داده و ارائه اپراتورهایی برای تبدیل، فیلتر و ترکیب این جریانها، ساده میکند.
معرفی RxJS: افزونههای واکنشی برای جاوا اسکریپت
RxJS کتابخانهای برای ساخت برنامههای غیرهمزمان و مبتنی بر رویداد با استفاده از دنبالههای قابل مشاهده (observable sequences) است. این کتابخانه مجموعهای از اپراتورهای قدرتمند را فراهم میکند که به شما امکان میدهد جریانهای داده را به راحتی دستکاری کنید. RxJS بر اساس الگوی Observer، الگوی Iterator و مفاهیم برنامهنویسی تابعی برای مدیریت کارآمد دنبالههای رویدادها یا دادهها ساخته شده است.
مفاهیم کلیدی در RxJS:
- Observableها (Observables): نمایانگر یک جریان داده هستند که میتوانند توسط یک یا چند Observer مشاهده شوند. آنها تنبل (lazy) هستند و تنها زمانی شروع به انتشار مقادیر میکنند که در آنها اشتراک (subscribe) ایجاد شود.
- Observerها (Observers): دادههای منتشر شده توسط Observableها را مصرف میکنند. آنها سه متد دارند:
next()
برای دریافت مقادیر،error()
برای مدیریت خطاها، وcomplete()
برای اعلام پایان جریان. - اپراتورها (Operators): توابعی که Observableها را تبدیل، فیلتر، ترکیب یا دستکاری میکنند. RxJS مجموعه وسیعی از اپراتورها را برای اهداف مختلف فراهم میکند.
- Subjectها (Subjects): هم به عنوان Observable و هم به عنوان Observer عمل میکنند، که به شما امکان میدهد دادهها را به چندین مشترک (subscriber) ارسال کنید (multicast) و همچنین دادهها را به داخل جریان وارد کنید.
- Schedulerها (Schedulers): همزمانی (concurrency) Observableها را کنترل میکنند و به شما امکان میدهند کد را به صورت همزمان یا غیرهمزمان، در تردهای مختلف یا با تأخیرهای مشخص اجرا کنید.
جریانهای Observable به تفصیل
Observableها پایه و اساس RxJS هستند. آنها نمایانگر جریانی از داده هستند که میتوان در طول زمان مشاهده کرد. یک Observable مقادیر را برای مشترکین خود منتشر میکند، که سپس میتوانند آن مقادیر را پردازش کرده یا به آنها واکنش نشان دهند. آن را مانند یک خط لوله در نظر بگیرید که در آن دادهها از یک منبع به یک یا چند مصرفکننده جریان مییابند.
ایجاد Observableها:
RxJS چندین راه برای ایجاد Observableها فراهم میکند:
Observable.create()
: یک متد سطح پایین که به شما کنترل کامل بر رفتار Observable را میدهد.from()
: یک آرایه، promise، شیء تکرارپذیر (iterable) یا شیء شبه-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('دریافت شد:', value), error => console.error('خطا:', error), () => console.log('تکمیل شد') ); // خروجی: // دریافت شد: 1 // دریافت شد: 2 // دریافت شد: 3 // دریافت شد: 4 // دریافت شد: 5 // تکمیل شد ```
مثال: ساخت یک Observable از یک رویداد
```javascript import { fromEvent } from 'rxjs'; const button = document.getElementById('myButton'); const clickObservable = fromEvent(button, 'click'); clickObservable.subscribe( event => console.log('دکمه کلیک شد!', event) ); ```
اشتراک در Observableها:
برای شروع دریافت مقادیر از یک Observable، باید با استفاده از متد subscribe()
در آن مشترک شوید. متد subscribe()
حداکثر سه آرگومان را میپذیرد:
next
: تابعی که برای هر مقدار منتشر شده توسط Observable فراخوانی میشود.error
: تابعی که در صورت بروز خطا توسط Observable فراخوانی میشود.complete
: تابعی که هنگام تکمیل شدن Observable (اعلام پایان جریان) فراخوانی میشود.
متد subscribe()
یک شیء Subscription را برمیگرداند که نشاندهنده اتصال بین Observable و Observer است. شما میتوانید از شیء Subscription برای لغو اشتراک از Observable استفاده کنید و از انتشار مقادیر بیشتر جلوگیری کنید.
لغو اشتراک از Observableها:
لغو اشتراک برای جلوگیری از نشت حافظه (memory leaks) بسیار مهم است، به خصوص هنگام کار با Observableهای طولانیمدت یا Observableهایی که مقادیر را به طور مکرر منتشر میکنند. شما میتوانید با فراخوانی متد unsubscribe()
روی شیء Subscription، اشتراک خود را از یک Observable لغو کنید.
```javascript import { interval } from 'rxjs'; const myInterval = interval(1000); const subscription = myInterval.subscribe( value => console.log('بازه زمانی:', value) ); // پس از 5 ثانیه، لغو اشتراک setTimeout(() => { subscription.unsubscribe(); console.log('اشتراک لغو شد!'); }, 5000); // خروجی (تقریبی): // بازه زمانی: 0 // بازه زمانی: 1 // بازه زمانی: 2 // بازه زمانی: 3 // بازه زمانی: 4 // اشتراک لغو شد! ```
اپراتورهای RxJS: تبدیل و فیلتر کردن جریانهای داده
اپراتورهای RxJS قلب این کتابخانه هستند. آنها به شما امکان میدهند Observableها را به روشی اعلانی و قابل ترکیب، تبدیل، فیلتر، ترکیب و دستکاری کنید. اپراتورهای متعددی در دسترس هستند که هر کدام هدف خاصی را دنبال میکنند. در اینجا برخی از رایجترین اپراتورها آورده شده است:
اپراتورهای تبدیل (Transformation):
map()
: یک تابع را بر روی هر مقدار منتشر شده توسط Observable اعمال میکند و نتیجه را منتشر میکند. مشابه متدmap()
در آرایهها.pluck()
: یک ویژگی خاص را از هر مقدار منتشر شده توسط Observable استخراج میکند.scan()
: یک تابع انباشتگر (accumulator) را بر روی 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('مربع:', value)); // خروجی: // مربع: 1 // مربع: 4 // مربع: 9 // مربع: 16 // مربع: 25 ```
اپراتورهای فیلتر (Filtering):
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('زوج:', value)); // خروجی: // زوج: 2 // زوج: 4 // زوج: 6 ```
اپراتورهای ترکیب (Combination):
merge()
: چندین Observable را در یک Observable واحد ادغام میکند.concat()
: چندین Observable را به هم متصل میکند و مقادیر هر Observable را به ترتیب منتشر میکند.combineLatest()
: آخرین مقادیر از چندین Observable را ترکیب میکند و هرگاه هر یک از Observableهای منبع مقداری را منتشر کنند، یک مقدار جدید منتشر میکند.zip()
: مقادیر از چندین Observable را بر اساس ایندکس آنها ترکیب میکند و برای هر ترکیب یک مقدار جدید منتشر میکند.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) => `بازه ۱: ${x}, بازه ۲: ${y}` ); combinedIntervals.subscribe(value => console.log(value)); // خروجی (تقریبی): // بازه ۱: 0, بازه ۲: 0 // بازه ۱: 1, بازه ۲: 0 // بازه ۱: 1, بازه ۲: 1 // بازه ۱: 2, بازه ۲: 1 // بازه ۱: 2, بازه ۲: 2 // ... ```
الگوهای رایج RxJS
RxJS چندین الگوی قدرتمند ارائه میدهد که میتوانند وظایف رایج برنامهنویسی غیرهمزمان را ساده کنند:
Debouncing:
اپراتور debounceTime()
برای به تأخیر انداختن انتشار مقادیر تا زمانی که مقدار مشخصی از زمان بدون انتشار مقدار جدیدی گذشته باشد، استفاده میشود. این الگو به ویژه برای مدیریت ورودی کاربر، مانند جستجوها یا ارسال فرمها، مفید است، جایی که میخواهید از ارسال درخواستهای بیش از حد به سرور جلوگیری کنید.
مثال: Debounce کردن ورودی جستجو
```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), // ۳۰۰ میلیثانیه پس از هر فشار کلید صبر کن distinctUntilChanged() // فقط در صورتی منتشر کن که مقدار تغییر کرده باشد ); searchObservable.subscribe(searchTerm => { console.log('در حال جستجو برای:', searchTerm); // یک درخواست API برای جستجوی عبارت ارسال کن }); ```
Throttling:
اپراتور throttleTime()
نرخ انتشار مقادیر از یک Observable را محدود میکند. این اپراتور اولین مقدار منتشر شده در یک پنجره زمانی مشخص را منتشر میکند و مقادیر بعدی را تا بسته شدن پنجره نادیده میگیرد. این برای محدود کردن فرکانس رویدادها، مانند رویدادهای اسکرول یا تغییر اندازه، مفید است.
Switching:
اپراتور switchMap()
برای جابجایی به یک Observable جدید هر زمان که مقدار جدیدی از Observable منبع منتشر میشود، استفاده میشود. این برای لغو درخواستهای در حال انتظار هنگامی که درخواست جدیدی آغاز میشود، مفید است. به عنوان مثال، میتوانید از switchMap()
برای لغو درخواست جستجوی قبلی هنگامی که کاربر کاراکتر جدیدی را در ورودی جستجو تایپ میکند، استفاده کنید.
مثال: استفاده از switchMap()
برای جستجوی Typeahead
```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 => { // یک درخواست API برای جستجوی عبارت ارسال کن return searchAPI(searchTerm).pipe( catchError(error => { console.error('خطا در جستجو:', error); return of([]); // در صورت خطا یک آرایه خالی برگردان }) ); }) ); searchObservable.subscribe(results => { console.log('نتایج جستجو:', results); // UI را با نتایج جستجو بهروز کن }); function searchAPI(searchTerm: string) { // شبیهسازی یک درخواست API return of([`نتیجه برای ${searchTerm} ۱`, `نتیجه برای ${searchTerm} ۲`]); } ```
کاربردهای عملی RxJS
RxJS یک کتابخانه همهکاره است که میتواند در طیف گستردهای از برنامهها استفاده شود. در اینجا برخی از موارد استفاده رایج آورده شده است:
- مدیریت ورودی کاربر: RxJS میتواند برای مدیریت رویدادهای ورودی کاربر مانند فشردن کلیدها، کلیکهای ماوس و ارسال فرمها استفاده شود. اپراتورهایی مانند
debounceTime()
وthrottleTime()
میتوانند برای بهینهسازی عملکرد و جلوگیری از درخواستهای بیش از حد استفاده شوند. - مدیریت عملیات غیرهمزمان: RxJS روش قدرتمندی برای مدیریت عملیات غیرهمزمان مانند درخواستهای شبکه و تایمرها فراهم میکند. اپراتورهایی مانند
switchMap()
وmergeMap()
میتوانند برای مدیریت درخواستهای همزمان و لغو درخواستهای در حال انتظار استفاده شوند. - ساخت اپلیکیشنهای بلادرنگ (Real-Time): RxJS برای ساخت اپلیکیشنهای بلادرنگ مانند اپلیکیشنهای چت و داشبوردها بسیار مناسب است. Observableها میتوانند برای نمایش جریانهای داده از WebSockets یا Server-Sent Events (SSE) استفاده شوند.
- مدیریت وضعیت (State Management): RxJS میتواند به عنوان یک راه حل مدیریت وضعیت در فریمورکهایی مانند Angular، React و Vue.js استفاده شود. Observableها میتوانند برای نمایش وضعیت برنامه استفاده شوند و اپراتورها میتوانند برای تبدیل و بهروزرسانی وضعیت در پاسخ به اقدامات کاربر یا رویدادها استفاده شوند.
RxJS با فریمورکهای محبوب
Angular:
انگولار به شدت به RxJS برای مدیریت عملیات غیرهمزمان و جریانهای داده متکی است. سرویس HttpClient
در انگولار Observableها را برمیگرداند و اپراتورهای RxJS به طور گسترده برای تبدیل و فیلتر کردن دادههای بازگشتی از درخواستهای API استفاده میشوند. مکانیسم تشخیص تغییر (change detection) انگولار نیز از RxJS برای بهروزرسانی کارآمد UI در پاسخ به تغییرات داده بهره میبرد.
مثال: استفاده از RxJS با HttpClient انگولار
```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
ادغام کرد. این کتابخانهها هوکهای سفارشی فراهم میکنند که به شما امکان میدهند در Observableها مشترک شوید و اشتراکها را در کامپوننتهای React مدیریت کنید. RxJS میتواند در React برای مدیریت واکشی دادههای غیرهمزمان، مدیریت وضعیت کامپوننت و ساخت UIهای واکنشی استفاده شود.
مثال: استفاده از RxJS با هوکهای React
```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}
Vue.js:
Vue.js نیز ادغام بومی با RxJS ندارد، اما میتوان آن را با کتابخانههایی مانند vue-rx
یا با مدیریت دستی اشتراکها در کامپوننتهای Vue استفاده کرد. RxJS میتواند در Vue.js برای اهداف مشابهی مانند React، از جمله مدیریت واکشی دادههای غیرهمزمان و مدیریت وضعیت کامپوننت استفاده شود.
بهترین شیوهها برای استفاده از RxJS
- لغو اشتراک از Observableها: همیشه از Observableهایی که دیگر نیازی به آنها نیست لغو اشتراک کنید تا از نشت حافظه جلوگیری شود. از شیء Subscription بازگشتی از متد
subscribe()
برای لغو اشتراک استفاده کنید. - استفاده از متد
pipe()
: از متدpipe()
برای زنجیر کردن اپراتورها به روشی خوانا و قابل نگهداری استفاده کنید. - مدیریت خطاها به صورت صحیح: از اپراتور
catchError()
برای مدیریت خطاها و جلوگیری از انتشار آنها در زنجیره Observable استفاده کنید. - انتخاب اپراتورهای مناسب: اپراتورهای مناسب را برای مورد استفاده خاص خود انتخاب کنید. RxJS مجموعه وسیعی از اپراتورها را ارائه میدهد، بنابراین درک هدف و رفتار آنها مهم است.
- ساده نگه داشتن Observableها: از ایجاد Observableهای بیش از حد پیچیده خودداری کنید. عملیات پیچیده را به Observableهای کوچکتر و قابل مدیریتتر تقسیم کنید.
مفاهیم پیشرفته RxJS
Subjectها:
Subjectها هم به عنوان Observable و هم به عنوان Observer عمل میکنند. آنها به شما امکان میدهند دادهها را به چندین مشترک ارسال کنید (multicast) و همچنین دادهها را به داخل جریان وارد کنید. انواع مختلفی از Subjectها وجود دارد، از جمله:
- Subject: یک Subject پایه که مقادیر را به همه مشترکین ارسال میکند.
- BehaviorSubject: به یک مقدار اولیه نیاز دارد و مقدار فعلی را به مشترکین جدید منتشر میکند.
- ReplaySubject: تعداد مشخصی از مقادیر را بافر میکند و آنها را برای مشترکین جدید بازپخش میکند.
- AsyncSubject: فقط آخرین مقدار را هنگامی که Observable کامل میشود، منتشر میکند.
Schedulerها:
Schedulerها همزمانی Observableها را کنترل میکنند. آنها به شما امکان میدهند کد را به صورت همزمان یا غیرهمزمان، در تردهای مختلف یا با تأخیرهای مشخص اجرا کنید. RxJS چندین Scheduler داخلی ارائه میدهد، از جمله:
queueScheduler
: وظایف را برای اجرا در ترد جاوا اسکریپت فعلی، پس از زمینه اجرای فعلی، زمانبندی میکند.asapScheduler
: وظایف را برای اجرا در ترد جاوا اسکریپت فعلی، در اسرع وقت پس از زمینه اجرای فعلی، زمانبندی میکند.asyncScheduler
: وظایف را برای اجرای غیرهمزمان با استفاده ازsetTimeout
یاsetInterval
زمانبندی میکند.animationFrameScheduler
: وظایف را برای اجرا در فریم انیمیشن بعدی زمانبندی میکند.
نتیجهگیری
RxJS یک کتابخانه قدرتمند برای ساخت اپلیکیشنهای واکنشی در جاوا اسکریپت است. با تسلط بر Observableها، اپراتورها و الگوهای رایج، میتوانید اپلیکیشنهای واکنشگراتر، مقیاسپذیرتر و قابل نگهداریتری ایجاد کنید. چه با Angular، React، Vue.js یا جاوا اسکریپت خالص کار کنید، RxJS میتواند به طور قابل توجهی توانایی شما را در مدیریت جریانهای داده غیرهمزمان و ساخت UIهای پیچیده بهبود بخشد.
قدرت برنامهنویسی واکنشی را با RxJS در آغوش بگیرید و امکانات جدیدی را برای اپلیکیشنهای جاوا اسکریپت خود باز کنید!