1. Kirish
Vizend Dock foydalanuvchining autentifikatsiya ma'lumotlarini va joriy ish maydoni ma'lumotlarini boshqaradigan frontend umumiy modulidir. Kirishdan so'ng beriladigan access token va refresh tokenlardan tashqari pavilion, cineroom, stage, actor kabi ish kontekstlarini saqlaydi va API so'rovlarida zarur bo'lgan autentifikatsiya sarlavhalarini va Dock kontekstini interceptor orqali uzatadi. Shunday qilib, Dockning holati noto'g'ri bo'lsa, bu faqat ekranning bitta joyi xato ko'rinishda qolishi bilan cheklanmaydi. Kirish holati, huquqlarni aniqlash, ekran siljishi, API so'rov kontekstlari birgalikda ta'sir qiladi.
Oldingi Dock faqat veb ilovasi uchun yozilgan edi. window, localStorage, sessionStorage, BroadcastChannel kabi brauzer API'larini bir nechta atom, hook, interceptor to'g'ridan-to'g'ri ishlatdi. Vebda ishga tushganda to'g'ri ishladi, lekin mobilya ilovasiga Dockni qo'llashga harakat qilganda bu shart darhol muammo bo'lib qoldi. React Native muhitida brauzerning window.localStorage va window.sessionStorage mavjud emas.
Vebda ham oldingi foydalanuvchining Dock holati qolishi yoki storage qiymati o'zgargan, lekin hooklar darhol yangilanmay, faqat yangilanishdan so'ng aks etadigan muammolar mavjud edi. Autentifikatsiya atom, Dock atom, haqiqiy storage har biri o'z holatini olib yurgani uchun o'zgarish yo'li bo'yicha faqat ba'zilari yangilanishi mumkin edi.
Mobil qo'llash tartibi bo'yicha uzoq muddatli qayta loyihalash mumkin emasdi. Avvalambor saqlash joyini kiritishingiz uchun vebga bog'lanishni uzib, keyinchalik holatning muvofiq nuqtasini storage birida birlashtirib, Observer naqshiga ko'ra React hookini sinxronlashtirdik. Ushbu maqolada ko'rsatilgan qayta loyihalashning fonisi va haqiqiy qo'llash jarayoni tushuntiriladi.
2. Oldingi tuzilmada yuzaga kelgan muammolar
2.1 Brauzer saqlash joyiga nisbatan kuchli bog'lanish
Qayta loyihalashdan oldin autentifikatsiya holati atomni e'lon qilgan paytda brauzer saqlash joyini to'g'ridan-to'g'ri o'qidi. Dock holati ham modul yuklaganda window.sessionStorage'dan qiymatni olib atomning dastlabki qiymati sifatida belgilandi.
export const accessTokenAtom = atom(
sessionStorage.getItem(accessTokenKey) ?? '',
);
const load = () => {
const session =
window?.sessionStorage.getItem(dockSessionKey) ?? '{}';
const context =
window?.sessionStorage.getItem(dockContextKey) ?? '{}';
return {
session: JSON.parse(session),
context: JSON.parse(context),
};
};
Ushbu kod saqlash texnologiyasi va holatni boshqarish kodlari ajratilmagan. Brauzerda Storage implementatsiyasi allaqachon berilgan, ammo React Native'da ilova foydalanishi kerak bo'lgan saqlash joyini tanlashi kerak. Mobil saqlash joyini qo'shish uchun atom, hook, interceptor ichida tarqalgan to'g'ridan-to'g'ri havolalarni topib, tuzatish kerak edi. Testda xotira saqlash joyini qo'shish ham oson bo'lmadi.
Saqlash joyi bilan bog'liq siyosatlar ham bir nechta joylarga tarqalgan edi. access token sessiya saqlash joyida, refresh token va eslaydigan qiymatlar esa lokal saqlash joyida saqlanishi kerak edi, bu qoidalar atom va interceptor'da birma-bir amalga oshirilgandi.
2.2 Atom va storage har biri holatni saqlovchi ikki baravarlik
Oldingi tuzilma bo'yicha Jotai atomi Reactning reaktiv holatini iste'mol edi, storage esa doimiy holatni boshqarardi. Muammo shundaki, barcha o'zgarishlar majburiy ravishda atomni bosib o'tmasdi. Hook atomni yangilasa-da, interceptor so'rovlarni qayta ishlash vaqtida storage'ni to'g'ridan-to'g'ri o'zgartira olardi. Aksincha, atomning qiymati o'zgargan bo'lsa-da, storage'ga aks ettirish vaqti yoki boshlanishi farq qilishi mumkin edi.
Brauzer saqlash voqealari bilan bu muammoni hal qilish ham qiyin bo'ldi. Bir xil hujjatda bajarilgan storage o'zgarishi, o'sha hujjatning storage voqealarini keltirib chiqarmaydi, mobil saqlash joyida esa brauzer voqealari mavjud emas. Natijada “qiymatlarni saqlovchi kod” bilan “React komponentini qayta chizish kod” o'rtasida aniq bog'lanish zarurati paydo bo'ldi.
-
Kirish yoki token yangilanishidan so'ng storage qiymati o'zgardi, lekin hook oldingi qiymatni ushlab qoldi.
-
Chiqish jarayonida autentifikatsiya tokeni faqat o'chiriladi, Dock konteksti yoki logbookning bir qismi qolib, kelgusi foydalanuvchiga oldingi holat kabi ko'rinishi mumkin edi.
-
atomni boshlash tartibi va interceptionni boshlash tartibi farq qilgani sababli birinchi kirishdan holatni to'g'ri o'qib bo'lmasligi mumkin edi.
-
hook to'g'ridan-to'g'ri o'qiydigan qiymatlar va interceptor to'g'ridan-to'g'ri o'qiydigan qiymatlar manbalari farq qilgani uchun muammoni takrorlash va xatolarni tuzatish qiyin bo'ldi.
2.3 Sozlamalar mas'uliyatining parchalanganligi
Mobil qurilmalarda storage'dan tashqari veb uchun maxsus harakatlarni almashtirish kerak edi. Masalan, window.alert va window.location'ni to'g'ridan-to'g'ri chaqira olmaymiz, shuning uchun xabarlar va ekran ko'chirish funksiyalarini mobil dasturlarda kiritishimiz kerak. Ammo dastlabki tuzilishda tasdiqlash atomini boshqarish, Dock atomini boshqarish, auth interceptor storage, kontekst interceptor storage, logbook interceptor storage har biri alohida sozlash funksiyalariga ega edi.
Sozlamalar bir nechta kirish nuqtalariga bo'linganda “hook yangi storage'nı ko'radi, lekin interceptor asosiy storage'nı ko'radi” muvofiqlik masalasi paydo bo'lishi mumkin. Shuning uchun storage'ni amalga oshirishdan tashqari, boshlash chegarasini bitta joyga jamlash kerak edi.
3. Birinchi javob: storage inject qilish orqali mobil ijro yo'lini ta'minlash
Avval React Native'da Dock kodini ishlatish uchun veb va no-vizual muhitlarni ajratib, tashqi storage'ni inject qilish yo'lini qo'shdik. Vebda mavjud asosiy storage'dan foydalanamiz, mobilda esa KeyValueStorage ijrosini dastur o'tkazishi kerak.
Umumiy storage shartlari get, set, remove, clear kabi oddiy key-value operatsiyalarini belgilab qo'ydik.
export interface KeyValueStorage {
get: (key: string) => string | null;
set: (key: string, value: string) => void;
remove: (key: string) => void;
clear: () => void;
}
Dock faqat ushbu interfeysga bog'liq bo'lgani uchun aniq storage texnologiyalarini bilish shart emas. Vebda brauzer storage'ni qamrab oluvchi amalga oshirishdan foydalanamiz, mobilda esa xuddi shunday shartni qoniqtiradigan adaptorni o'tkazamiz. Alohida session storage bo'lmagan muhitda bitta storage ikki vazifani bajarishini tashkil qildik.
Dastlabki qo'llash jarayonida uzatilgan storage obyekti va haqiqiy saqlash va qidirish natijalarini jurnalga yozganmiz. Mobil build’da modul yuklash va storage inject qilish vaqti farq qilgani uchun boshlash tartibini tasdiqlash jarayoni muhim edi.
Shuningdek, veb uchun maxsus xabarlar va harakatlarni almashtirish maqsadida showAlert, navigateTo handler'ni kiritiladigan qilib qo'ydik. Storage'dan tashqari, platformaga bog'liq bo'lgan yon ta'sirlarni ham tashqi tomonga surdik.
Ushbu birinchi javob orqali mobil ijro yo'li ta'minlandi, lekin atom va storage hali ham bir vaqtning o'zida holatni boshqarardi va tasdiqlash va Dockning boshlash funksiyalari ham ajratilgan edi. Storage'ni inject qila olish faqat holatni sinxronlashtirish muammolarini hal qilmadi.
4. Yakuniy yo'nalish: storage'ni bitta mezon nuqtasiga o'tkazish
Birinchi javobdan so'ng storage'ni atom ortida yashirish usulidan storage'ni o'z holatining bazasi sifatida ishlatish yo'nalishiga o'tdik. Asosiy tamoyil to'rtta edi.
Birinchidan, tasdiqlash va Dockning doimiy holatlari faqat markaziy storage menejeri orqali o'qiladi va yoziladi.
Ikkinchisi, React hook qiymatlarni alohida egallamaydi, balki menedjer tomonidan o'qilgan qiymatlarni ekranlarga aks ettiradi.
Uchinchisi, agar menedjerda qiymat o'zgarsa, tegishli kalitga obuna bo'lgan hookga darhol xabar beradi.
To'rtinchisi, storage va interceptorni dastlabki sozlash bitta kirish nuqtasida amalga oshiriladi.
Mavjud zanjir atom tuzilmasini olib tashladik va CentralStorageManagerni joriy qildik. Dock bo'ylab Jotai-ni yo'q qilmadik. Faqatgina autentifikatsiya tokeni va Dock sessiya/kontekst kabi davomiylik talab qiladigan holatlar storage menedjeriga o'tkazildi, heartbeat va ishlab chiqish rejimi kabi faqat xotiradan foydalanadigan holatlarda esa mavjud usuli saqlanib qolindi.
Maqsad barcha holatlarni bir texnologiyaga birlashtirish emas, balki davomiy holatning aslidan atom va storage ikkita joyda mavjud bo'lish muammosini bartaraf etish edi.
5. Storage abstraksiyasini amalga oshirish
5.1 Markaziy menedjer va saqlash joyi siyosati
CentralStorageManager lokal/sessiya storageni bir marta joylashtirib, umumiy get, set, remove operatsiyalarini taqdim etadi. Matndan tashqari qiymatlar JSON formatida seriyalashadi, qidirishda esa avval JSON parsing qilishga harakat qilinadi.
export class CentralStorageManager {
private localStorage: KeyValueStorage | null = null;
private sessionStorage: KeyValueStorage | null = null;
private listeners = new Map<string, Set<(value: any) => void>>();
initialize(
localStorage: KeyValueStorage,
sessionStorage: KeyValueStorage,
) {
this.localStorage = localStorage;
this.sessionStorage = sessionStorage;
}
set<T>(
key: string,
value: T,
storageType: StorageType = 'session',
): void {
const storage = this.getStorage(storageType);
if (!storage) return;
const serialized =
typeof value === 'string' ? value : JSON.stringify(value);
storage.set(key, serialized);
this.notifyListeners(key, value);
}
remove(
key: string,
storageType: StorageType = 'session',
): void {
const storage = this.getStorage(storageType);
if (!storage) return;
storage.remove(key);
this.notifyListeners(key, undefined);
}
}
Saqlash joyi siyosati auth va dock degan domen API ichida to'plangan. Chaqaruvchi har safar access token qaysi storage-da saqlanishini belgilamaydi, balki storageManager.auth.setAccessToken()ni chaqiradi. refresh token lokal, access token sessiya, Dock sessiyasi va kontekst esa sessiya deb belgilangan siyosat bitta faylda ko'rsatilgan.
auth = {
getAccessToken: () =>
this.get(STORAGE_KEYS.ACCESS_TOKEN, '', 'session'),
setAccessToken: (token: string) =>
this.set(STORAGE_KEYS.ACCESS_TOKEN, token, 'session'),
getRefreshToken: () =>
this.get(STORAGE_KEYS.REFRESH_TOKEN, '', 'local'),
setRefreshToken: (token: string) =>
this.set(STORAGE_KEYS.REFRESH_TOKEN, token, 'local'),
clearTokens: () => {
this.remove(STORAGE_KEYS.ACCESS_TOKEN, 'session');
this.remove(STORAGE_KEYS.REFRESH_TOKEN, 'local');
this.remove(STORAGE_KEYS.REMEMBERED, 'local');
},
};
hook, interceptor, chiqish jarayoni, token yangilanish jarayonida bir xil API ishlatiladi, shuning uchun saqlash joyi o'zgarganda faqat markaziy menedjerni o'zgartirish kifoya. Test davomida xotira asosidagi KeyValueStorage qo'shib, brauzersiz holat oqimini tuzish mumkin.
5.2 Dock holatini qisman yangilaydigan API
Mavjud dockAtom sessiya va kontekstni birlashtirgan katta ob'ektni qabul qildi. Ba'zi maydonlarni o'zgartirishda hozirgi ob'ektni olgan holda nusxalash va yana atom va storage-ga saqlash kerak edi. Refaktoringdan so'ng menedjer hozirgi qiymatni tekshirgandan so'ng qisman yangilaydigan funktsiyani taqdim etmoqda.
updateContext: (updates: Partial<DockContext>): void => {
const currentContext = this.dock.getContext();
const updatedContext = { ...currentContext, ...updates };
this.set(
STORAGE_KEYS.DOCK_CONTEXT,
updatedContext,
'session',
);
},
updateSessionField: <T extends keyof SessionDockRdo>(
field: T,
value: SessionDockRdo[T],
): void => {
const currentSession = this.dock.getSession();
const updatedSession = {
...currentSession,
[field]: value,
};
this.set(
STORAGE_KEYS.DOCK_SESSION,
updatedSession,
'session',
);
},
Shundan so'ng useDock katta atom ob'ektini butunlay almashtirish o'rniga o'zgarish niyatini ko'rsatadigan API dan foydalanishga o'tdi va barcha yozuvlar bir xil xabar yo'lagidan o'tadi.
6. Observer naqshiga ko'ra hook va storageni sinxronlashtirish
storage-ni yagona mezon nuqtasi sifatida belgilaganimizda, quyidagi muammo paydo bo'ladi. storage React holati emas, shuning uchun qiymat o'zgarganda komponent avtomatik ravishda qayta renderlanmaydi. Buni hal qilish uchun menedjerga kalit bo'yicha obuna olish funksiyasi qo'shildi.
subscribe(
key: string,
listener: (value: any) => void,
): () => void {
if (!this.listeners.has(key)) {
this.listeners.set(key, new Set());
}
this.listeners.get(key)!.add(listener);
return () => {
const keyListeners = this.listeners.get(key);
keyListeners?.delete(listener);
if (keyListeners?.size === 0) {
this.listeners.delete(key);
}
};
}
private notifyListeners(key: string, value: any): void {
this.listeners.get(key)?.forEach((listener) => {
listener(value);
});
}
manager Subyekt roli o'ynaydi va hook Observer roli o'ynaydi. set yoki remove bajarilganda, mos keyning listeneriga xabar beriladi va listener getterni yana bajarib, authenticated kabi hosil bo'lgan qiymatlarni ham hisoblaydi. Bu obuna mantiqi umumiy hook bo'lgan useStorageValuesga ajratildi.
export const useStorageValues = <
T extends Record<string, any>
>(
gettersMap: { [K in keyof T]: () => T[K] },
subscriptionKeys: string[],
): T => {
const gettersMapRef = useRef(gettersMap);
gettersMapRef.current = gettersMap;
const [values, setValues] = useState<T>(() => {
const initialValues = {} as T;
for (const [key, getter] of Object.entries(gettersMap)) {
initialValues[key as keyof T] = getter();
}
return initialValues;
});
const updateValues = useCallback(() => {
const newValues = {} as T;
for (
const [key, getter]
of Object.entries(gettersMapRef.current)
) {
newValues[key as keyof T] = getter();
}
setValues(newValues);
}, []);
useEffect(() => {
if (!storageManager.isInitialized()) return;
const unsubscribers = subscriptionKeys.map((key) =>
storageManager.subscribe(key, updateValues),
);
updateValues();
return () =>
unsubscribers.forEach((unsubscribe) => unsubscribe());
}, [updateValues]);
return values;
};
getterMap ref sifatida saqlanadi, shunda listener funksiyasi har safar rendering qilinganida o'zgarib qolmaydi. useAuthValues autentifikatsiya bilan bog'liq keyni obuna qiladi, useDockValues esa Dock sessiyasini va kontekstni obuna qiladi. Umumiy hook sifatida chiqarilganida, har bir hookda mavjud bo'lgan majburiy yangilanish counter va obuna·bo'shatish ortiqcha kodlar olib tashlandi.
7. Boshlang'ich xarakterni birlashtirish
storage manager ishlashi uchun har bir platforma to'g'ri amalga oshirishni qo'shishi kerak. Buni amalga oshirish uchun storageni boshlash va interceptorni tuzilishini initStorageSystemga birlashtirdik.
Vebda alohida sozlamalar bo'lmasa, eski local/session storage adapteridan foydalaniladi. Veb bo'lmagan muhitda storage va showAlert, navigateTo handlerni majburiy oladi va sessionStorage alohida bo'lmasa, local storage amalga oshirilishi bilan birga ishlatiladi. Keyin storage managerni birinchi bo'lib boshlang'ich qilib, auth, context, log kabi interceptorlarni tuzamiz.
if (isWeb) {
finalLocalStorage =
localStorage || getLocalKeyValueStorage();
finalSessionStorage =
sessionStorage || getSessionKeyValueStorage();
} else {
setNotWebHandlers({
showAlert: notWebHandlers!.showAlert,
navigateTo: notWebHandlers!.navigateTo,
});
finalLocalStorage = localStorage!;
finalSessionStorage = sessionStorage || localStorage!;
}
storageManager.initialize(
finalLocalStorage,
finalSessionStorage,
);
configureInterceptors([
authInterceptor,
contextInterceptor,
logbookInterceptor,
...interceptors,
]);
Boshlang'ich funktsiyasiga takroriy chaqovlarni oldini olish uchun tekshiruv qo'shildi. Ilova kirish nuqtasida bir marta sozlanganida, hook va interceptor bir xil storage managerdan foydalanadi. Vebda eski chaqiriq usulini ham saqlab qoldik va eski ilovaning o'zgarish doirasini kamaytirdik.
8. Amalga oshirish natijalari
Ushbu ishning eng katta natijasi, veb funktsiyasini mobilda ham qayta ishlatish imkoniya bo'lib, holat o'zgarishlarining oqimlarini bitta yo'l bilan tushuntirish imkoniyatiga ega bo'lishidir.
Birinchidan, saqlash amalga oshirishi Dockdan tashqariga ajratildi. Vebda asosiy adapterdan foydalaniladi, mobilda esa KeyValueStorage shartlariga javob beradigan amalga oshirishni qo'shamiz.
Ikkinchidan, autentifikatsiya tokenlari va Dock sessiya/kontekstining asosiy holati storage managerga birlashtirildi. hook va interceptor bir-biridan turlicha holat manbalarini ko'rish muammosi kamaydi va qiymat o'zgartirilgandan keyin Observer xabarnomasi orqali ekran darhol yangilanadi.
Uchinchidan, saqlash siyosati va boshlang'ich mas'uliyati markazlashtirildi. access/refresh tokenlarining saqlash joyi, veb·veb bo'lmagan boshlanish tartibi, log out paytida tartibga solish doirasini cheklangan faylda ko'rish mumkin. Log out va sessiya muddati tugaganda tokenni emas, Dock sessiyasi, kontekst, loglar ham birgalikda o'chirilishi uchun tartibga solib keyingi foydalanuvchining holat wadq qoldirilishi muammosini oldini oldik.
To'rtinchidan, zanjirli atomlarni boshqaradigan faylni olib tashlab, storageManager va useStorageSubscription orqali rolni qayta tuzib, ortiqcha kodni kamaytirdik.
Beshinchidan, useCrossTabTokenRefresh, useLogbook, auth/context/log interceptor bir xil manager APIdan foydalanishi natijasida keyingi o'zgarish nuqtalari aniq ko'rindi.
Soniy qattiq ko'rsatkichlarni alohida o'lchash ishlaridan emas. Ammo yangilanishga tayanib, holat yangilanish yo'llari va foydalanuvchi o'zgarish paytida qolgan holat sabablarini bartaraf etdik va mobilda brauzer storageisiz boshlang'ich qilish tuzilishini ta'minladik.
9. O'rganilgan narsalar
Ushbu ish orqali eng katta o'rgangan narsam, holat boshqaruvi kutubxonasi o'zidan ko'ra holatning mulkiga muhimroqdir. Atomlarni ko'p ishlatmaslik emas, balki atom, storage va interceptorning barchasi holatni egallab, bir-biridan turlicha o'zgartira olishlari tufayli murakkab bo'ldi.
Ikkinchisi, platforma abstraksiyasining doirasini to'g'ri belgilash kerakligi. Dastlab, localStorage'ni mobil storage'ga o'tkazish masalasi ko'rinardi, ammo xabarnomalar, harakat, dastlabki tartib, token yangilash, logbook da platformalar asoslari tarqalgan edi. Saqlash interfeysi bilan bir qatorda, dastlabki chegaralar va yon ta'sirlarni ham alohida ajratish kerak edi.
Uchinchisi, Observer shabloni amaliy aloqalar vositasi bo'lishi mumkinligini ko'rsatmoqda. Holatni qayd etuvchi subyekt bevosita o'zgarishni bildirishga imkon berish orqali, veb va mobilda bir xil reaktsion yangilanish usulini qo'llay olishga muvaffaq bo'ldik va hook'lar bo'yicha majburiy renderlash kodini umumiylashtirdik.
To'rtinchisi, shoshilinch birinchi o'zgartirish bilan tuzilishni yaxshilashni ajratish amaliy strategiyaga aylanishidir. Dastlab, amalga oshiriladigan yo'lni ta'minlab, haqiqiy muvaffaqiyatsizlik nuqtalarini aniqlagach, atom zanjirli tuzilishini bartaraf etish, markaziy menejerni kiritish, obuna hooklarini ajratish va dastlabki birlashishni bosqichma-bosqich amalga oshirdik.
10. Xulosa qilib
Vizend Dockning mobil qo'llanilishi oddiygina brauzer API'sini shartli operatorlar bilan o'rab olish bilan tugamadi. Avvaldan yashirin holatning ikki marta ko'payishi va dastlabki parchalash muammosi platforma kengayish jarayonida yanada aniq ko'rinishda paydo bo'ldi.
Saqlashni KeyValueStorage'ga abstraktlashtirib, markaziy menejerni doimiy holat uchun yagona belgi nuqtasi sifatida tanlab, kalit asosidagi Observer shabloni yordamida hook'larni bog'lash orqali holat oqimini soddalashtira oldik. Shuningdek, dastlabki kirish nuqtasi va logout tozalash doirasini birlashtirib, vebdagi mavjud ishlash usulini saqlab turib, mobil saqlovni kiritish uchun tuzilma yaratish imkoniyatiga ega bo'ldik.
Ushbu tajriba fruntend holat boshqaruvi refaktorizatsiyasi bo'yicha muhim savollardan biri “qaysi kutubxonani tanlash kerak?” da emasligini ko'rsatdi. Oldindan aniqlanishi kerak bo'lgan narsa holatning asl manbasi qayerda, kim o'zgartirishi mumkin va o'zgarish haqidagi faktlar iste'molchilarga qanday etkazilayotganidir. Bu uch narsani aniq belgilagach, kutubxona va platforma o'zgarganda ham kengaytiriladigan tuzilmani yaratish mumkin.
IAN