Przejdź do treści głównej

Korzystanie z TypeScript w React Native

· 7 minut czytania
Ash Furrow
Inżynier Oprogramowania w Artsy
Nieoficjalne Tłumaczenie Beta

Ta strona została przetłumaczona przez PageTurner AI (beta). Nie jest oficjalnie zatwierdzona przez projekt. Znalazłeś błąd? Zgłoś problem →

JavaScript! Wszyscy go kochamy. Ale niektórzy z nas uwielbiają także typy. Na szczęście istnieją rozwiązania pozwalające dodać silniejsze typowanie do JavaScript. Moim ulubionym jest TypeScript, ale React Native natywnie wspiera Flow. Wybór między nimi zależy od preferencji – każde podejście inaczej dodaje magię typów do JavaScript. Dzisiaj przyjrzymy się, jak używać TypeScript w aplikacjach React Native.

Ten wpis korzysta z repozytorium Microsoftu TypeScript-React-Native-Starter jako przewodnika.

Aktualizacja: Od czasu powstania tego wpisu proces stał się jeszcze prostszy. Możesz zastąpić całą konfigurację opisaną w tym artykule, wykonując jedno polecenie:

npx react-native init MyAwesomeProject --template react-native-template-typescript

Istnieją jednak pewne ograniczenia w obsłudze TypeScript przez Babel, które szczegółowo omówiono w powyższym artykule. Kroki opisane w tym wpisie nadal działają, a Artsy nadal używa react-native-typescript-transformer w produkcji, ale najszybszym sposobem na rozpoczęcie pracy z React Native i TypeScript jest użycie powyższego polecenia. Zawsze możesz przełączyć się później, jeśli zajdzie taka potrzeba.

W każdym razie – dobrej zabawy! Oryginalny wpis blogowy kontynuujemy poniżej.

Wymagania wstępne

Ponieważ możesz rozwijać aplikacje na różnych platformach dla różnych typów urządzeń, podstawowa konfiguracja może być złożona. Najpierw upewnij się, że potrafisz uruchomić zwykłą aplikację React Native bez TypeScript. Postępuj zgodnie z instrukcjami na stronie React Native, aby rozpocząć. Gdy uda ci się wdrożyć aplikację na urządzeniu lub emulatorze, będziesz gotowy do stworzenia aplikacji React Native z TypeScript.

Będziesz także potrzebować Node.js, npm i Yarn.

Inicjalizacja

Gdy już spróbujesz stworzyć szkielet zwykłego projektu React Native, będziesz gotowy, aby dodać TypeScript. Zróbmy to teraz.

react-native init MyAwesomeProject
cd MyAwesomeProject

Dodawanie TypeScript

Następnym krokiem jest dodanie TypeScript do projektu. Poniższe polecenia:

  • dodadzą TypeScript do twojego projektu

  • dodadzą React Native TypeScript Transformer do projektu

  • zainicjują pusty plik konfiguracyjny TypeScript, który skonfigurujemy później

  • dodadzą pusty plik konfiguracyjny React Native TypeScript Transformer, który skonfigurujemy później

  • dodadzą definicje typów dla React i React Native

OK, uruchommy te polecenia.

yarn add --dev typescript
yarn add --dev react-native-typescript-transformer
yarn tsc --init --pretty --jsx react
touch rn-cli.config.js
yarn add --dev @types/react @types/react-native

Plik tsconfig.json zawiera wszystkie ustawienia kompilatora TypeScript. Domyślne wartości utworzone przez powyższe polecenie są w większości poprawne, ale otwórz plik i odkomentuj następującą linię:

{
/* Search the config file for the following line and uncomment it. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
}

Plik rn-cli.config.js zawiera ustawienia dla React Native TypeScript Transformer. Otwórz go i dodaj następujący kod:

module.exports = {
getTransformModulePath() {
return require.resolve('react-native-typescript-transformer');
},
getSourceExts() {
return ['ts', 'tsx'];
},
};

Migracja do TypeScript

Zmień nazwy wygenerowanych plików App.js i __tests_/App.js na App.tsx. Plik index.js musi zachować rozszerzenie .js. Wszystkie nowe pliki powinny używać rozszerzenia .tsx (lub .ts, jeśli plik nie zawiera JSX).

Gdybyś spróbował teraz uruchomić aplikację, otrzymałbyś błąd podobny do object prototype may only be an object or null. Jest to spowodowane niepowodzeniem w zaimportowaniu domyślnego eksportu z React oraz nazwanego eksportu w tej samej linii. Otwórz plik App.tsx i zmodyfikuj import na początku pliku:

-import React, { Component } from 'react';
+import React from 'react'
+import { Component } from 'react';

Część tego problemu wynika z różnic w sposobie współdziałania Babela i TypeScripta z modułami CommonJS. W przyszłości oba narzędzia ustabilizują się pod kątem tego samego zachowania.

W tym momencie powinieneś być w stanie uruchomić aplikację React Native.

Dodawanie infrastruktury testowej TypeScript

React Native dostarczany jest z Jest, więc aby testować aplikację React Native z TypeScript, dodamy ts-jest do naszych devDependencies.

yarn add --dev ts-jest

Następnie otworzymy nasz package.json i zastąpimy pole jest następującą konfiguracją:

{
"jest": {
"preset": "react-native",
"moduleFileExtensions": [
"ts",
"tsx",
"js"
],
"transform": {
"^.+\\.(js)$": "<rootDir>/node_modules/babel-jest",
"\\.(ts|tsx)$": "<rootDir>/node_modules/ts-jest/preprocessor.js"
},
"testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$",
"testPathIgnorePatterns": [
"\\.snap$",
"<rootDir>/node_modules/"
],
"cacheDirectory": ".jest/cache"
}
}

To skonfiguruje Jesta do uruchamiania plików .ts i .tsx za pomocą ts-jest.

Instalowanie deklaracji typów zależności

Aby uzyskać najlepsze doświadczenia w TypeScript, potrzebujemy, aby sprawdzanie typów rozumiało strukturę i API naszych zależności. Niektóre biblioteki publikują swoje pakiety z plikami .d.ts (deklaracje typów/definicje typów), które opisują strukturę podstawowego JavaScriptu. Dla innych bibliotek musimy jawnie zainstalować odpowiedni pakiet z zakresu npm @types/.

Na przykład będziemy potrzebować typów dla Jesta, Reacta, React Native oraz React Test Renderer.

yarn add --dev @types/jest @types/react @types/react-native @types/react-test-renderer

Zapisaliśmy te pakiety deklaracji jako zależności dev, ponieważ jest to aplikacja React Native używająca tych zależności wyłącznie podczas rozwoju, a nie w czasie działania. Gdybyśmy publikowali bibliotekę na NPM, musielibyśmy dodać niektóre z tych zależności typów jako zwykłe zależności.

Więcej o uzyskiwaniu plików .d.ts możesz przeczytać tutaj.

Ignorowanie dodatkowych plików

W systemie kontroli wersji warto zignorować folder .jest. Jeśli używasz gita, możesz dodać wpisy do pliku .gitignore.

# Jest
#
.jest/

Jako punkt kontrolny warto zatwierdzić pliki w systemie kontroli wersji.

git init
git add .gitignore # import to do this first, to ignore our files
git add .
git commit -am "Initial commit."

Dodawanie komponentu

Dodajmy komponent do naszej aplikacji. Stwórzmy komponent Hello.tsx. Jest to komponent dydaktyczny - nie coś, co napisałbyś w rzeczywistej aplikacji, ale przykład pokazujący nietrywialne użycie TypeScript w React Native.

Utwórz katalog components i dodaj następujący przykład:

// components/Hello.tsx
import React from 'react';
import {Button, StyleSheet, Text, View} from 'react-native';

export interface Props {
name: string;
enthusiasmLevel?: number;
}

interface State {
enthusiasmLevel: number;
}

export class Hello extends React.Component<Props, State> {
constructor(props: Props) {
super(props);

if ((props.enthusiasmLevel || 0) <= 0) {
throw new Error(
'You could be a little more enthusiastic. :D',
);
}

this.state = {
enthusiasmLevel: props.enthusiasmLevel || 1,
};
}

onIncrement = () =>
this.setState({
enthusiasmLevel: this.state.enthusiasmLevel + 1,
});
onDecrement = () =>
this.setState({
enthusiasmLevel: this.state.enthusiasmLevel - 1,
});
getExclamationMarks = (numChars: number) =>
Array(numChars + 1).join('!');

render() {
return (
<View style={styles.root}>
<Text style={styles.greeting}>
Hello{' '}
{this.props.name +
this.getExclamationMarks(this.state.enthusiasmLevel)}
</Text>

<View style={styles.buttons}>
<View style={styles.button}>
<Button
title="-"
onPress={this.onDecrement}
accessibilityLabel="decrement"
color="red"
/>
</View>

<View style={styles.button}>
<Button
title="+"
onPress={this.onIncrement}
accessibilityLabel="increment"
color="blue"
/>
</View>
</View>
</View>
);
}
}

// styles
const styles = StyleSheet.create({
root: {
alignItems: 'center',
alignSelf: 'center',
},
buttons: {
flexDirection: 'row',
minHeight: 70,
alignItems: 'stretch',
alignSelf: 'center',
borderWidth: 5,
},
button: {
flex: 1,
paddingVertical: 0,
},
greeting: {
color: '#999',
fontWeight: 'bold',
},
});

Wow! To sporo, ale przeanalizujmy to:

  • Zamiast renderować elementy HTML jak div, span, h1 itd., renderujemy komponenty takie jak View i Button. Są to natywne komponenty działające na różnych platformach.

  • Stylowanie określamy za pomocą funkcji StyleSheet.create dostarczonej przez React Native. Arkusze stylów React pozwalają kontrolować układ przy użyciu Flexboxa i stylizować przy użyciu konstrukcji podobnych do CSS.

Dodawanie testu komponentu

Skoro mamy już komponent, przetestujmy go.

Mamy już zainstalowanego Jesta jako runner testów. Będziemy pisać testy snapshotów dla naszych komponentów, więc dodajmy wymagane rozszerzenie do testów snapshotów:

yarn add --dev react-addons-test-utils

Teraz utwórz folder __tests__ w katalogu components i dodaj test dla Hello.tsx:

// components/__tests__/Hello.tsx
import React from 'react';
import renderer from 'react-test-renderer';

import {Hello} from '../Hello';

it('renders correctly with defaults', () => {
const button = renderer
.create(<Hello name="World" enthusiasmLevel={1} />)
.toJSON();
expect(button).toMatchSnapshot();
});

Podczas pierwszego uruchomienia testu zostanie utworzony migawkowy zapis wyrenderowanego komponentu, który zostanie zapisany w pliku components/__tests__/__snapshots__/Hello.tsx.snap. Po wprowadzeniu zmian w komponencie konieczne będzie zaktualizowanie migawek i przejrzenie ich pod kątem nieoczekiwanych zmian. Więcej o testowaniu komponentów React Native można przeczytać tutaj.

Kolejne kroki

Zapoznaj się z oficjalnym samouczkiem React oraz biblioteką do zarządzania stanem Redux. Te zasoby mogą okazać się pomocne podczas tworzenia aplikacji React Native. Dodatkowo warto przyjrzeć się bibliotece komponentów ReactXP, napisanej w całości w TypeScript i obsługującej zarówno React na webie, jak i React Native.

Powodzenia w bardziej bezpiecznym typowo środowisku programistycznym React Native!

Zbudowane w React Native - aplikacja Build.com

· 5 minut czytania
Garrett McCullough
Starszy Inżynier Mobilny
Nieoficjalne Tłumaczenie Beta

Ta strona została przetłumaczona przez PageTurner AI (beta). Nie jest oficjalnie zatwierdzona przez projekt. Znalazłeś błąd? Zgłoś problem →

Build.com, z siedzibą w Chico w Kalifornii, to jeden z największych internetowych sprzedawców artykułów do domu i remontów. Nasz zespół od 18 lat prowadzi prężną działalność zorientowaną na sieć, a w 2015 roku rozpoczęliśmy prace nad aplikacją mobilną. Stworzenie osobnych aplikacji na Androida i iOS nie było praktyczne ze względu na mały zespół i ograniczone doświadczenie w natywnym rozwoju. Zamiast tego zdecydowaliśmy się zaryzykować z bardzo nowym frameworkiem React Native. Nasze pierwsze commity powstały 12 sierpnia 2015 roku przy użyciu React Native v0.8.0! Aplikacja trafiła do obydwu sklepów App Store 15 października 2016 roku. Przez ostatnie dwa lata stale ulepszaliśmy i rozbudowywaliśmy aplikację. Obecnie korzystamy z wersji 0.53.0 React Native.

Aplikację możesz zobaczyć pod adresem https://www.build.com/app.

Funkcje

Nasza aplikacja jest w pełni funkcjonalna i zawiera wszystko, czego można oczekiwać od aplikacji e-commerce: listy produktów, wyszukiwanie i sortowanie, możliwość konfigurowania złożonych produktów, ulubione itp. Akceptujemy standardowe metody płatności kartą kredytową, a także PayPal oraz Apple Pay dla użytkowników iOS.

Kilka wyróżniających się funkcji, których możesz się nie spodziewać:

  1. Modele 3D dostępne dla około 40 produktów w 90 wykończeniach

  2. Rozszerzona rzeczywistość (AR), która pozwala użytkownikowi zobaczyć, jak oświetlenie i baterie będą wyglądać w jego domu z dokładnością rozmiaru na poziomie 98%. Aplikacja Build.com React Native jest promowana w Apple App Store w kategorii zakupów z AR! AR jest teraz dostępne na Androida i iOS!

  3. Funkcje wspólnego zarządzania projektami, które umożliwiają tworzenie list zakupów dla różnych faz projektu i współpracę przy wyborze produktów

Pracujemy nad wieloma nowymi i ekscytującymi funkcjami, które będą ulepszać naszą aplikację, w tym nad kolejnym etapem zakupów immersyjnych z wykorzystaniem AR.

Nasz proces rozwoju

Build.com pozwala każdemu deweloperowi wybrać narzędzia, które najlepiej mu odpowiadają.

  • Używane IDE to m.in. Atom, IntelliJ, VS Code, Sublime, Eclipse itp.

  • Przy testach jednostkowych deweloperzy odpowiadają za tworzenie testów jednostkowych Jest dla nowych komponentów. Pracujemy też nad zwiększeniem pokrycia testami starszych części aplikacji przy użyciu jest-coverage-ratchet.

  • Używamy Jenkinsa do budowania wersji beta i kandydatów do wydania. Proces działa dobrze, ale wciąż wymaga znacznej pracy przy tworzeniu notatek wydania i innych artefaktów.

  • Testy integracyjne obejmują wspólną pulę testerów pracujących na desktopie, urządzeniach mobilnych i w sieci. Nasz inżynier automatyzacji tworzy zestaw zautomatyzowanych testów integracyjnych przy użyciu Javy i Appium.

  • Inne elementy procesu obejmują szczegółową konfigurację ESLint, własne reguły wymuszające właściwości potrzebne do testowania oraz haki przed wysłaniem (pre-push), które blokują problematyczne zmiany.

Biblioteki używane w aplikacji

Aplikacja Build.com korzysta z wielu popularnych bibliotek open source, w tym: Redux, Moment, Numeral, Enzyme oraz wielu modułów mostkowych React Native. Używamy też szeregu rozwidlonych bibliotek open source - rozwidlonych dlatego, że były porzucone lub potrzebowaliśmy niestandardowych funkcji. Szybkie zliczenie pokazuje około 115 zależności JavaScriptowych i natywnych. Chcielibyśmy zbadać narzędzia usuwające nieużywane biblioteki.

Jesteśmy w trakcie dodawania statycznego typowania przez TypeScript i rozważamy wprowadzenie opcjonalnego łańcuchowania (optional chaining). Te funkcje mogą pomóc nam w rozwiązaniu kilku klas błędów, które wciąż obserwujemy:

  • Dane o nieprawidłowym typie

  • Dane niezdefiniowane, ponieważ obiekt nie zawierał tego, czego się spodziewaliśmy

Wkład w Open Source

Ponieważ tak bardzo polegamy na otwartym oprogramowaniu, nasz zespół jest zaangażowany w oddawanie cząstki społeczności. Build.com umożliwia zespołowi publikowanie bibliotek, które stworzyliśmy, oraz zachęca nas do współtworzenia bibliotek, z których korzystamy.

Opublikowaliśmy i utrzymujemy kilka bibliotek React Native:

  • react-native-polyfill

  • react-native-simple-store

  • react-native-contact-picker

Przyczyniliśmy się również do rozwoju długiej listy bibliotek, w tym: React i React Native, react-native-schemes-manager, react-native-swipeable, react-native-gallery, react-native-view-transformer, react-native-navigation.

Nasza podróż

W ciągu ostatnich kilku lat obserwowaliśmy ogromny rozwój React Native i jego ekosystemu. Na początku wydawało się, że każda wersja React Native naprawiała jedne błędy, ale wprowadzała kilka nowych. Na przykład zdalne debugowanie JavaScript było niesprawne na Androida przez kilka miesięcy. Na szczęście w 2017 roku sytuacja stała się znacznie bardziej stabilna.

Biblioteki nawigacyjne

Jednym z naszych powracających wyzwań były biblioteki nawigacyjne. Przez długi czas używaliśmy biblioteki ex-nav od Expo. Sprawdzała się dobrze, ale ostatecznie została porzucona. W tym czasie intensywnie rozwijaliśmy nowe funkcje, więc zmiana biblioteki nawigacyjnej nie była możliwa. Oznaczało to, że musieliśmy ją sforkować i dostosować do obsługi Reacta 16 i iPhone'a X. Ostatecznie udało nam się przejść na react-native-navigation i mamy nadzieję, że będzie ona nadal wspierana.

Moduły mostkowe

Kolejnym dużym wyzwaniem były moduły mostkowe (bridge modules). Na początku brakowało wielu kluczowych mostków. Jeden z członków zespołu napisał react-native-contact-picker, ponieważ potrzebowaliśmy dostępu do selektora kontaktów Androida w naszej aplikacji. Widzieliśmy też wiele mostków, które zostały zepsute przez zmiany w React Native. Na przykład w wersji React Native v40 wprowadzono zmiany łamiące kompatybilność i podczas aktualizacji naszej aplikacji musiałem zgłaszać pull requesty, aby naprawić 3 lub 4 biblioteki, które nie zostały jeszcze zaktualizowane.

Perspektywy na przyszłość

W miarę jak React Native się rozwija, nasza lista życzeń do społeczności obejmuje:

  • Stabilizację i ulepszenie bibliotek nawigacyjnych

  • Utrzymanie wsparcia dla bibliotek w ekosystemie React Native

  • Usprawnienie procesu dodawania natywnych bibliotek i modułów mostkowych do projektu

Firmy i osoby z społeczności React Native wspaniale angażują swój czas i wysiłek w ulepszanie narzędzi, z których wszyscy korzystamy. Jeśli jeszcze nie angażujesz się w open source, zachęcam do przyjrzenia się ulepszaniu kodu lub dokumentacji bibliotek, których używasz. Istnieje wiele artykułów, które pomogą ci zacząć, i może to być znacznie prostsze, niż myślisz!

Tworzenie komponentu <InputAccessoryView> dla React Native

· 5 minut czytania
Peter Argany
Inżynier oprogramowania w Facebooku
Nieoficjalne Tłumaczenie Beta

Ta strona została przetłumaczona przez PageTurner AI (beta). Nie jest oficjalnie zatwierdzona przez projekt. Znalazłeś błąd? Zgłoś problem →

Motywacja

Trzy lata temu zgłoszono problem na GitHubie dotyczący dodania obsługi widżetu pomocniczego klawiatury w React Native.

Przez kolejne lata pojawiały się liczne "+1", różne obejścia i zero konkretnych zmian w RN w tej sprawie - aż do dziś. Zaczynając od iOS, udostępniamy API do obsługi natywnego widżetu pomocniczego klawiatury i z przyjemnością dzielimy się szczegółami implementacji.

Kontekst

Czym dokładnie jest widżet pomocniczy klawiatury? Z dokumentacji Apple dowiadujemy się, że to niestandardowy widok kotwiczony u góry systemowej klawiatury, gdy obiekt staje się first responder. Każdy obiekt dziedziczący po UIResponder może przedefiniować właściwość .inputAccessoryView jako read-write i zarządzać niestandardowym widokiem. Infrastruktura responderów montuje widok i synchronizuje go z klawiaturą systemową. Gesty zamykające klawiaturę (przeciągnięcie lub dotknięcie) są automatycznie stosowane do widżetu pomocniczego. Pozwala to budować interfejsy z interaktywnym zamykaniem klawiatury - kluczową funkcją w aplikacjach takich jak iMessage czy WhatsApp.

Istnieją dwa główne zastosowania dla kotwiczenia widoku nad klawiaturą. Pierwsze to tworzenie paska narzędzi klawiatury, jak selektor tła w kreatorze Facebooka.

W tym scenariuszu klawiatura jest skupiona na polu tekstowym, a widżet pomocniczy dostarcza dodatkowej funkcjonalności kontekstowej. W aplikacji mapowej mogą to być sugestie adresów, a w edytorze tekstu - narzędzia formatowania.


Obiektem Objective-C UIResponder posiadającym <InputAccessoryView> w tym przypadku jest wyraźnie <TextInput> stający się first responderem, który pod maską staje się instancją UITextView lub UITextField.

Drugi typowy scenariusz to przyklejone pola tekstowe:

Tutaj pole tekstowe jest częścią samego widżetu pomocniczego. Rozwiązanie powszechnie stosowane w aplikacjach messengeringowych, gdzie wiadomość można komponować podczas przewijania historii.


Kto posiada <InputAccessoryView> w tym przykładzie? Czy może to być znowu UITextView/UITextField? Pole tekstowe jest WEWNĄTRZ widżetu pomocniczego - tworzy to zależność cykliczną. Rozwiązanie tego problemu to temat na osobny artykuł. Spoiler: właścicielem jest generyczna podklasa UIView, której ręcznie każemy zostać first responderem.

Projekt API

Znając już zastosowania <InputAccessoryView>, kolejnym krokiem było zaprojektowanie API uwzględniającego oba scenariusze i współpracującego z istniejącymi komponentami RN jak <TextInput>.

Dla pasków narzędzi klawiatury istotne były:

  1. Możliwość umieszczenia dowolnej hierarchii widoków RN w <InputAccessoryView>

  2. Zdolność tej odłączonej hierarchii do odbierania dotknięć i modyfikowania stanu aplikacji

  3. Powiązanie <InputAccessoryView> z konkretnym <TextInput>

  4. Możliwość współdzielenia jednego <InputAccessoryView> między wieloma polami tekstowymi

Punkt 1 osiągnęliśmy za pomocą koncepcji zbliżonej do portali React. W tym podejściu "przenosimy" widoki RN do hierarchii UIView zarządzanej przez infrastrukturę responderów. Ponieważ widoki RN renderują się jako UIView, jest to proste - wystarczy nadpisać:

- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex

i przekierować wszystkie subview do nowej hierarchii UIView. Dla punktu 2 skonfigurowaliśmy nowy RCTTouchHandler dla <InputAccessoryView>. Aktualizacje stanu realizowane są standardowymi callbackami. Dla punktów 3-4 użyliśmy pola nativeID do lokalizacji widżetu w kodzie natywnym podczas tworzenia komponentu <TextInput>. Funkcja ta wykorzystuje właściwość .inputAccessoryView natywnego pola tekstowego, skutecznie łącząc <InputAccessoryView> z <TextInput> w ich implementacjach ObjC.

Obsługa przyklejonych pól tekstowych (scenariusz 2) dodaje dodatkowe ograniczenia. Ponieważ pole tekstowe jest dzieckiem widżetu pomocniczego, powiązanie przez nativeID nie wchodzi w grę. Zamiast tego ustawiamy .inputAccessoryView generycznej UIView (poza ekranem) na naszą natywną hierarchię <InputAccessoryView>. Ręczne uczynienie tej UIView first responderem powoduje zamontowanie widżetu przez infrastrukturę responderów - koncepcja dokładnie wyjaśniona we wspomnianym artykule.

Wyzwania

Proces tworzenia tego API nie był pozbawiony wyzwań. Oto napotkane problemy i ich rozwiązania.

Początkowy pomysł zakładał nasłuchiwanie zdarzeń UIKeyboardWill(Show/Hide/ChangeFrame) w NSNotificationCenter. Ten wzorzec stosowany jest w bibliotekach open-source'owych i wewnętrznie w aplikacji Facebooka. Niestety, zdarzenia UIKeyboardDidChangeFrame nie były wywoływane na czas, aby zaktualizować ramkę <InputAccessoryView> podczas przeciągania, a zmiany wysokości klawiatury nie były wychwytywane. Powodowało to błędy typu:

Na iPhone X klawiatura tekstowa i emoji mają różne wysokości. Większość aplikacji używających zdarzeń klawiatury musiała łatać ten błąd. Naszym rozwiązaniem było pełne wykorzystanie .inputAccessoryView, gdzie infrastruktura responderów automatycznie obsługuje takie aktualizacje.


Kolejny podchwytliwy błąd dotyczył omijania przycisku Home na iPhone X. Można pomyśleć: "Apple stworzył safeAreaLayoutGuide właśnie po to!". Byliśmy równie naiwni. Problem? Natywna implementacja <InputAccessoryView> nie ma okna do zakotwiczenia aż do momentu pojawienia się. Rozwiązaliśmy to nadpisując -(BOOL)becomeFirstResponder i wymuszając constraintsy w tym momencie. Ale pojawił się kolejny problem:

Widżet omija przycisk Home, ale treść z obszaru unsafe staje się widoczna. Rozwiązanie znaleźliśmy w tym radarze. Owinęliśmy natywną hierarchię <InputAccessoryView> kontenerem ignorującym safeAreaLayoutGuide. Kontener zakrywa treść w unsafe area, podczas gdy <InputAccessoryView> pozostaje w bezpiecznej strefie.


Przykład użycia

Oto przykład przycisku resetującego stan <TextInput> na pasku narzędzi:

class TextInputAccessoryViewExample extends React.Component<
{},
*,
> {
constructor(props) {
super(props);
this.state = {text: 'Placeholder Text'};
}

render() {
const inputAccessoryViewID = 'inputAccessoryView1';
return (
<View>
<TextInput
style={styles.default}
inputAccessoryViewID={inputAccessoryViewID}
onChangeText={text => this.setState({text})}
value={this.state.text}
/>
<InputAccessoryView nativeID={inputAccessoryViewID}>
<View style={{backgroundColor: 'white'}}>
<Button
onPress={() =>
this.setState({text: 'Placeholder Text'})
}
title="Reset Text"
/>
</View>
</InputAccessoryView>
</View>
);
}
}

Przykład przyklejonych pól tekstowych znajdziesz w repozytorium.

Kiedy będzie dostępne?

Pełna implementacja tej funkcjonalności jest dostępna w tym commicie. Komponent <InputAccessoryView> będzie dostępny w nadchodzącym wydaniu wersji v0.55.0.

Miłego pisania :)

Korzystanie z AWS w React Native

· 10 minut czytania
Richard Threlkeld
Starszy Kierownik Produktu Technicznego w AWS Mobile
Nieoficjalne Tłumaczenie Beta

Ta strona została przetłumaczona przez PageTurner AI (beta). Nie jest oficjalnie zatwierdzona przez projekt. Znalazłeś błąd? Zgłoś problem →

AWS jest powszechnie znanym w branży technologicznej dostawcą usług chmurowych. Obejmują one technologie obliczeniowe, magazynowe i bazodanowe, a także w pełni zarządzane rozwiązania serwerless. Zespół AWS Mobile ściśle współpracuje z klientami i członkami ekosystemu JavaScript, aby aplikacje mobilne i webowe połączone z chmurą były bezpieczniejsze, skalowalne oraz łatwiejsze w tworzeniu i wdrażaniu. Rozpoczęliśmy od kompletu startowego, ale mamy też nowsze rozwiązania.

W tym wpisie omówimy kilka ciekawych rozwiązań dla developerów React i React Native:

  • AWS Amplify, deklaratywna biblioteka dla aplikacji JavaScript korzystających z usług chmurowych

  • AWS AppSync, w pełni zarządzana usługa GraphQL z funkcjami offline i czasu rzeczywistego

AWS Amplify

Aplikacje React Native są bardzo łatwe do uruchomienia przy użyciu narzędzi takich jak Create React Native App czy Expo. Jednak połączenie ich z chmurą może stanowić wyzwanie, gdy próbujesz dopasować przypadki użycia do usług infrastrukturalnych. Na przykład Twoja aplikacja React Native może wymagać przesyłania zdjęć. Czy powinny być one chronione per użytkownik? To prawdopodobnie oznacza potrzebę rejestracji lub logowania. Chcesz mieć własny katalog użytkowników czy korzystasz z dostawcy mediów społecznościowych? Być może aplikacja potrzebuje też wywoływać API z własną logiką biznesową po zalogowaniu użytkowników.

Aby pomóc developerom JavaScript w tych problemach, wydaliśmy bibliotekę o nazwie AWS Amplify. Jej projekt podzielono na "kategorie" zadań, zamiast implementacji specyficznych dla AWS. Na przykład, jeśli chcesz, aby użytkownicy rejestrowali się, logowali, a następnie przesyłali prywatne zdjęcia, wystarczy dodać do aplikacji kategorie Auth i Storage:

import { Auth } from 'aws-amplify';

Auth.signIn(username, password)
.then(user => console.log(user))
.catch(err => console.log(err));

Auth.confirmSignIn(user, code)
.then(data => console.log(data))
.catch(err => console.log(err));

W powyższym kodzie widać przykłady typowych zadań, w których Amplify może pomóc, jak używanie kodów uwierzytelniania wieloskładnikowego (MFA) przez e-mail lub SMS. Obecnie obsługiwane kategorie to:

  • Auth: Zapewnia automatyzację poświadczeń. Gotowa implementacja wykorzystuje dane uwierzytelniające AWS do podpisywania oraz tokeny JWT OIDC z Amazon Cognito. Obsługiwane są typowe funkcje, takie jak MFA.

  • Analytics: Jedną linijką kodu uzyskaj śledzenie uwierzytelnionych i nieuwierzytelnionych użytkowników w Amazon Pinpoint. Możesz rozszerzyć to o własne metryki lub atrybuty.

  • API: Zapewnia bezpieczną interakcję z interfejsami RESTful API, wykorzystując AWS Signature Version 4. Moduł API doskonale sprawdza się w infrastrukturach serwerless z Amazon API Gateway.

  • Storage: Uproszczone polecenia do przesyłania, pobierania i listowania treści w Amazon S3. Możesz też łatwo grupować dane jako publiczne lub prywatne per użytkownik.

  • Caching: Interfejs pamięci podręcznej LRU działający zarówno w aplikacjach webowych, jak i React Native, wykorzystujący specyficzną dla implementacji trwałość.

  • i18n and Logging: Zapewnia możliwości internacjonalizacji i lokalizacji oraz debugowania i logowania.

Jedną z zalet Amplify jest wbudowanie "najlepszych praktyk" w projekt dostosowany do Twojego środowiska programistycznego. Przykładowo, zauważyliśmy współpracując z klientami i developerami React Native, że skróty stosowane podczas rozwoju aplikacji dla szybkiego efektu często trafiały do środowisk produkcyjnych. To może naruszać skalowalność lub bezpieczeństwo, wymuszając przebudowę infrastruktury i refaktoryzację kodu.

Jednym z przykładów, jak pomagamy programistom uniknąć tego problemu, są Referencyjne architektury bezserwerowe z AWS Lambda. Pokazują one najlepsze praktyki współpracy Amazon API Gateway i AWS Lambda przy budowaniu zaplecza. Ten wzorzec został wbudowany w kategorię API Amplify. Możesz użyć tego wzorca do interakcji z wieloma różnymi punktami końcowymi REST i przekazywania nagłówków bezpośrednio do funkcji Lambda dla niestandardowej logiki biznesowej. Udostępniliśmy również AWS Mobile CLI do inicjowania nowych lub istniejących projektów React Native z tymi funkcjami. Aby rozpocząć, zainstaluj poprzez npm i postępuj zgodnie z monitami konfiguracyjnymi:

npm install --global awsmobile-cli
awsmobile configure

Kolejnym przykładem wbudowanych najlepszych praktyk specyficznych dla ekosystemu mobilnego jest bezpieczeństwo haseł. Domyślna implementacja kategorii Auth wykorzystuje pule użytkowników Amazon Cognito do rejestracji i logowania. Ta usługa implementuje protokół Secure Remote Password jako sposób ochrony użytkowników podczas prób uwierzytelniania. Jeśli chcesz zgłębić matematyczne podstawy protokołu, zauważysz, że musisz użyć dużej liczby pierwszej przy obliczaniu weryfikatora hasła nad pierwiastkiem pierwotnym do generowania grupy. W środowiskach React Native JIT jest wyłączony. To sprawia, że obliczenia BigInteger dla operacji bezpieczeństwa takich jak ta są mniej wydajne. Aby to zrekompensować, udostępniliśmy natywne mosty w Androidzie i iOS, które możesz podlinkować w swoim projekcie:

npm install --save aws-amplify-react-native
react-native link amazon-cognito-identity-js

Cieszymy się również, widząc że zespół Expo uwzględnił to w swoim najnowszym SDK, dzięki czemu możesz używać Amplify bez konieczności ejectowania.

Wreszcie, specjalnie dla rozwoju React Native (i React), Amplify zawiera komponenty wyższego rzędu (HOCs) do łatwego opakowywania funkcjonalności, takich jak rejestracja i logowanie w twojej aplikacji:

import Amplify, { withAuthenticator } from 'aws-amplify-react-native';
import aws_exports from './aws-exports';

Amplify.configure(aws_exports);

class App extends React.Component {
...
}

export default withAuthenticator(App);

Podstawowy komponent jest również dostarczany jako <Authenticator />, co daje pełną kontrolę nad dostosowywaniem interfejsu użytkownika. Dostarcza także właściwości do zarządzania stanem użytkownika, np. czy zalogował się czy czeka na potwierdzenie MFA, oraz callbacki, które możesz wywołać przy zmianie stanu.

Podobnie znajdziesz ogólne komponenty React do różnych przypadków użycia. Możesz je dostosować do swoich potrzeb, np. aby pokazać wszystkie prywatne obrazy z Amazon S3 w module Storage:

<S3Album
level="private"
path={path}
filter={(item) => /jpg/i.test(item.path)}/>

Możesz kontrolować wiele funkcji komponentów poprzez props, jak pokazano wcześniej, z opcjami publicznego lub prywatnego przechowywania. Istnieją nawet możliwości automatycznego zbierania analiz, gdy użytkownicy wchodzą w interakcję z określonymi komponentami UI:

return <S3Album track/>

AWS Amplify preferuje konwencję nad konfiguracją w stylu rozwoju, z globalną procedurą inicjalizacji lub inicjalizacją na poziomie kategorii. Najszybszym sposobem na rozpoczęcie jest użycie pliku aws-exports. Jednak programiści mogą również używać biblioteki niezależnie z istniejącymi zasobami.

Aby zgłębić filozofię i zobaczyć pełne demo, obejrzyj film z AWS re:Invent.

AWS AppSync

Krótko po premierze AWS Amplify, udostępniliśmy również AWS AppSync. To w pełni zarządzana usługa GraphQL z funkcjami offline i działania w czasie rzeczywistym. Chociaż możesz używać GraphQL w różnych językach programowania klienckiego (w tym natywnych Android i iOS), jest ona szczególnie popularna wśród programistów React Native. Dzieje się tak, ponieważ model danych doskonale wpasowuje się w jednokierunkowy przepływ danych i hierarchię komponentów.

AWS AppSync umożliwia łączenie się z zasobami w twoim własnym koncie AWS, co oznacza, że posiadasz i kontrolujesz swoje dane. Odbywa się to poprzez użycie źródeł danych, a usługa obsługuje Amazon DynamoDB, Amazon Elasticsearch oraz AWS Lambda. Dzięki temu możesz łączyć funkcjonalności (takie jak NoSQL i wyszukiwanie pełnotekstowe) w pojedynczym interfejsie API GraphQL jako schemat. Pozwala to na mieszanie i dopasowywanie źródeł danych. Usługa AppSync może również tworzyć zasoby na podstawie schematu, więc jeśli nie znasz usług AWS, możesz napisać schemat GraphQL w SDL, kliknąć przycisk i automatycznie rozpocząć pracę.

Funkcjonalność czasu rzeczywistego w AWS AppSync jest kontrolowana poprzez subskrypcje GraphQL z dobrze znanym wzorcem opartym na zdarzeniach. Ponieważ subskrypcje w AWS AppSync są kontrolowane na poziomie schematu za pomocą dyrektywy GraphQL, a schemat może używać dowolnego źródła danych, oznacza to, że możesz wyzwalać powiadomienia z operacji bazodanowych przy użyciu Amazon DynamoDB i Amazon Elasticsearch Service, lub z innych części twojej infrastruktury za pomocą AWS Lambda.

Podobnie jak w AWS Amplify, możesz używać funkcji bezpieczeństwa klasy enterprise w swoim interfejsie API GraphQL z AWS AppSync. Usługa pozwala szybko rozpocząć pracę z kluczami API. Jednak gdy przechodzisz do produkcji, możesz przejść na użycie AWS Identity and Access Management (IAM) lub tokenów OIDC z pul użytkowników Amazon Cognito. Możesz kontrolować dostęp na poziomie resolvera za pomocą polityk na typach. Możesz nawet używać sprawdzeń logicznych dla szczegółowej kontroli dostępu w czasie wykonywania, takich jak wykrywanie, czy użytkownik jest właścicielem określonego zasobu bazy danych. Istnieją również możliwości sprawdzania przynależności do grup w celu wykonywania resolverów lub dostępu do poszczególnych rekordów bazy danych.

Aby pomóc programistom React Native w poznaniu tych technologii, dostępny jest wbudowany przykładowy schemat GraphQL, który możesz uruchomić na stronie głównej konsoli AWS AppSync. Ten przykład wdraża schemat GraphQL, tworzy tabele bazy danych i automatycznie łączy zapytania, mutacje oraz subskrypcje. Dostępny jest również działający przykład dla React Native dla AWS AppSync, który wykorzystuje ten wbudowany schemat (oraz przykład dla React), co pozwala uruchomić zarówno komponenty klienckie, jak i chmurowe w ciągu kilku minut.

Rozpoczęcie pracy jest proste, gdy używasz AWSAppSyncClient, który podłącza się do Apollo Client. AWSAppSyncClient obsługuje bezpieczeństwo i podpisywanie dla twojego interfejsu API GraphQL, funkcjonalność offline oraz proces uzgadniania i negocjacji subskrypcji:

import AWSAppSyncClient from "aws-appsync";
import { Rehydrated } from 'aws-appsync-react';
import { AUTH_TYPE } from "aws-appsync/lib/link/auth-link";

const client = new AWSAppSyncClient({
url: awsconfig.graphqlEndpoint,
region: awsconfig.region,
auth: {type: AUTH_TYPE.API_KEY, apiKey: awsconfig.apiKey}
});

Konsola AppSync udostępnia plik konfiguracyjny do pobrania, który zawiera twój punkt końcowy GraphQL, region AWS i klucz API. Następnie możesz użyć klienta z React Apollo:

const WithProvider = () => (
<ApolloProvider client={client}>
<Rehydrated>
<App />
</Rehydrated>
</ApolloProvider>
);

W tym momencie możesz używać standardowych zapytań GraphQL:

query ListEvents {
listEvents{
items{
__typename
id
name
where
when
description
comments{
__typename
items{
__typename
eventId
commentId
content
createdAt
}
nextToken
}
}
}
}

Powyższy przykład pokazuje zapytanie z przykładowym schematem aplikacji utworzonym przez AppSync. Prezentuje on nie tylko interakcję z DynamoDB, ale także zawiera paginację danych (w tym zaszyfrowanych tokenów) oraz relacje typów między Events i Comments. Ponieważ aplikacja jest skonfigurowana z AWSAppSyncClient, dane są automatycznie utrwalane offline i będą synchronizowane, gdy urządzenia ponownie połączą się z siecią.

Możesz zobaczyć dogłębną analizę technologii klienckiej stojącej za tym oraz demo React Native w tym filmie.

Opinie

Zespół stojący za tymi bibliotekami jest bardzo ciekaw, jak działają one dla Ciebie. Chcemy również usłyszeć, co jeszcze możemy zrobić, aby ułatwić Ci tworzenie aplikacji w React i React Native z wykorzystaniem usług w chmurze. Skontaktuj się z zespołem AWS Mobile na GitHubie w sprawie AWS Amplify lub AWS AppSync.

Implementacja animacji ładowania aplikacji Twittera w React Native

· 9 minut czytania
Eli White
Eli White
Software Engineer @ Meta
Nieoficjalne Tłumaczenie Beta

Ta strona została przetłumaczona przez PageTurner AI (beta). Nie jest oficjalnie zatwierdzona przez projekt. Znalazłeś błąd? Zgłoś problem →

Animacja ładowania w aplikacji Twittera na iOS bardzo mi się podoba.

Gdy aplikacja jest gotowa, logo Twittera rozwija się w przyjemny sposób, odsłaniając aplikację.

Postanowiłem odtworzyć tę animację ładowania za pomocą React Native.


Aby zrozumieć, jak to zbudować, musiałem najpierw rozłożyć animację na części. Najłatwiej dostrzec subtelności, spowalniając ją.

Musimy rozwiązać kilka kluczowych elementów tej animacji.

  1. Skalowanie ptaka (logo).

  2. Podczas powiększania ptaka – odsłanianie aplikacji znajdującej się pod spodem

  3. Lekkie pomniejszenie aplikacji na końcu animacji

Odtworzenie tej animacji zajęło mi sporo czasu.

Początkowo przyjąłem błędne założenie, że niebieskie tło i ptak Twittera stanowią warstwę na wierzchu aplikacji, a podczas powiększania logo stawało się przeźroczyste, odsłaniając aplikację. To podejście nie działa, bo przeźroczyste logo pokazałoby niebieską warstwę, a nie aplikację pod spodem!

Na szczęście dla was, drodzy czytelnicy, oszczędzę wam tych frustracji. W tym poradniku od razu przejdziemy do konkretów!


Prawidłowe rozwiązanie

Zanim przejdziemy do kodu, warto zrozumieć rozkład warstw. Aby zobrazować ten efekt, odtworzyłem go w CodePen (osadzony poniżej), gdzie możecie interaktywnie zobaczyć poszczególne warstwy.

Efekty tworzą trzy główne warstwy. Pierwsza to niebieska warstwa tła. Choć wydaje się być na wierzchu aplikacji, tak naprawdę znajduje się z tyłu.

Następnie mamy jednolitą białą warstwę. Na samym przodzie znajduje się nasza aplikacja.


Kluczowa sztuczka to użycie logo Twittera jako mask, która zakrywa zarówno aplikację, jak i białą warstwę. Nie będę szczegółowo omawiał maskowania — w sieci znajdziecie mnóstwo zasobów na ten temat.

Podstawowa zasada maskowania: nieprzeźroczyste piksele maski odsłaniają zawartość, którą zakrywają, a przeźroczyste piksele maski ją ukrywają.

Logo Twittera służy jako maska dla dwóch warstw: jednolitej białej warstwy i warstwy aplikacji.

Aby odsłonić aplikację, powiększamy maskę, aż przekroczy rozmiar całego ekranu.

Podczas powiększania maski zwiększamy przezroczystość warstwy aplikacji, odsłaniając ją i ukrywając białą warstwę pod spodem. Na koniec efektu: warstwę aplikacji początkowo skalujemy do rozmiaru >1, a następnie zmniejszamy do 1 pod koniec animacji. Potem ukrywamy warstwy nie-aplikacyjne, bo nie będą już widoczne.

Mówi się, że obraz wart jest 1000 słów. A ile słów warta jest interaktywna wizualizacja? Klikajcie przycisk "Next Step", by przejść przez animację. Widok warstw pokazuje perspektywę boczną. Siatka pomaga wizualizować przeźroczyste warstwy.

Czas na React Native

Dobra. Skoro wiemy, co budujemy i jak działa animacja, pora przejść do kodu — czyli tego, po co tu właściwie jesteście.

Kluczowym elementem jest MaskedViewIOS, rdzenny komponent React Native.

import {MaskedViewIOS} from 'react-native';

<MaskedViewIOS maskElement={<Text>Basic Mask</Text>}>
<View style={{backgroundColor: 'blue'}} />
</MaskedViewIOS>;

MaskedViewIOS przyjmuje właściwości maskElement i children. Elementy children są maskowane przez maskElement. Warto zauważyć, że maska nie musi być obrazem – może to być dowolny widok. W powyższym przykładzie efektem będzie wyrenderowanie niebieskiego widoku, który będzie widoczny tylko tam, gdzie znajdują się słowa "Basic Mask" z maskElement. Właśnie stworzyliśmy skomplikowany niebieski tekst.

Chcemy wyrenderować naszą niebieską warstwę, a na wierzchu umieścić naszą maskowaną warstwę aplikacji i białą warstwę z logo Twittera.

{
fullScreenBlueLayer;
}
<MaskedViewIOS
style={{flex: 1}}
maskElement={
<View style={styles.centeredFullScreen}>
<Image source={twitterLogo} />
</View>
}>
{fullScreenWhiteLayer}
<View style={{flex: 1}}>
<MyApp />
</View>
</MaskedViewIOS>;

Da nam to warstwy widoczne poniżej.

Teraz część animowana

Mamy już wszystkie elementy potrzebne do działania, następnym krokiem jest ich animowanie. Aby animacja była płynna, użyjemy Animated API React Native.

Animated pozwala nam deklaratywnie definiować animacje w JavaScript. Domyślnie te animacje działają w JavaScripcie i mówią warstwie natywnej, jakie zmiany wprowadzać w każdej klatce. Mimo że JavaScript będzie próbował aktualizować animację w każdej klatce, prawdopodobnie nie będzie w stanie robić tego wystarczająco szybko, co spowoduje utratę klatek (jank). Tego właśnie nie chcemy!

Animated ma specjalne zachowanie, które pozwala uniknąć tego problemu. Flaga useNativeDriver wysyła definicję animacji z JavaScriptu do warstwy natywnej na początku animacji, pozwalając stronie natywnej przetwarzać aktualizacje bez konieczności komunikacji z JavaScriptem w każdej klatce. Ograniczeniem useNativeDriver jest to, że można animować tylko określony zestaw właściwości, głównie transform i opacity. Nie można animować np. koloru tła za pomocą useNativeDriver – przynajmniej na razie. Z czasem dodamy więcej możliwości, a tymczasem zawsze możesz złożyć PR z brakującymi właściwościami dla swojego projektu, co przysłuży się całej społeczności 😀.

Ponieważ zależy nam na płynności animacji, będziemy działać w ramach tych ograniczeń. Więcej o działaniu useNativeDriver możesz przeczytać w naszym poście na blogu.

Dekonstrukcja naszej animacji

Nasza animacja składa się z 4 elementów:

  1. Powiększenie ptaka, odsłaniając aplikację i jednolitą białą warstwę

  2. Wygaszenie aplikacji (fade in)

  3. Pomniejszenie aplikacji

  4. Ukrycie białej i niebieskiej warstwy po zakończeniu

W Animated mamy dwa główne sposoby definiowania animacji. Pierwszy to Animated.timing, który pozwala określić dokładny czas trwania animacji wraz z krzywą wygładzania ruchu. Drugi to fizyczne API jak Animated.spring. Z Animated.spring podajesz parametry jak tarcie i napięcie sprężyny, pozwalając fizyce sterować animacją.

Mamy wiele animacji, które mają działać równocześnie i są ze sobą ściśle powiązane. Na przykład chcemy, aby aplikacja zaczęła wygasać w trakcie odsłaniania maski. Ponieważ te animacje są tak powiązane, użyjemy Animated.timing z pojedynczą wartością Animated.Value.

Animated.Value to opakowanie wartości natywnej, której Animated używa do śledzenia stanu animacji. Zazwyczaj wystarczy jedna taka wartość dla całej animacji. Komponenty używające Animated przechowują tę wartość w stanie.

Ponieważ myślę o tej animacji jako o krokach występujących w różnych momentach jej trwania, zaczniemy od wartości Animated.Value równej 0 (0% ukończenia), a kończymy na 100 (100% ukończenia).

Początkowy stan naszego komponentu będzie następujący.

state = {
loadingProgress: new Animated.Value(0),
};

Gdy będziemy gotowi rozpocząć animację, każemy Animated animować tę wartość do 100.

Animated.timing(this.state.loadingProgress, {
toValue: 100,
duration: 1000,
useNativeDriver: true, // This is important!
}).start();

Następnie próbuję określić przybliżone wartości dla różnych elementów animacji w poszczególnych etapach. Poniższa tabela przedstawia różne komponenty animacji oraz proponowane wartości w miarę postępu czasu.

Maska z logiem Twittera powinna zaczynać od skali 1, potem lekko się zmniejszyć, by następnie gwałtownie powiększyć. Zatem przy 10% animacji powinna mieć wartość skali około 0.8, by na końcu osiągnąć skalę 70. Wybór 70 był dość arbitralny - musiała być na tyle duża, by ptak całkowicie odsłonił ekran, a 60 okazało się za małe 😀. Co ciekawe, im wyższa wartość, tym szybciej będzie się wydawało, że maska rośnie, bo musi osiągnąć większy rozmiar w tym samym czasie. Ta wartość wymagała kilku prób, by dobrze współgrała z tym logo. Inne rozmiary logo lub urządzeń będą wymagały innej końcowej skali, by zapewnić pełne odsłonięcie ekranu.

Aplikacja powinna pozostać niewidoczna przez dłuższy czas, przynajmniej do momentu, gdy logo Twittera zacznie rosnąć. Bazując na oryginalnej animacji, chcę zacząć ją pokazywać, gdy ptak jest w połowie skali, i w pełni ujawnić dość szybko. Zatem przy 15% zaczynamy ją pokazywać, a przy 30% całej animacji jest już w pełni widoczna.

Skala aplikacji zaczyna się od 1.1 i zmniejsza do normalnej skali pod koniec animacji.

A teraz w kodzie.

To, co zrobiliśmy powyżej, to mapowanie wartości z postępu animacji (w procentach) na wartości poszczególnych elementów. Robimy to w Animated za pomocą .interpolate. Tworzymy 3 różne obiekty stylów - jeden dla każdego elementu animacji - używając wartości interpolowanych na podstawie this.state.loadingProgress.

const loadingProgress = this.state.loadingProgress;

const opacityClearToVisible = {
opacity: loadingProgress.interpolate({
inputRange: [0, 15, 30],
outputRange: [0, 0, 1],
extrapolate: 'clamp',
// clamp means when the input is 30-100, output should stay at 1
}),
};

const imageScale = {
transform: [
{
scale: loadingProgress.interpolate({
inputRange: [0, 10, 100],
outputRange: [1, 0.8, 70],
}),
},
],
};

const appScale = {
transform: [
{
scale: loadingProgress.interpolate({
inputRange: [0, 100],
outputRange: [1.1, 1],
}),
},
],
};

Mając te obiekty stylów, możemy ich użyć podczas renderowania fragmentu widoku z początku wpisu. Pamiętaj, że tylko Animated.View, Animated.Text i Animated.Image mogą używać obiektów stylów opartych na Animated.Value.

const fullScreenBlueLayer = (
<View style={styles.fullScreenBlueLayer} />
);
const fullScreenWhiteLayer = (
<View style={styles.fullScreenWhiteLayer} />
);

return (
<View style={styles.fullScreen}>
{fullScreenBlueLayer}
<MaskedViewIOS
style={{flex: 1}}
maskElement={
<View style={styles.centeredFullScreen}>
<Animated.Image
style={[styles.maskImageStyle, imageScale]}
source={twitterLogo}
/>
</View>
}>
{fullScreenWhiteLayer}
<Animated.View
style={[opacityClearToVisible, appScale, {flex: 1}]}>
{this.props.children}
</Animated.View>
</MaskedViewIOS>
</View>
);

Jupi! Elementy animacji wyglądają teraz tak, jak chcemy. Pozostaje tylko posprzątać niebieską i białą warstwę, które nigdy więcej nie będą widoczne.

Aby wiedzieć, kiedy możemy je usunąć, potrzebujemy znać moment zakończenia animacji. Na szczęście Animated.timing w metodzie .start przyjmuje opcjonalne callbacki wykonywane po zakończeniu animacji.

Animated.timing(this.state.loadingProgress, {
toValue: 100,
duration: 1000,
useNativeDriver: true,
}).start(() => {
this.setState({
animationDone: true,
});
});

Mając w state wartość informującą o zakończeniu animacji, możemy zmodyfikować nasze warstwy.

const fullScreenBlueLayer = this.state.animationDone ? null : (
<View style={[styles.fullScreenBlueLayer]} />
);
const fullScreenWhiteLayer = this.state.animationDone ? null : (
<View style={[styles.fullScreenWhiteLayer]} />
);

Voilà! Animacja działa, a nieużywane warstwy są czyszczone po jej zakończeniu. Odtworzyliśmy animację ładowania aplikacji Twittera!

Chwila, u mnie nie działa!

Spokojnie, drogi czytelniku. Sam nie cierpię, gdy poradniki podają tylko fragmenty kodu bez pełnego źródła.

Ten komponent został opublikowany na npm i jest dostępny na GitHubie jako react-native-mask-loader. Aby przetestować na telefonie, wersja na Expo jest dostępna tutaj:

Dalsza lektura / Zadania dodatkowe

  1. Ta książka to świetne źródło wiedzy o Animated po przeczytaniu dokumentacji React Native.

  2. Oryginalna animacja Twittera przyspiesza odsłanianie maski pod koniec. Spróbuj zmodyfikować loader, używając innej funkcji easingu (lub springa!), by lepiej odwzorować to zachowanie.

  3. Obecna końcowa skala maski jest zakodowana na sztywno i może nie odsłaniać całej aplikacji na tablecie. Obliczanie końcowej skali na podstawie rozmiaru ekranu i obrazu byłoby świetnym PR-em.

Miesięczne podsumowanie React Native #6

· 4 minuty czytania
Tomislav Tenodi
Założyciel Speck
Nieoficjalne Tłumaczenie Beta

Ta strona została przetłumaczona przez PageTurner AI (beta). Nie jest oficjalnie zatwierdzona przez projekt. Znalazłeś błąd? Zgłoś problem →

Comiesięczne spotkania React Native wciąż trwają w pełni! Sprawdź notatkę na dole tego posta, aby poznać terminy kolejnych sesji.

Expo

  • Gratulacje dla Devin Abbott i Houssein Djirdeh z okazji przedpremierowej publikacji książki "Full Stack React Native"! Poprowadzi ona czytelników przez naukę React Native poprzez budowanie kilku małych aplikacji.

  • Wydano pierwszą (eksperymentalną) wersję reason-react-native-scripts, która pomoże łatwo wypróbować ReasonML.

  • Expo SDK 24 zostało wydane! Wykorzystuje React Native 0.51 i zawiera mnóstwo nowych funkcji i ulepszeń: dołączanie obrazów w aplikacjach standalone (brak konieczności cache'owania przy pierwszym ładowaniu!), API do manipulacji obrazami (przycinanie, zmiana rozmiaru, obracanie, odbicie), API do wykrywania twarzy, nowe funkcje kanałów wydań (ustawianie aktywnego wydania dla danego kanału i wycofywanie), panel internetowy do śledzenia budowania aplikacji standalone oraz naprawienie długo istniejącego błędu w implementacji OpenGL dla Androida i wielozadaniowości Androida, by wymienić tylko kilka.

  • Od stycznia przeznaczamy więcej zasobów na React Navigation. Silnie wierzymy, że jest możliwe i pożądane budowanie nawigacji React Native przy użyciu komponentów React i prymitywów takich jak Animated oraz react-native-gesture-handler, i jesteśmy naprawdę podekscytowani niektórymi zaplanowanymi ulepszeniami. Jeśli szukasz możliwości, by przyczynić się do społeczności, sprawdź react-native-maps i react-native-svg — oba projekty mogą skorzystać z pomocy!

Infinite Red

Microsoft

  • Rozpoczęto pull request w celu migracji rdzenia mostka React Native Windows do .NET Standard, co uczyni go efektywnie niezależnym od systemu operacyjnego. Mamy nadzieję, że wiele innych platform .NET Core będzie mogło rozszerzyć mostek własnymi modelami wątkowości, środowiskami uruchomieniowymi JavaScript i UIManagerami (pomyśl o JavaScriptCore, Xamarin.Mac, Linux Gtk# i opcjach Samsung Tizen).

Wix

  • Detox

    • Aby móc skalować testy E2E, chcemy zminimalizować czas spędzany na CI, pracujemy nad wsparciem dla równoległości w Detox.
    • Zgłoszono pull request w celu umożliwienia wsparcia dla niestandardowych wersji buildów, aby lepiej wspierać mocking w testach E2E.
  • DetoxInstruments

    • Praca nad przełomową funkcją DetoxInstruments okazuje się bardzo wymagającym zadaniem. Tworzenie śladu stosu JavaScript w dowolnym momencie wymaga niestandardowej implementacji JSCore obsługującej zawieszanie wątku JS. Testy profilera wewnętrznie w aplikacji Wix ujawniły ciekawe wnioski dotyczące wątku JS.
    • Projekt nadal nie jest wystarczająco stabilny do ogólnego użytku, ale trwają nad nim aktywne prace i mamy nadzieję wkrótce go ogłosić.
  • React Native Navigation

    • Tempo rozwoju wersji 2 znacząco przyspieszyło. Dotychczas tylko jeden programista pracował nad projektem przez 20% swojego czasu, obecnie trzech programistów pracuje nad nim na pełen etat!
  • Wydajność Androida

    • Zastąpienie starego JSCore dołączonego do RN jego najnowszą wersją (szczytową wersją projektu webkitGTK z niestandardową konfiguracją JIT) przyniosło 40% wzrost wydajności wątku JS. Kolejnym krokiem jest kompilacja wersji 64-bitowej. Prace te opierają się na skryptach budujących JSC dla Androida. Aktualny status można śledzić tutaj.

Kolejne spotkania

Trwała dyskusja na temat przekształcenia tych spotkań w dyskusje skupione na jednym konkretnym temacie (np. nawigacja, przenoszenie modułów React Native do oddzielnych repozytoriów, dokumentacja, ...). Wierzymy, że w ten sposób najlepiej przysłużymy się społeczności React Native. Zmiana może nastąpić już podczas kolejnego spotkania. Zachęcamy do podzielenia się na Twitterze tematami, które chcielibyście zobaczyć.

Miesięcznik React Native #5

· 4 minuty czytania
Tomislav Tenodi
Założyciel w Speck
Nieoficjalne Tłumaczenie Beta

Ta strona została przetłumaczona przez PageTurner AI (beta). Nie jest oficjalnie zatwierdzona przez projekt. Znalazłeś błąd? Zgłoś problem →

Kolejne miesięczne spotkanie React Native! Zobaczmy, co robią nasze zespoły.

Callstack

  • Pracowaliśmy nad ciągłą integracją (CI) React Native. Najważniejsze, że migrowaliśmy z Travisa na Circle, zapewniając React Native jednolity, spójny proces CI.

  • Zorganizowaliśmy Hacktoberfest - edycja React Native, gdzie wraz z uczestnikami składaliśmy liczne pull requesty do projektów open source.

  • Kontynuujemy prace nad Haul. W zeszłym miesiącu wydaliśmy dwie nowe wersje, w tym wsparcie dla webpacka 3. Planujemy dodać obsługę CRNA i Expo oraz ulepszyć HMR. Nasz plan rozwoju jest publicznie dostępny w systemie śledzenia problemów. Jeśli chcesz zgłosić sugestie lub opinię, daj nam znać!

Expo

  • Wydano Expo SDK 22 (oparte na React Native 0.49) i zaktualizowano CRNA.

    • Zawiera ulepszone API ekranu startowego, podstawowe wsparcie ARKit, API "DeviceMotion", obsługę SFAuthenticationSession w iOS11 oraz więcej.
  • Twoje snacki mogą teraz zawierać wiele plików JavaScript, a obrazy i inne zasoby możesz przesyłać przeciągając je do edytora.

  • Wspieraliśmy rozwój react-navigation dodając obsługę iPhone'a X.

  • Skupiamy się na wygładzaniu niedoskonałości przy budowaniu dużych aplikacji w Expo. Przykładowo:

    • Wsparcie najwyższej klasy dla wdrażania w wielu środowiskach: staging, produkcja i dowolne kanały. Kanały będą obsługiwać wycofywanie wersji i ustawianie aktywnej wersji dla kanału. Daj znać jeśli chcesz być testerem wczesnej wersji: @expo_io.
    • Pracujemy też nad ulepszeniem infrastruktury budowania samodzielnych aplikacji oraz dodajemy możliwość dołączania obrazów i innych zasobów niebędących kodem, przy zachowaniu możliwości aktualizacji zasobów przez sieć.

Facebook

  • Lepsze wsparcie języków RTL:

    • Wprowadzamy kierunkowo-świadome style:
      • Pozycja:
        • (left|right) → (start|end)
      • Margines:
        • margin(Left|Right) → margin(Start|End)
      • Dopełnienie:
        • padding(Left|Right) → padding(Start|End)
      • Obramowanie:
        • borderTop(Left|Right)Radius → borderTop(Start|End)Radius
        • borderBottom(Left|Right)Radius → borderBottom(Start|End)Radius
        • border(Left|Right)Width → border(Start|End)Width
        • border(Left|Right)Color → border(Start|End)Color
    • Znaczenie "left" i "right" było zamienione w układach RTL dla stylów pozycji, marginesów, dopełnienia i obramowania. W ciągu kilku miesięcy usuniemy to zachowanie - "left" zawsze będzie oznaczać lewą stronę, a "right" prawą. Zmiany łamiące kompatybilność są ukryte pod flagą. Użyj I18nManager.swapLeftAndRightInRTL(false) w komponentach React Native, aby je włączyć.
  • Pracujemy nad typowaniem naszych wewnętrznych modułów natywnych w Flow i wykorzystujemy to do generowania interfejsów w Javie oraz protokołów w ObjC, które muszą implementować natywne implementacje. Mamy nadzieję, że ten system generowania kodu stanie się open source najwcześniej w przyszłym roku.

Infinite Red

  • Nowe narzędzie open source wspierające React Native i inne projekty. Więcej informacji tutaj.

  • Gruntowna modernizacja Ignite przed nową wersją boilerplate (kryptonim: Bowser)

Shoutem

  • Usprawniamy proces rozwoju na platformie Shoutem. Chcemy uspójnić ścieżkę od tworzenia aplikacji do pierwszej niestandardowej ekranu, obniżając próg wejścia dla nowych developerów React Native. Przygotowaliśmy warsztaty testujące nowe funkcje. Ulepszyliśmy również Shoutem CLI pod kątem nowych przepływów.

  • Shoutem UI otrzymało ulepszenia komponentów i poprawki błędów. Sprawdziliśmy też kompatybilność z najnowszymi wersjami React Native.

  • Platforma Shoutem zyskała istotne aktualizacje, a nowe integracje są dostępne w ramach projektu rozszerzeń open source. Cieszymy się widząc aktywny rozwój rozszerzeń Shoutem przez innych developerów. Aktywnie kontaktujemy się z nimi, oferując porady i wsparcie.

Kolejne spotkanie

Kolejna sesja odbędzie się w środę, 6 grudnia 2017. Śmiało daj mi znać na Twitterze, jeśli masz pomysły jak usprawnić rezultaty naszych spotkań.

Miesięczne podsumowanie React Native #4

· 3 minuty czytania
Mike Grabowski
Mike Grabowski
CTO and Co-Founder @ Callstack
Nieoficjalne Tłumaczenie Beta

Ta strona została przetłumaczona przez PageTurner AI (beta). Nie jest oficjalnie zatwierdzona przez projekt. Znalazłeś błąd? Zgłoś problem →

Miesięczne spotkanie React Native trwa! Oto notatki od każdego zespołu:

Callstack

  • React Native EU dobiegło końca. Ponad 300 uczestników z 33 krajów odwiedziło Wrocław. Wystąpienia można znaleźć na YouTube.

  • Po konferencji powoli wracamy do naszego harmonogramu open source. Warto wspomnieć, że pracujemy nad kolejną wersją react-native-opentok, która naprawia większość istniejących problemów.

GeekyAnts

Staramy się obniżyć próg wejścia dla deweloperów zaczynających przygodę z React Native, wprowadzając:

  • Ogłoszono BuilderX.io na React Native EU. BuilderX to narzędzie projektowe, które bezpośrednio współpracuje z plikami JavaScript (obecnie obsługiwane jest tylko React Native) w celu generowania pięknego, czytelnego i edytowalnego kodu.

  • Uruchomiono ReactNativeSeed.com dostarczający zestaw szablonów dla twojego kolejnego projektu React Native. Oferuje różne opcje, w tym TypeScript i Flow do typów danych, MobX, Redux oraz mobx-state-tree do zarządzania stanem, z CRNA i czystym React-Native jako stosem technologicznym.

Expo

  • Wkrótce wydamy SDK 21, dodające obsługę react-native 0.48.3 oraz zestaw poprawek błędów, ulepszeń stabilności i nowych funkcji w Expo SDK, w tym nagrywanie wideo, nowe API ekranu startowego, obsługę react-native-gesture-handler oraz ulepszoną obsługę błędów.

  • Odnośnie react-native-gesture-handler, Krzysztof Magiera z Software Mansion kontynuuje rozwój tej biblioteki, a my wspieramy go testami i finansowaniem części czasu deweloperskiego. Integracja z Expo w SDK21 pozwoli łatwo eksperymentować z nią w Snacku - nie możemy się doczekać, co społeczność stworzy.

  • Odnośnie ulepszonego logowania/obsługi błędów - szczegóły implementacji znajdziesz w tym gist wewnętrznego PR Expo (szczególnie "Problem 2") oraz w tym commicie dotyczącym obsługi błędów importu standardowych modułów npm. To doskonała okazja do ulepszania komunikatów błędów w samym React Native - planujemy kolejne PRe i zachęcamy społeczność do współpracy.

  • native.directory wciąż rośnie - swoje projekty możesz dodać przez repozytorium GitHub.

  • Uczestniczymy w hackathonach w całej Ameryce Północnej, w tym PennApps, Hack The North, HackMIT, a wkrótce MHacks.

Facebook

  • Pracujemy nad ulepszeniem komponentów <Text> i <TextInput> na Androida (natywne automatyczne powiększanie <TextInput>; problemy układu głęboko zagnieżdżonych <Text>; lepsza struktura kodu; optymalizacje wydajności).

  • Wciąż poszukujemy dodatkowych współtwórców do pomocy w triage'owaniu zgłoszeń i pull requestów.

Microsoft

  • Wydano funkcję podpisywania kodów (Code Signing) dla CodePush. Deweloperzy React Native mogą teraz podpisywać swoje pakiety aplikacji w CodePush. Ogłoszenie można znaleźć tutaj

  • Pracujemy nad ukończeniem integracji CodePush z Mobile Center. Rozważamy także integrację testów i raportowania awarii (test/crash integration).

Kolejne spotkanie

Kolejna sesja odbędzie się w środę, 10 października 2017 roku. Ponieważ to dopiero nasze czwarte spotkanie, chcielibyśmy wiedzieć, jak te notatki przynoszą korzyści społeczności React Native. Śmiało daj mi znać na Twitterze, jeśli masz sugestie, jak możemy ulepszyć rezultaty tych spotkań.

React Native Monthly #3

· 4 minuty czytania
Mike Grabowski
Mike Grabowski
CTO and Co-Founder @ Callstack
Nieoficjalne Tłumaczenie Beta

Ta strona została przetłumaczona przez PageTurner AI (beta). Nie jest oficjalnie zatwierdzona przez projekt. Znalazłeś błąd? Zgłoś problem →

Miesięczne spotkanie React Native trwa! Tym razem było nieco krótsze, ponieważ większość zespołów była zajęta wydawaniem produktów. W przyszłym miesiącu spotkamy się na konferencji React Native EU we Wrocławiu. Koniecznie zdobądź bilet i do zobaczenia na miejscu! Tymczasem sprawdźmy, czym zajmują się nasze zespoły.

Zespoły

Na tym trzecim spotkaniu gościliśmy przedstawicieli 5 zespołów:

Notatki

Oto podsumowania od każdego zespołu:

Callstack

  • Niedawno otworzyliśmy źródła react-native-material-palette. Narzędzie wyodrębnia dominujące kolory z obrazów, pomagając tworzyć wizualnie atrakcyjne aplikacje. Obecnie działa tylko na Androida, ale planujemy dodać wsparcie dla iOS.

  • Wdrożyliśmy obsługę HMR w haul oraz mnóstwo innych fajnych funkcji! Sprawdź najnowsze wydania.

  • React Native EU 2017 już wkrótce! Cały przyszły miesiąc poświęcony React Native i Polsce! Koniecznie złap ostatnie bilety tutaj.

Expo

  • Wprowadziliśmy obsługę instalacji pakietów npm w Snack. Obowiązują standardowe ograniczenia Expo — pakiety nie mogą używać niestandardowych natywnych API niewbudowanych w Expo. Pracujemy też nad obsługą wielu plików i przesyłaniem zasobów w Snack. Satyajit opowie o Snacku na React Native Europe.

  • Wydaliśmy SDK20 z aparatem, płatnościami, bezpiecznym przechowywaniem, magnetometrem, wstrzymywaniem/wznawianiem pobierania plików oraz ulepszonym ekranem powitalnym/ładowania.

  • Kontynuujemy współpracę z Krzysztofem nad react-native-gesture-handler. Wypróbujcie bibliotekę, odtwórzcie gesty zbudowane wcześniej w PanResponder lub natywnych rozpoznawaczach i dajcie znać o napotkanych problemach.

  • Eksperymentujemy z protokołem debugowania JSC, pracujemy nad wieloma zgłoszeniami funkcji na Canny.

Facebook

  • W zeszłym miesiącu omawialiśmy zarządzanie śledzeniem problemów na GitHubie i zapowiedzieliśmy poprawy w utrzymywalności projektu.

  • Liczba otwartych zgłoszeń utrzymuje się na poziomie ~600 i prawdopodobnie pozostanie taka jakiś czas. W ostatnim miesiącu zamknęliśmy 690 zgłoszeń z powodu braku aktywności (definiowanej jako brak komentarzy w ciągu 60 dni). Spośród nich 58 zostało ponownie otwartych z różnych powodów (opiekun zadeklarował poprawkę lub współtwórca przekonująco uzasadnił utrzymanie zgłoszenia).

  • Planujemy kontynuować automatyczne zamykanie nieaktywnych zgłoszeń w przewidywalnej przyszłości. Chcielibyśmy osiągnąć stan, w którym każde istotne zgłoszenie w repozytorium jest rozpatrywane, ale jeszcze tam nie jesteśmy. Potrzebujemy wszelkiej możliwej pomocy od maintainerów w klasyfikowaniu zgłoszeń, aby nie przeoczyć problemów powodujących regresje lub zmiany łamiące kompatybilność, szczególnie tych wpływających na nowo tworzone projekty. Osoby zainteresowane pomocą mogą używać Facebook GitHub Bot do klasyfikowania zgłoszeń i pull requestów. Nowy przewodnik dla maintainerów zawiera więcej informacji o klasyfikacji i używaniu GitHub Bota. Prosimy o dodanie siebie do zespołu zgłoszeń i zachęcanie innych aktywnych członków społeczności do zrobienia tego samego!

Microsoft

  • Nowa aplikacja Skype'a jest zbudowana na React Native, co umożliwia współdzielenie jak największej ilości kodu między platformami. Aplikacja Skype'a oparta na React Native jest już dostępna w sklepach Google Play i App Store.

  • Podczas budowania aplikacji Skype'a w React Native przesyłamy pull requesty do głównego repozytorium React Native, aby naprawić napotkane błędy i brakujące funkcje. Dotychczas udało nam się scalić około 70 pull requestów.

  • React Native umożliwił nam zasilenie aplikacji Skype'a na Androida i iOS z tego samego kodu bazowego. Chcemy również użyć tego kodu do zasilenia webowej wersji Skype'a. Aby osiągnąć ten cel, zbudowaliśmy i otworzyliśmy źródła cienkiej warstwy nad React/React Native o nazwie ReactXP. ReactXP dostarcza zestaw komponentów wieloplatformowych, które mapują się na React Native dla iOS/Android oraz na react-dom dla webu. Cele ReactXP są podobne do innej biblioteki open source o nazwie React Native for Web. Krótki opis różnic w podejściu tych bibliotek znajduje się w FAQ ReactXP.

Shoutem

  • Kontynuujemy prace nad usprawnianiem i upraszczaniem doświadczeń developerskich podczas budowania aplikacji przy użyciu Shoutem.

  • Rozpoczęliśmy migrację naszych aplikacji na react-navigation, ale ostatecznie odłożyliśmy to do czasu wydania stabilniejszej wersji lub aż któraś z natywnych bibliotek nawigacyjnych osiągnie stabilność.

  • Aktualizujemy wszystkie nasze rozszerzenia i większość bibliotek open source (animation, theme, ui) do React Native 0.47.1.

Kolejne spotkanie

Kolejna sesja odbędzie się w środę, 13 września 2017 roku. Ponieważ to dopiero nasze trzecie spotkanie, chcielibyśmy wiedzieć, czy te notatki przynoszą korzyść społeczności React Native. Zachęcamy do kontaktu na Twitterze w przypadku sugestii, jak możemy poprawić rezultaty naszych spotkań.

Wydajność React Native w Marketplace

· 5 minut czytania
Inżynier oprogramowania w Facebooku
Nieoficjalne Tłumaczenie Beta

Ta strona została przetłumaczona przez PageTurner AI (beta). Nie jest oficjalnie zatwierdzona przez projekt. Znalazłeś błąd? Zgłoś problem →

React Native jest używany w wielu miejscach w różnych aplikacjach z rodziny Facebooka, w tym w zakładce najwyższego poziomu w głównej aplikacji. W tym poście skupiamy się na bardzo widocznym produkcie – Marketplace. Jest dostępny w kilkunastu krajach i umożliwia użytkownikom odkrywanie produktów i usług oferowanych przez innych użytkowników.

W pierwszej połowie 2017 roku, dzięki wspólnym wysiłkom zespołów Relay, Marketplace, Mobile JS Platform i React Native, skróciliśmy czas do interakcji (TTI) w Marketplace o połowę na urządzeniach z Androidem klasy rocznikowej 2010-11. Facebook historycznie uznawał te urządzenia za urządzenia z Androidem niskiej klasy, które mają najwolniejsze czasy TTI na dowolnej platformie.

Typowy proces uruchamiania React Native wygląda następująco:

Uwaga: przedstawione proporcje nie są reprezentatywne i będą się różnić w zależności od konfiguracji i sposobu użycia React Native.

Najpierw inicjujemy rdzeń React Native (tzw. „Bridge”), a następnie uruchamiamy produktowy kod JavaScript, który określa, jakie natywne widoki React Native wyrenderuje w czasie przetwarzania natywnego.

Alternatywne podejście

Jednym z naszych wcześniejszych błędów było pozwolenie, by Systrace i CTScan napędzały nasze działania optymalizacyjne. Narzędzia te pomogły nam znaleźć wiele nisko wiszących owoców w 2016 roku, ale odkryliśmy, że zarówno Systrace, jak i CTScan nie odzwierciedlają scenariuszy produkcyjnych i nie mogą symulować warunków rzeczywistych. Proporcje czasu spędzanego w poszczególnych segmentach są często nieprawidłowe, a czasem wręcz bardzo odbiegają od rzeczywistości. W skrajnych przypadkach operacje, które powinny zająć kilka milisekund, faktycznie trwały setki lub tysiące milisekund. Mimo to CTScan pozostaje przydatny – wykrywa jedną trzecią regresji przed trafieniem do produkcji.

Na Androidzie niedoskonałości tych narzędzi przypisujemy faktowi, że 1) React Native to framework wielowątkowy, 2) Marketplace współdzieli przestrzeń z wieloma złożonymi widokami (jak Newsfeed czy inne zakładki najwyższego poziomu) oraz 3) czasy obliczeń bardzo się różnią. Dlatego w tym okresie pozwoliliśmy, by pomiary produkcyjne i szczegółowe analizy napędzały niemal wszystkie nasze decyzje i priorytety.

W głąb instrumentacji produkcyjnej

Instrumentacja produkcji może wydawać się prosta, ale okazała się dość złożonym procesem. Wymagała wielu cykli iteracyjnych (po 2-3 tygodnie każdy) z powodu opóźnień: od zatwierdzenia commita w gałęzi głównej, przez wypuszczenie aplikacji w Sklepie Play, po zebranie wystarczającej liczby próbek produkcyjnych dla weryfikacji. Każda iteracja obejmowała sprawdzenie dokładności naszych analiz, ich odpowiedniego poziomu szczegółowości oraz poprawnego sumowania się do całkowitego czasu. Nie mogliśmy polegać na wersjach alpha i beta, ponieważ nie reprezentują ogólnej populacji. W efekcie mozolnie zbudowaliśmy bardzo dokładny ślad produkcyjny oparty na agregacji milionów próbek.

Jednym z powodów skrupulatnego sprawdzania, czy każda milisekunda w analizach sumuje się do metryk nadrzędnych, było wczesne wykrycie luk w instrumentacji. Okazało się, że nasze wstępne analizy nie uwzględniały przestojów spowodowanych przełączaniem wątków. Same przełączania nie są kosztowne, ale przełączanie na zajęte wątki już wykonujące pracę jest bardzo kosztowne. Odtworzyliśmy te blokady lokalnie poprzez dodanie wywołań Thread.sleep() w kluczowych momentach i rozwiązaliśmy problem poprzez:

  1. usunięcie zależności od AsyncTask,

  2. rezygnację z wymuszonej inicjalizacji ReactContext i NativeModules w wątku UI oraz

  3. usunięcie zależności od pomiaru ReactRootView podczas inicjalizacji.

Razem, usunięcie tych problemów z blokadą wątków skróciło czas uruchamiania o ponad 25%.

Metryki z produkcji podważyły też niektóre z naszych wcześniejszych założeń. Przykładowo, wcześniej wstępnie ładowaliśmy wiele modułów JavaScript na ścieżce startowej, zakładając, że zgrupowanie ich w jednym pakiecie zmniejszy koszt ich inicjalizacji. Jednak koszt wstępnego ładowania i grupowania tych modułów znacznie przewyższał korzyści. Poprzez rekonfigurację naszych czarnych list inline require i usunięcie modułów JavaScript ze ścieżki startowej, udało nam się uniknąć ładowania niepotrzebnych modułów, takich jak Relay Classic (gdy potrzebny był tylko Relay Modern). Dziś nasza sekcja RUN_JS_BUNDLE jest ponad 75% szybsza.

Odnaleźliśmy też korzyści badając natywne moduły specyficzne dla produktu. Przykładowo, poprzez leniwą inicjalizację zależności modułu natywnego, zmniejszyliśmy jego koszt o 98%. Usuwając rywalizację uruchamiania Marketplace z innymi produktami, skróciliśmy czas startu o równoważny przedział.

Najlepsze jest to, że wiele z tych ulepszeń ma szerokie zastosowanie na wszystkich ekranach budowanych z React Native.

Podsumowanie

Ludzie zakładają, że problemy z wydajnością startu React Native wynikają z powolnego JavaScriptu lub nadmiernie długiego czasu sieciowego. Choć przyspieszenie elementów takich jak JavaScript skróciłoby TTI w znaczącym stopniu, każdy z tych czynników stanowi znacznie mniejszy procent TTI niż wcześniej sądzono.

Dotychczasowa lekcja brzmi: mierz, mierz i jeszcze raz mierz! Niektóre korzyści płyną z przenoszenia kosztów czasu wykonania na etap budowania, jak w przypadku Relay Modern i Lazy NativeModules. Inne wynikają z unikania pracy poprzez inteligentniejsze równoległe przetwarzanie kodu lub usuwanie martwego kodu. Kolejne płyną z dużych zmian architektonicznych w React Native, jak usuwanie blokad wątków. Nie istnieje jedno wielkie rozwiązanie problemów wydajności, a długoterminowe sukcesy będą wynikać z przyrostowej instrumentacji i ulepszeń. Nie pozwól, by decyzjami kierowały uprzedzenia poznawcze. Zamiast tego, skrupulatnie zbieraj i interpretuj dane produkcyjne, aby kierować przyszłymi pracami.

Plany na przyszłość

Długoterminowo chcemy, aby TTI Marketplace było porównywalne z podobnymi produktami zbudowanymi natywnie, a ogólnie – by wydajność React Native dorównywała natywnej. Co więcej, chociaż w tym półroczu drastycznie zmniejszyliśmy koszt startu mostka React Native o około 80%, planujemy zbliżyć ten koszt do zera poprzez projekty takie jak Prepack i więcej przetwarzania na etapie budowania.