Omezení výkonu Native React a jak je překonat

nezkrotte draka, jezdte na něm

React Native má velký slib, pokud jde o vynikající vývojářské zkušenosti s Javascriptem a opětovné použití kódu mezi platformami. Velkou otázkou je - dělají tyto výhody cenu výkonu? Jak dobře může React Native obstát proti čistě nativním implementacím? Ukazuje se, že docela dobře. Obzvláště pokud rozumíte vaší cestě kolem omezení, která jsou základnímu rámci.

Reagujte Nativní pod kapotou

Abychom pochopili omezení výkonu produktu React Native, musíme nejprve nahlédnout do jeho vnitřního fungování. Z důvodu přehlednosti se pokusím tuto část udržet na vysoké úrovni. Pokud hledáte krvavé detaily, podívejte se na tento vynikající příspěvek od Tadeu Zagalla.

Jedním z hlavních prostorů React Native je poskytování nativního prostředí aplikací, které mobilní uživatelé očekávali - a přitom se zaměřili na efektivitu vývojářů založenou na Javascriptu a React. Jinými slovy, i když většina kódu naší aplikace je napsána v jazyce Javascript, uživatelské rozhraní samotné aplikace je zcela nativní.

To znamená, že naše aplikace běží ve dvou různých sférách:

  • Nativní říše - Království Objective-C / Swift v iOS a Java v Androidu. Zde se propojujeme s OS a vykreslují se všechny pohledy. UI se manipuluje výhradně na hlavním vláknu, ale mohou existovat i jiné pro výpočet na pozadí. React Native pro nás dělá většinu těžkých zvedání v této říši.
  • Oblast JS - Javascript je spouštěn ve svém samostatném vlákně pomocí Javascriptového enginu. Naše obchodní logika, včetně toho, které pohledy se mají zobrazovat a jak je stylizovat, je obvykle implementována zde.

K proměnným definovaným v jedné oblasti nelze přímo přistupovat v druhé. To znamená, že veškerá komunikace mezi oběma oblastmi musí probíhat explicitně přes most. Ve skutečnosti je to podobné konceptu, jakým klienti a servery komunikují přes web - aby data mohla projít, musí být data serializována. Skvělá anekdota - při ladění kódu RN JS v Chromu se dvě říše skutečně spouští na různých počítačích (ploše a mobilu) a most mezi nimi prochází přes WebSocket.

Zde leží jeden z hlavních klíčů k pochopení React Native performance. Každá říše sama o sobě je nesmírně rychlá. Úzký výkon se často vyskytuje, když se přesuneme z jedné oblasti do druhé. Abychom mohli zpracovat výkonné aplikace React Native, musíme udržovat průchody přes most na minimum.

React s konceptem virtuálního DOMu nám poskytuje vynikající optimalizaci hned po vybalení. Změny v našich vykreslených komponentách v JS jsou dávkovány asynchronně pomocí algoritmu smart diff - tedy minimalizuje množství dat odeslaných přes most. To je vlastně důvod, proč je produkt React Native výkonnější než konkurenční technologie jako Appcelerator, které jej předcházely o několik let.

Hra se skutečným životním případem

Vidíte odkládací kartu vlevo? Jedná se o oblíbený mobilní UX vzor používaný v aplikacích, jako je Chytré karty Google.

Je také překvapivě zajímavé implementovat výkonně.

Tento příklad budeme implementovat vícekrát a uvidíme důsledky každého přístupu na výkon.

Dává smysl vytvořit opakovaně použitelnou komponentu Swipeable, která přidává chování při přejíždění (změna x překladu a neprůhlednosti) do jakékoli komponenty obsahu, kterou v tomto případě dáme jako podřízená karta.

Naše první implementace - PanResponder

Začněme s přímým přístupem. Protože chceme poslouchat dotykovým gestem, použijeme PanResponder React Native. Pokaždé, když obdržíme událost přesunu, vypočítáme novou neprůhlednost a x překlad na základě celkové vodorovné ujeté vzdálenosti a aktualizujeme je pomocí místního stavu:

Jaký výkon bychom měli očekávat od tohoto přístupu? Vzpomeňme si na pokyny uvedené výše - Abychom mohli architekta výkonného Reacta Native aplikovat, musíme udržovat přechody přes most na minimum.

Zdá se, že tato implementace příkladu dělá pravý opak. Dotykové události pocházejí z nativní říše, protože tam zařízení sleduje prst uživatele. Naše aktualizace stavu komponenty se zjevně odehrávají v říši JS. To obvykle není hlavní problém, problém je v tom, že tyto aktualizace probíhají v každém snímku! To znamená, že pro každý jednotlivý animační rámec, kde chceme, aby se věci cítily co nejplynuleji, musí data přes most přes.

Jedná se o překážku výkonu, kterou čistě nativní aplikace nemají, což jim mnohem usnadňuje dosažení svatého grálu 60 FPS, zejména na slabších zařízeních, a zejména v případech skutečného života, které jsou o něco složitější než tento příklad.

Četl jsem něco v dokumentech o přímé manipulaci?

Pokud vám záleží na výkonu, pravděpodobně jste si přečetli dokumenty cover-to-cover a vágně si pamatujete tento článek o přímé manipulaci se součástmi.

Zní to slibně, pojďme aktualizovat nativní součást přímo a zlepšit výkon! Zkusíme to, tady je implementace:

Vyřešilo to naše problémy s výkonem mostu? Ne tak docela - protože stále aktualizujeme z oblasti JS. Ale to optimalizovalo něco, co stojí za pochopení. V předchozí implementaci jsme v každém rámci, který jsme neposlali pouze data přes most, znovu vykreslili naši komponentu. V tomto konkrétním případě funkce vykreslení sotva něco dělá, takže to nebyl problém. Ale co kdyby naše funkce vykreslování byla složitější a výpočetně náročnější?

Abychom mohli aktualizovat komponentu React, musíme tradičně znovu vykreslit. Pokud je naše aktualizace velmi lokalizovaná, jako je změna konkrétního stylu (x překlad a neprůhlednost), můžeme ji chirurgicky provést přímo bez úplného vykreslení a usmíření. To je v rozporu s reakčním „stavem mysli“, takže je nejlepší to nedělat často a omezovat se na případy, kdy se konkrétní vlastnost mění velmi rychle (např. Během animace).

Můžeme se vrátit k vyřešení problému s mostem?

Jedna z nejkrásnějších věcí na React Native je, že můžeme vzít jakoukoli část naší kódové základny a hladce ji přesunout do původního - dokonce jen jednu součást.

Vývojáři často považují React Native za čisté prostředí JS - není to tak. Je pravda, že JS by často poskytovala ty nejlepší zkušenosti s vývojářem, ale existují případy, kdy nativní poskytuje lepší uživatelský zážitek. Naléhám na vás, pokud pocházíte z webového prostředí - nebojte se rodáka. Je to další nástroj v opasku, který obvykle vyžaduje stejné množství stohování.

Vzhledem k tomu, že dotykové události pocházejí z nativní říše, co by se stalo, kdybychom také aktualizovali x překlad a neprůhlednost nativní? Podívej se:

Jedinou částí, kterou jsme přesunuli do nativní, je komponenta Swipeable container. To by nám zaručilo 60 FPS a zdá se, že kód je ve skutečnosti kratší. Všimněte si, že naše komponenty obsahu karty zůstaly v čistě JS. Zde je návod, jak se naše nativní třída používá v našem rozvržení JS:

Budoucnost React Native

I když je pravda, že můžeme nativní kód použít selektivně pro připojení našich výkonnostních dír, budoucnost rámce je zlepšit a zajistit, abychom tak učinili méně a méně.

Je možné navrhnout chytré rozhraní JS, které by minimalizovalo průchody přes most a dosáhlo stejných výsledků. Co kdyby v našem příkladu nemusel náš kód JS aktualizovat nativní oblast v každém snímku? Co kdybychom mohli na začátku interakce pouze určit, jaké vlastnosti jsou uzamčeny pro kterou nativní událost, a nechat nějaký nativní modul ve vnitřním břiše React Native uvolnit aktualizace pro nás? To by nás přimělo projít mostem jen jednou - na začátku.

React Native se vyvíjí tímto směrem a jedním z primárních dárků, které jsme dostali, je nová animovaná knihovna. Pojďme implementovat náš příklad počtvrté a naposledy s Animated in pure JS:

Jak vidíte, knihovna Animated zachází s animacemi a interakcemi velmi deklarativně. Pokud dokážeme deklarovat, jak se interakce chová, může být tato deklarace serializována a poslána přes most. Tím se otevírá možnost, aby obecný nativní modul pro nás zpracoval interakci a uvolnil aktualizace jednotlivých snímků.

Současná implementace Animated (červen 2016) bohužel zatím nezatěžuje všechno nativním. To znamená, že naše čtvrtá implementace v současnosti stále trpí stejným úzkým profilem mostu. Po tom, co bylo řečeno, dochází k pokroku a jsem přesvědčen, že budoucí verze nám v mnoha případech umožní překonat omezení mostu od JS.

Porovnání všech čtyř implementací

Čtení o výkonu není to samé jako pocit ve skutečném životě. Můžete si hrát s plně funkčními verzemi čtyř implementací v následujícím repu, který je uveden vedle sebe pro snadné srovnání:

Spusťte příklad na skutečném zařízení, protože simulátor nedává autentické výsledky. Repo navíc obsahuje volitelné vlajky, které simulují stresové podmínky v aplikaci - jako jsou výboje aktivity nad mostem a výpočetně těžší vykreslovací funkce. Je zajímavé prozkoumat, jak se za těchto podmínek mění plynulost každé implementace.

Závěr a rozloučení slov

Vývoj mobilních aplikací v produktu React Native je úžasný, ale pohodlí někdy přichází za cenu. Je však možné zmírnit téměř každý problém s výkonem a klíčem je pochopení toho, co se děje pod kapotou.

Na webu Wix.com jsme posedlí UX a poskytujeme nativní uživatelské prostředí, které mobilní uživatelé očekávali. Zde je náš hrubý pokyn pro posedlý React Native performance:

  1. Začněte implementací všeho v JS pro maximální produktivitu. Nepřiměřujte příliš brzy.
  2. V oblastech, které jsou náchylné k nadměrnému využívání mostů, jako jsou animace / interakce, dávejte přednost deklarativním knihovnám, jako je Animated. Mnoho interakcí lze vyjádřit deklarativně ai když to na první pohled nemusí být intuitivní (viz naše čtvrtá implementace), stojí za to úsilí.
  3. Počkejte, až celý produkt na skutečném zařízení uvidí, kde vaše aplikace zmizí.
  4. Pokud selže tradiční optimalizace reakcí, chirurgicky přesuňte problematické části na původní. Za tímto účelem udržujeme poměr přibližně 10% domácích vývojářů v našich technických týmech.
  5. Povzbuzujte vývojáře JS k natáčení v nativním jazyce. Je to mocný nástroj k použití na správném místě a není mimo dosah. Některé složité interakce nemohou být deklarativně vyjádřeny a uvolněny knihovnami jako Animated.