Bir nechta kuzatilishi mumkin bo'lgan oqimlar bilan ishlashda sizning BFF.

Hech shubha yo'qki, RxJS bugungi kunda veb-ishlab chiqishdagi eng issiq kutubxonalardan biri hisoblanadi. RxJS ishlatilmagan va yaxshi sabablarga ko'ra men ishtirok etgan loyiha yo'q. Ayniqsa, ko'payib borayotgan ramkalar, kutubxonalar va yordamchi dasturlarga yo'naltirilgan integratsiya nuqtalaridan foydalanish bilan. Bu hodisalar bilan shug'ullanish uchun kuchli va funktsional yondashuvni taklif qiladi. Men uchun RxJS va u nimani taklif qilishini yaxshi bilish aql bovar qilmaydigandek tuyuladi.

RxJS va reaktiv dasturlashni o'rganish qiyin bo'lishi mumkin, ayniqsa siz veb-ishlab chiqish uchun yangi bo'lsangiz. Ko'plab yangi kontseptsiyalar, katta API xizmati mavjud va keling, tafakkurning imperativdan deklarativ uslubga tubdan o'zgarishini unutmaylik. RxJlarga boshingizni o'rash qiyin bo'lishi mumkin bo'lsa-da, bilaman, bu men uchun edi, bu a***da og'riq bo'lishi shart emas.

Ushbu postda men eng keng tarqalgan transformator operatorlaridan biri ekanligini ko'rib chiqaman, transformatsiya operatori nima ekanligini tasvirlab beraman va unga mos keladigan ba'zi misollar keltiraman.

Transformatsiya operatorlari nima?

Transformatsiya operatorlari, nomidan ko'rinib turibdiki, kuzatilishi mumkin bo'lgan oqim orqali o'tayotganda qiymatlarni o'zgartirishga imkon beradi. Mening rivojlanishim uchun transformatsiya operatorlari API so'rovlari bilan ishlashda va kiruvchi ma'lumotlarni qayta ishlashda muhim rol o'ynadi. Siz bir nechta ichki obunalar bilan shug'ullanmaslik uchun ushbu operatorlarni kunni tejashni topasiz. Eng keng tarqalgan o'zgartirish operatorlari ma'lum bir kichik bo'limga to'g'ri keladi, ular tekislash operatorlari deb nomlanadi, masalan: mergeMap, concatMap, switchMap va exhaustMap.

Yassilash operatori nima?

RxJS bilan zamonaviy veb-ilovani yaratishda siz doimo duch kelishingiz mumkin bo'lgan umumiy foydalanish holati bu bir oqimdan boshqasiga uzatish zaruratidir.

Tasavvur qilaylik, bizda matn kiritishdan kuzatiladigan oqim bor va biz foydalanuvchi kiritgan ma'lumotlarni qayd qilamiz.

const textInput = document.getElementByID('search-box');
const input$ = fromEvent(textInput, 'keyup)
input$.pipe(
   map(event => event.target.value)
).subscribe(console.log)

Biz voqealar oqimini o'zgartirish va maqsadli qiymatni qaytarish uchun map operatoridan foydalanamiz. Biroq, biz hali ham asosiy qiymatni chiqarmoqdamiz. Haqiqiy dunyo ilovasida biz shunchaki qiymatni qayd qilmasligimiz mumkin, balki foydalanuvchi kiritishi asosida tarmoq so'rovini ishga tushirishimiz mumkin. Men endigina ish boshlaganimda, men obunalarni joylashtirish orqali bu muammoni hal qilardim, masalan:

const textInput = document.getElementByID('search-box');
const input$ = fromEvent(textInput, 'keyup)
input$.pipe(
   map(event => {
       const term = event.target.value;
       return ajax.getJSON(`https://api.github.com/users/${term}`)
      })
).subscribe(response => { 
     response.subscribe(console.log)
});

Garchi bu javob konsolga muvaffaqiyatli kirishi bilan ishlayotgan bo'lsa-da, bir necha sabablarga ko'ra bu haqiqatan ham ideal emas.

Birinchidan, ichki o'rnatilgan obunalar juda xunuk ko'rinishi mumkin, ular o'qish qobiliyatini yo'qotadi va kuzatish va disk raskadrovka qilish qiyin bo'lishi mumkin. Natijada sizda ikkinchi ichki obuna mavjud bo'lib, uni ko'rib chiqishingiz kerak.

Bunga qo'shimcha ravishda, siz hatto ushbu javobni o'zgartirishingiz kerak bo'lishi mumkin, natijada bir nechta kuzatiladigan obuna ichida joylashgan o'z operatorlari bilan kuzatiladigan bilan ishlashga to'g'ri keladi. Shunga o'xshash narsa:

input$.pipe(
   map(event => {
       const term = event.target.value;
       return ajax.getJSON(`https://api.github.com/users/${term}`)
      })
).subscribe(response => { 
      response.pipe(
         map(({login, id}) => { 
          return of(`Do not do this. This is bad.`);
         })
      ).subscribe(response2 => { 
                  response2.subscribe(console.log)
                 })
});

Bu qanday qilib bosh og'rig'iga aylana boshlaganini ko'rishingiz mumkin.

Bu bizning tekislash operatorlarimiz kunni tejash uchun keladi. Xulosa qilib aytganda: ular kuzatilishi mumkin bo'lgan narsalarni chiqaradi va faqat chiqarilgan kuzatilishi mumkin bo'lgan qiymatlarning kuzatilishini qaytaradi. Buni baland ovozda aytsam, bu juda chalkash tuyuladi, shuning uchun sizga eng to'g'ri tekislash operatoridan boshlab ko'rsataman: mergeAll.

input$.pipe(
   map(event => {
      const term = event.target.value;
      return ajax.getJSON(`https://api.github.com/users/${term}`)
   }), 
   mergeAll()
).subscribe(console.log);

mergeAll manbadan kuzatilishi mumkin bo'lgan narsani olganida, u kuzatilishi mumkin bo'lgan har qanday natijani chiqarib, ichki obuna bo'ladi. Bunday holda, u ajax kuzatilishi mumkin bo'lgan obuna bo'ladi va javobni ushbu oqimda chiqaradi. Ichki obuna bo'lish va qayta qo'ng'iroq qilish do'zaxini yaratishni boshlashning hojati yo'q, shunchaki mergeAll operatoridan foydalaning va unga buni siz uchun qilishiga ruxsat bering.

MergeMap operatorini tushunish

Eng keng tarqalgan tekislash operatori (mening fikrimcha) mergeMap operatori bo'lishi kerak. Keling, yuqoridagi misolni olaylik va uni yanada tozalaymiz. Shunday qilib, map dan foydalanish o'rniga, keyin mergeAll. Biz ikkalasini birlashtiramiz va mergeMap dan foydalanamiz:

input$.pipe(
   mergeMap(event => {
      const term = event.target.value;
      return ajax.getJSON(`https://api.github.com/users/${term}`)
   }), 
).subscribe(console.log);

Endi bizda xuddi avvalgidek xatti-harakatlar mavjud, lekin ikkita o'rniga bitta operator bilan. Ishonchim komilki, endi siz transformator operatorlarining kuchini va ular kuzatiladigan oqimlaringizni qanday yaxshilashini ko'rishingiz mumkin.

Keling, boshqa operatorga o'tamiz, switchMap. mergeMap va switchMap o'rtasidagi farqlarni tushunish uchun mergeMap operatori qayerda etishmayotganini va qayerda switchMap yaxshiroq tanlov bo'lishi mumkinligini ko'ring.

SwitchMap operatori haqida tushuncha

switchMap operatori har bir qiymatni kuzatilishi mumkin bo'lgan qiymatga moslashtiradi va keyin uni tekislaydi. mergeMap dan farqli o'laroq, switchMap bir vaqtning o'zida faqat bitta faol ichki obunani saqlaydi.

Bu shuni anglatadiki, har qanday vaqtda biz kuzatilishi mumkin bo'lgan yangi ichki ko'rinishga xaritada oldingisi tugallanadi.

switchMap ni noyob qiladigan narsa kuzatilishi mumkin bo'lgan manbadan keyingi chiqarilgan qiymatda ko'rinadi. Shu nuqtada oldingi faol ichki kuzatilishi tugallanadi va keyingi qaytarilgan kuzatilishiga o'tadi.

switchMap mergeMap dan qanday farq qilishini tushunish uchun yangi misoldan boshlaylik. Ushbu misolda bizda har bir bosishda ichki intervalga mos keladigan bosish hodisalari oqimi mavjud.

const interval$ = interval(1000);
const click$ = fromEvent(document, 'click');
click$.pipe(
    mergeMap(() => interval$)
).subscribe(console.log)

Birinchi marta bosish orqali interval yaratiladi. Ikkinchi bosishda ikkinchi interval yaratiladi, birinchi interval qoladi. Agar boshqa bosish bo'lsa; uchinchi interval yaratiladi. Konsol quyidagicha ko'rinadi:

0 1 2 3 0 4 1 5 2 6 0 3 7 1

Endi mergeMap ni switchMap ga almashtiramiz.

Xulq-atvor boshqacha bo'lishini ko'rasiz.

Birinchi marta bosish orqali interval xuddi mergeMap kabi ishlay boshlaydi, lekin ikkinchi marta bosish bo'lganda, ikkinchi ichki obuna yaratilayotganda, siz intervalni qayta ishga tushirishni topasiz:

click$.pipe(
    switchMap(() => interval$)
).subscribe(console.log)
// outcome: 0 1 2 3 0 1 2 0 1 2 3

Qayta ishga tushirish kuzatilishi mumkin bo'lgan manbadan har bir qoldiqda sodir bo'ladi (klik $), yangi oraliq xaritalanadi, keyin obuna bo'ladi, so'ngra avval faol obuna tugallanadi.

Endi biz switchMap operatorining xatti-harakatlarini yaxshiroq tushundik, keling, haqiqiy misolni ko'rib chiqaylik:

const searchInput = document.getElementByID('search-box');
const input$ = fromEvent(searchInput, 'keyup')
input$.pipe(
  debounceTime(200),
  pluck('target', 'value'),
  distinctUntilChanged()
).subscribe(console.log)

Bizning birinchi misolimizga o'xshab, bizda qidiruv kiritishdan yana bir "keyup" hodisalari oqimi mavjud.

Biz 200 ms pauzadan so'ng oxirgi qiymatni chiqarish uchun avval debounceTime operatori bilan oqimni o'chiramiz. Keyin biz maqsadli qiymatning nest xususiyatini tanlash uchun pluck operatoridan foydalanamiz, so'ngra faqat noyob qiymatlar chiqarilishini ta'minlash uchun distinctUntilChanged() operatorini qo'llaymiz. Shunday qilib, foydalanuvchi qidiruv kiritishni kiritganda, foydalanuvchi 200 ms yoki undan ko'proq pauza qilgandan so'ng, qiymat konsolga chiqariladi.

Ushbu funksiyani tugatish uchun switchMap qo'shamiz:

input$.pipe(
  debounceTime(200),
  pluck('target', 'value'),
  distinctUntilChanged(), 
  switchMap(searchTerm => { 
     return ajax.getJSON(`http://api.som.org/${searchTerm}`)
  })
).subscribe(console.log)

Bu biz kirishdan olingan har qanday qidiruv so'zini ajax kuzatilishi mumkin bo'lgan xaritalash, uni switchMap operatori bilan tekislash va keyin javobni chiqaradi.

Nima uchun bu funksiya uchun switchMap dan mergeMap dan foydalanish kerak, deb soʻraganingizni eshitdim.

mergeMap bilan siz serverga bir nechta javob olasiz va javoblar ishlamay qolishi mumkin, bu esa yomon ishlash va sub-optimal foydalanuvchi tajribasiga teng bo'ladi. Siz serverga bir nechta keraksiz qo'ng'iroqlarni amalga oshirasiz 👎

switchMap operatori bilan u oldingi ichki kuzatiladiganni, shu jumladan yangilangan qiymat chiqarilsa, so'rovni bekor qilganda yangi kuzatiladiganga o'tadi.

switchMap operatori tekislash uchun eng xavfsiz standart sifatida ko'riladi, u bekor qilinishi mumkin bo'lgan HTTP so'rovlari uchun juda foydali va dam olish, pauza qilish va davom ettirish uchun juda yaxshi.

Ma'lumotnomalar: Kod misollarining ba'zilari Ultimate Courses tomonidan RxJs Basic kursidan olingan.