Как да обезсилите кешираните данни в Apollo и да боравите с актуализиране на страници, свързани със страници

След като отделих известно време да разбера за разбиране и внедряване на стек, базиран на графичен график, аз съм взривен от това колко лесно е бързото разработване на сложни приложения. Особено ме впечатли как графичните заявки дават възможност на разработчиците от предния край да имат достъп до необходимите им данни от множество свързани типове данни, без да се нуждаят от конкретна крайна точка за всяка заявка.

Това каза, че стека не е без точки на болка. В предния край, едно от тези предизвикателства е свързано с невалидни данни, съхранявани в кеша на Apollo, когато тези данни са променени от страна на сървъра. това може да бъде особено обезпокоително при работа с страници с въпроси.

Стартовото ни приложение

В тази статия ще разгледаме някои примери за обикновено Next.js приложение, което използва Apollo за извличане на данни от още по-опростения графичен заден край на Йога / Призма. Ако искате да следвате заедно с тези примери, можете да вземете началните файлове тук, в основния клон: https://github.com/martinseanhunt/padgey-nation-frontend

Този преден край е свързан с демонстрационен заден край, който е хостван сега.sh, така че просто трябва да можете да стартирате npm install и npm run dev и ще бъдете добри!

Проблемът

Когато използвате клиента Apollo, отговорът на всяка заявка, която се изпраща до сървъра, се кешира локално (освен ако изрично не му кажем, на което ще се върнем по-късно). Това е чудесно за ускоряване на нашето приложение, тъй като Аполон ще вземе исканите данни от кеша, вместо да запитва сървъра за данни, които вече имаме.

Кеширането на данни прави многократните заявки по-бързи и намалява натоварването на сървъра

Когато обаче направим промяна в данните на нашия сървър чрез мутация на GraphQL, тази промяна няма да бъде отразена в състоянието на приложението, освен ако не опресним страницата.

Добавянето на елемент изисква опресняване, за да видите промените, същото е и за изтриването

refetchQueries

Аполон ни дава удобна функция, наречена refetchQueries, на която може да бъде предаден масив от заявки, които се нуждаят от повторно извличане, след като извършим мутация. Въпреки това, всички заявки, които предаваме на refetchQueries, ще бъдат извиквани веднага щом мутацията приключи, това е далеч по-малко от идеалното за страници с данни. Ако приемем, че имаме 100 страници с данни, които всички са попълнени в отделни заявки, използвайки refetchQueries ще изпратим 100 заявки за мрежата на нашия сървър наведнъж.

Сървърите са подпалени

readQuery & writeQuery

Също така можем да четем и записваме данни в кеша на Apollo, използвайки функциите readQuery и writeQuery, които в много случаи са идеални за справяне с тези видове операции. Можем просто да вмъкнем новосъздадения ни елемент в или да филтрираме изтрития елемент от кеша си, показвайки незабавно актуализациите на екрана. Този подход обаче може да причини проблеми при справяне със страници от заявки в зависимост от това, какви страници е кеширал потребителят, преди да инициира мутацията. Това може да бъде особено проблематично, когато се правят множество промени.

Например, ето проблем, който можем да срещнем при ръчно изтриване на елемент от кеша, използвайки функцията за актуализиране в нашия компонент Create.js:

Ето визуален пример за един от проблемите, които това може да създаде:

Тестовите елементи 11 и 12 изчезват до опресняване

Сблъскваме се с подобен проблем, когато добавяме новосъздадени елементи в кеша по този начин.

Елементи 22, 21 и 20 се дублират на страница 2

Тук също се излагаме на трудна грешка. Тъй като винаги искаме да добавим данните за нов елемент към страница 1 и ако тези кеширани данни не съществуват, получаваме грешка при извикване на кеша за него.

Тук влязохме в приложението на страница 2, преди да създадем елемент, следователно няма данни за пропускане: 0 в кеша

Ако искате да проучите кодовата база за този подход, можете да я намерите тук: https://github.com/martinseanhunt/padgey-nation-frontend/tree/update-cache-direct

fetchPolicy

Друго потенциално решение е да зададете fetchPolicy на нашите първоначални заявки „само за мрежа“. Това означава, че данните за текущата страница винаги ще идват от сървъра. Този подход работи отлично, но губите усещането, което идва с обслужването на кеширани данни, когато са налични и повече заявки ще бъдат изпратени до сървъра, отколкото е необходимо.

  заявка = {GET_LIST_ITEMS_QUERY}
  fetchPolicy = 'мрежа само за'
  променливи = {{skip: (страница - 1) * perPage}}
>

От тук накъде ?

Този проблем е проблемът, с който екипът на Apollo е запознат и в момента тече работа върху решение, което ще стане част от API на Apollo. Ако обаче като мен, вие сте гладни за преодоляване, което да използвате във временното време, тогава имате няколко възможности!

Временно, работещо решение ...

За щастие, има много просто решение, което би трябвало да работи в повечето случаи. Когато имаме достъп до кеш обекта, можем да се обадим на cache.data.delete (ключ), където ключ е ключът, който Apollo използва, за да съхранява данните за конкретен елемент. И записът ще бъде изтрит изцяло от кеша.

Ето кеша на Аполон, съдържащ първите ни две страници с данни от нашето приложение

Можете да видите, че Аполон взема името на типа на нашата конкретна информация и добавя уникален идентификатор, за да създаде ключа.

Това означава, че в нашите актуализиращи функции можем просто да прегледаме кеша и да изтрием всички записи, където ключът започва с ListItem: премахване на всички наши страници от заявки от кеша и принуждаване на повторно извличане при следваща заявка на страницата.

За да работи това в нашето приложение, можем да направим следното:

1) Създайте помощна функция, която ще се справи с изтриването на нашите страници на заявки от кеша.

2) Актуализирайте нашата мутация в Create.js, за да се обадите на новата ни помощна функция при актуализиране при създаване на нов елемент от списъка. (не забравяйте да зададете отново заявката за връзка)

3) В Index.js, преструктурирането на деструктурирането от обекта, дадено да предостави функцията за подпомагане под нашето запитване, и го предайте на ListItem.js

Предаваме тази функция за презапис на ListItems.js, така че да можем да се обадим на нова страница на текущата страница при изтриване на елемент. В противен случай в текущата версия на Apollo компонентът Index.js не се рендерира и заявката няма да бъде повторена. Вярвам, че причината за използването на cache.data.delete не задейства рендериране на родителски компоненти е, защото ние директно мутираме обекта на данни от кеша, но това е нещо, в което не съм 100% сигурен.

4) Създайте и се обадете на нашата актуализираща функция в ListItems.js, когато listItem е изтрит (отново не забравяйте да пренастроите нашата заявка за връзка за страници)

5) Бъдете сигурни, че имате всички правилни внос и износ за запитвания и помощни функции и ... Успех!

Приложението работи както се очаква

Сега имаме работещо приложение, в което можете да добавяте и изтривате елементи и данните за страниците ще бъдат поискани от сървъра само когато е необходимо.

Пълната кодова база за това решение е достъпна тук: https://github.com/martinseanhunt/padgey-nation-frontend/tree/simple-solution

Можете да видите пълната разлика между стартовите файлове и работещото решение тук: https://github.com/martinseanhunt/padgey-nation-frontend/compare/master...simple-solution

И можете да видите демонстрация на живо на https://padgey-nation.now.sh

5) Недостатъци на това решение

Нека не празнуваме твърде скоро ...

Един от проблемите с това решение е, че всички други кеширани данни с typename ListItem ще бъдат изтрити от локалния кеш и ще се нуждаят от повторно коригиране, дори ако не са част от нашите страници на заявки. Например, ако имахме вторичен списък от елементи със списък със звезда, които не е задължително да актуализираме, когато даден елемент е актуализиран или изтрит, няма да има начин да разграничим двете, така че тези заявки също да бъдат пренастроени. от сървъра следващия път, когато бъдат запитвани.

Тъй като ние трябва да картографираме целия си кеш при всяко добавяне или изтриване на запис, не съм сигурен дали това ще доведе до проблеми с производителността с голям и сложен преден край.

Другият проблем с това е, че ние мутираме локалния си кеш много директно и това потенциално може да има неочаквани странични последици (както видяхме, когато се налага ръчно да се обади за повторно изтегляне след изтриване на елемент).

Като цяло, въпреки че мисля, че това е чудесно временно решение за повечето случаи на използване, докато Аполон не успее да предложи по-добър начин.

Има още един начин, който съм запознат

Попаднах на горното решение, когато някой го спомена преди няколко дни в брой на github, който аз проследявам (https://github.com/apollographql/apollo-feature-requests/isissue/4#issuecomment-431119231). Като цяло мисля, че това е по-простото, елегантно решение засега. Въпреки това, в момента, в който попаднах на горния метод, вече работех върху решение, което не се нуждае от картографиране на целия кеш, нито директно мутира кеш обекта. Също така този метод ще работи в случай, че в други части на приложението си имате елементи със същото име, които не е необходимо да се актуализират.

Това решение обаче е доста по-сложно по своята същност и вярвам, че все още има място за подобрение.

Прегледът на високо ниво на идеята е, че поддържаме масив в локално състояние на номерата на страниците, които трябва да се актуализират и в зависимост от това дали текущата страница, която се запитва, е в масива, или изпращаме заявка с fetchPolicy на „network- само „или„ кеш-първо “

Ето основните две функции, участващи в тази работа:

1) setPagesToBeRefreshed - това се извиква при актуализация от нашите мутации, отговорни за създаването и изтриването на елементи от списъка

2) getItemsForPage - това се нарича в различни точки от жизнения цикъл на Index.js, за да се определи дали да потърсите в кеша за нашите данни или да отидете направо към сървъра

Тук няма да навлизам в твърде много подробности, тъй като смятам, че първият метод е по-простият и приложим вариант в повечето случаи. Въпреки това, ако има някакво търсене, ще се радвам да напиша последващи действия и да обясня второто решение по-подробно.

Ако имате любопитство, можете да разгледате пълната база данни тук: https://github.com/martinseanhunt/padgey-nation-frontend/tree/refetch-queries-only-when-needed

Разликата между началните файлове и крайния продукт можете да видите тук: https://github.com/martinseanhunt/padgey-nation-frontend/compare/master...refetch-queries-only-when-needed

Обобщавайки

Измислянето на всичко това е доста пътешествие. Все още съм нова в графичната екосистема, но съм изключително развълнувана да видя къде тези технологии ще ни отведат. Ако някой има конструктивна критика или отзиви относно тази статия, бих искал да го чуя.

Очаквам официално решение на този проблем, но междувременно наистина се надявам, че това може да бъде от полза за някого.

Честито кодиране!