Tworzenie konfigurowalnego komponentu wyboru dla React Native Paper
Przewodnik po projektowaniu i udostępnianiu kodu react-native-paper-select w formie otwartego oprogramowania, obejmujący funkcje wyszukiwania, elementy do wyboru wielokrotnego, listy podzielone na sekcje oraz kwestie związane z wydajnością.
Budowa prawdziwego menu rozwijanego w React Native prawie nigdy nie jest tak prosta, jak się wydaje
Jeśli poświęciłeś czas na próby implementacji prawidłowego menu rozwijanego w React Native, już wiesz, że „zwykłe menu rozwijane” szybko zamienia się w znacznie trudniejsze zadanie.
W końcu musisz zarządzać stanem wyboru, zintegrować funkcję wyszukiwania, obsługiwać wielokrotne wybory, radzić sobie z długimi listami, prezentować interfejs w sposób naturalny na urządzeniach mobilnych, dodać mechanizmy walidacji i upewnić się, że całość wizualnie pasuje do reszty aplikacji.
Jeśli twój projekt jest już zbudowany na bazie React Native Paper, używanie oddzielnej biblioteki interfejsu tylko po to, by mieć działające pole wyboru, wydaje się przesadą.
To niepasowanie jest dokładnie tym problemem, który miał rozwiązać ten komponent.
Celem było stworzenie komponentu wyboru wielokrotnego, który dobrze pasowałby do React Native Paper, umożliwiał wybór pojedynczych lub wielu elementów i pozwalał uniknąć konieczności ciągłego ponawiania tego samego wzorca interakcji w każdym projekcie.
Dlatego zbudowano go od zera.
To, co zaczęło się jako dość wąski komponent interfejsu, stopniowo rozwinęło się w react-native-paper-select – pakiet o otwartym kodzie źródłowym dla React Native, który obecnie obejmuje funkcje wyszukiwania, wyboru wielu elementów, wyświetlania informacji w formie chipów, list podzielonych na sekcje, wyboru za pomocą przycisków radiowych, elementów typu bottom sheet, menu oraz renderowania list z uwzględnieniem wydajności.
W tym artykule omówiono motywacje stworzenia tego komponentu, jak zmieniał się on z biegiem czasu, decyzje inżynieryjne, które na niego wpłynęły, oraz wnioski wyciągnięte podczas przekształcania czegoś, co pierwotnie zostało stworzone dla jednej aplikacji, w uniwersalną bibliotekę o otwartym kodzie źródłowym.
Problem
Z pozoru spadające listy wydają się błahe.
Jednak gdy faktycznie zaczynasz je implementować, pojawia się długa lista pytań projektowych:
- Jak wyświetlić aktualnie wybraną wartość?
- Czy pole musi obsługiwać jedno wybór, czy kilka?
- Jaka jest odpowiednia forma prezentacji listy opcji?
- Jak powinna działać funkcja wyszukiwania w tej liście?
- Czy zachowanie powinno się różnić między Androidem a iOS?
- Jak renderować bardzo długie listy bez pogorszenia wydajności?
- Jak utrzymać prostotę API przeznaczonego dla programistów?
- Jak umożliwić personalizację, nie czyniąc komponentu zbyt skomplikowanym w użyciu?
Rozwiązywanie tych problemów od zera w każdej pojedynczej aplikacji nie miało sensu.
Zamiast tworzyć kolejny komponent jednorazowy, przeznaczony wyłącznie dla jednej aplikacji, sensowniej było od samego początku zaprojektować coś, co można będzie wielokrotnie wykorzystać.
Podstawowa idea była prosta:
Zbuduj komponent do wyboru na bazie własnych elementów interfejsu React Native Paper i spraw, by jego użycie było na tyle proste, że inni deweloperzy React Native rzeczywiście chcieliby go stosować.
Prezentujemy react-native-paper-select
react-native-paper-select to konfigurowalny komponent dla React Native, który obsługuje zarówno przypadki użycia typu pojedynczy wybór, jak i wielokrotny wybór.
Pierwsza wersja opierała się na dobrze znanych elementach dostarczanych już przez React Native Paper, między innymi:
TextInputCheckboxDialog
Celem było zapewnienie programistom doświadczenia wyboru, które pod względem wizualnym i zachowania było spójne z aplikacjami już stworzonymi na bazie systemu projektowego React Native Paper.
Gdy pakiet się rozwinął, przekroczył tę początkową implementację, aby obsługiwać dodatkowe style prezentacji oraz bardziej złożone zachowania wyboru.
Instalacja pakietu
Zaczęcie pracy wymaga zaledwie kilku kroków.
npm install react-native-paper-select
Albo, jeśli wolisz Yarn:
yarn add react-native-paper-select
Po zainstalowaniu należy importować komponent w ten sposób:
import { PaperSelect } from 'react-native-paper-select';
Prosty element wyboru
Aby stworzyć minimalne pole wyboru, potrzebujesz jedynie listy opcji oraz funkcji do obsługi wyboru.
Oto przykład:
const [selected, setSelected] = useState({
value: '',
label: '',
});
const options = [
{
_id: '1',
value: 'React Native',
},
{
_id: '2',
value: 'Flutter',
},
{
_id: '3',
value: 'Native Android',
},
];
A następnie renderowanie komponentu:
<PaperSelect
label="Framework"
value={selected.label}
onSelection={(value) => setSelected(value)}
arrayList={options}
selectedArrayList={selected}
/>
Tutaj nie ma znaczenia to, jak mało kodu jest potrzebne.
Ważne jest to, co ten kod ukrywa.
Z punktu widzenia aplikacji istnieją właściwie tylko dwa pytania, na które trzeba odpowiedzieć:
Które opcje są dostępne?
i
Którą opcję wybrał użytkownik?
Wszystko, co dotyczy prezentacji i interakcji, jest obsługiwane wewnętrznie przez sam komponent.
Wybór pojedynczy vs Wybór wielokrotny
Jednym z kluczowych wymagań dla tego komponentu było od samego początku wsparcie zarówno wyboru pojedynczego, jak i wielokrotnego.
Wybór pojedynczy jest dość prosty do zrozumienia:
Select Framework
----------------
○ React Native
○ Flutter
○ Native Android
Z kolei wybór wielokrotny wymaga zarządzania dodatkowym stanem:
Select Technologies
-------------------
☑ React Native
☑ TypeScript
☐ Flutter
☑ Node.js
To oznacza, że komponent musi zajmować się:
- Śledzeniem stanu wyboru
- Obsługą anulowania wyboru
- Pokazywaniem kilku wybranych wartości jednocześnie
- Zwracaniem zaktualizowanego zestawu wyborów
Celem było umieszczenie całej tej złożoności wewnątrz samego komponentu, zamiast przenoszenia jej na każdego programistę korzystającego z pakietu.
Dlaczego React Native Paper?
Ponieważ aplikacja otaczająca już opierała się na React Native Paper, budowa komponentu select w oparciu o ten sam system projektowy była naturalnym wyborem.
Zamiast wymyślać na nowo każdy element wizualny, komponent mógł ponownie wykorzystać elementy interfejsu już dostarczone przez Paper.
PaperSelect
│
┌──────────┴──────────┐
│ │
TextInput Selection UI
│
┌───────────┼───────────┐
│ │ │
Checkbox Radio List
To rozwiązanie projektowe przynosi rzeczywistą korzyść.
Każda aplikacja już używająca React Native Paper może dodać ten komponent select bez wprowadzania stylu wizualnego, który kolidowałby z resztą interfejsu.
Najwcześniejsza wersja tego komponentu oferowała jedynie okno dialogowe do dokonywania wyboru.
Interakcja przebiegała w następujący sposób:
TextInput
↓
User taps field
↓
Dialog opens
↓
User selects an item
↓
Selected value appears in TextInput
Jednak w miarę rozwoju komponentu stało się oczywiste, że okno dialogowe nie zawsze jest odpowiednim rozwiązaniem dla każdego ekranu mobilnego.
Potrzeby użytkownika znacznie różnią się pomiędzy poszczególnymi aplikacjami.
Aby to uwzględnić, komponent rozszerzył funkcjonalności o kilka trybów prezentacji:
- Dialog
- Bottom Sheet
- Menu
Dzięki tej zmianie ten sam komponent do wyboru może być używany w zupełnie różnych kontekstach interakcji, a programiści nie muszą przepisywać swoich integracji, aby przełączać się między nimi.
Naprzимер:
<PaperSelect
presentationStyle="bottomSheet"
...
/>
Ta elastyczność stanowiła istotny krok naprzód dla tej biblioteki.
Logika selekcji pozostaje identyczna niezależnie od wybranego stylu — zmienia się jedynie sposób jej prezentacji użytkownikowi.
Zarządzanie dużymi listami
Mniej oczywistym wyzwaniem w komponentach typu dropdown jest jakość działania listy w miarę jej rozrastania się.
Wyobraź sobie komponent select z:
10 items
Jego renderowanie jest banalne.
A teraz wyobraź sobie taki z:
1,000 items
lub nawet:
10,000 items
Renderowanie tak dużej liczby elementów listy jednocześnie może negatywnie wpłynąć na responsywność aplikacji mobilnej.
Aby tego uniknąć, pakiet wykorzystuje FlashList do renderowania swoich list opcji.
Koncepcja jest prosta:
Large dataset
↓
Virtualized list
↓
Render what is needed
↓
Better scrolling performance
Wydajność to kwestia, której nie można odkładać do momentu tworzenia czegoś na dużą skalę.
Każdy komponent przeznaczony do ponownego użycia musi być budowany od samego początku z myślą o skalowalności, ponieważ rzadko można z góry wiedzieć, jak duże będą przekazywane do niego zbiory danych.
Szukanie
Gdy liczba dostępnych opcji rośnie, szukanie staje się niezbędne.
Zamiast zmuszać użytkowników do nieskończonego przewijania długiej listy, komponent oferuje możliwość wyszukiwania, która pomaga zawęzić wybór.
Przepływ działa w następujący sposób:
Search
↓
Filter options
↓
Select result
↓
Update selection
Z punktu widzenia użytkownika może to wydawać się drobnym ulepszeniem, ale ma duży wpływ na użyteczność, gdy zbiór danych przekracza kilka elementów.
Wielokrotne wybieranie z elementami typu „chips”
Pokazywanie kilku wybranych wartości jednocześnie może szybko prowadzić do chaosu.
Naprzимер, taka wartość jak:
React Native, TypeScript, Node.js, MongoDB
jest trudna do zinterpretowania, gdy jest umieszczona w standardowym polu tekstowym.
Elementy typu „chips” rozwiązują ten problem.
Wybrane wartości mogą być zamiast tego wyświetlane jako oddzielne, odrębne bloki:
[ React Native ] [ TypeScript ]
[ Node.js ] [ MongoDB ]
Komponent ten obsługuje ten ekran oparty na chipach w scenariuszach wielokrotnego wyboru, dając użytkownikom znacznie jaśniejszy obraz tego, co wybrali.
Poza tym znacznie ułatwia usuwanie pojedynczych wyborów.
Listy sekcjonalne
Kolejnym wymogiem było możliwość grupowania powiązanych opcji razem.
Zamiast jednej długiej, płaskiej listy w stylu:
React
Angular
Vue
Node.js
Java
Python
MongoDB
PostgreSQL
opcje mogą być uporządkowane pod nagłówkami:
Frontend
☐ React
☐ Angular
☐ Vue
Backend☐ Node.js
☐ Java
☐ Python
Database☐ MongoDB
☐ PostgreSQL
Takie grupowanie znacznie ułatwia przeglądanie i zrozumienie dużych zbiorów opcji.
W trybie wielokrotnego wyboru umożliwienie użytkownikom wybrania całej sekcji za jednym razem znacznie przyspiesza proces.
Naprzимер:
☑ Frontend
☑ React
☑ Angular
☑ Vue
To jest lepsze niż zmuszanie kogoś do klikania po każdym elemencie pojedynczo.
Pole wyboru typu radio do pojedynczego wyboru
Pole wyboru typu checkbox ma sens, gdy można wybrać więcej niż jedną opcję.
Jednak w przypadkach pojedynczego wyboru pola radio jasniej przekazują użytkownikowi intencję.
Dlatego pakiet oferuje również wersję interfejsu wyboru z użyciem pól typu radio.
Podstawowa idea pozostaje niezmienna:
Options
↓
User chooses one
↓
Selection state updated
Zmienia się tylko prezentacja wizualna.
To podkreśla ważną zasadę budowania komponentów wielokrotnie używalnych:
Należy oddzielić logikę wyboru od sposobu jej wyświetlania.
TypeScript i projektowanie API
Ponieważ ma to być używane przez innych programistów, forma API jest równie ważna jak wygląd interfejsu.
Osoba korzystająca z pakietu nie powinna wiedzieć nic o jego wewnętrznym funkcjonowaniu.
Idealnie rzecz biorąc, mogą o tym rozmyślać wyłącznie w kategoriach:
Input
↓
Options
↓
User interaction
↓
Selection
↓
Callback
Zamiast:
Dialog state
Modal state
List state
Search state
Checkbox state
Rendering state
...
Dobrze zaprojektowana API komponentu ukrywa tę wewnętrzną złożoność przed użytkownikiem.
To jedna z najjaśniejszych lekcji wynikających z tworzenia i doskonalenia tego pakietu.
Otwarty kod zmienia sposób projektowania
Pisanie komponentu dla własnej aplikacji to inny rodzaj zadania niż pisanie go dla szerszej grupy programistów.
W ramach pojedynczej aplikacji standardem może być:
"To rozwiązuje mój konkretny przypadek."
Gdy staje się to kodem otwartym, pytanie zmienia się na:
"Czy ta API będzie zrozumiała dla programisty, który nigdy nie widział kodu źródłowego?"
Ta zmiana wpływa na twoje priorytety podczas tworzenia.
Nagle zaczynasz z większą uwagą przyglądać się takim kwestiom jak:
- Jednolitość w całym API
- Dokumentacja
- Zgodność wsteczna
- Przypadki krawędziowe
- Rozsądne domyślne wartości
- Definicje typów
- Opcje personalizacji
- Wydajność
- Wersjonowanie
To, co na początku było małym, prostym komponentem, może stać się czymś zaskakująco skomplikowanym, gdy inni programiści zaczną od niego korzystać w środowisku produkcyjnym.
Jak projekt rozwijał się z biegiem czasu
To, co zaczęło się jako prosty komponent do wyboru, stopniowo przekształciło się w bardziej kompletny zestaw narzędzi do selekcji.
Jego rozwój można mniej więcej przedstawić w następujący sposób:
Simple Select
↓
Single Selection
↓
Multi Selection
↓
Search
↓
Large List Handling
↓
Chips
↓
Sections
↓
Radio Selection
↓
Bottom Sheet
↓
Menu Presentation
Każda nowa funkcjonalność rozwiązywała prawdziwy problem związany z użytecznością lub doświadczeniem programisty.
To jeden z aspektów pracy nad oprogramowaniem otwartym, który wyróżnia się na tle innych.
Gdy wysyłasz wersję 1, rzadko masz jasny obraz tego, jak ostatecznie będzie wyglądać gotowa biblioteka.
Zamiast tego pakiet ciągle zmienia swój kształt w miarę pojawiania się rzeczywistych wymagań, opinii użytkowników oraz nowych przypadków użycia.
Lekcje z tego procesu
1. Małe problemy mogą przerodzić się w wartościowe projekty open-source
Początkowy problem w żaden sposób nie był przełomowy.
Po prostu brakowało komponentu select, który można by ponownie wykorzystać.
Jednak istnieje prawdziwa różnica pomiędzy naprawieniem czegoś do własnego użytku a udoskonaleniem go do tego stopnia, aby inni również mogli go używać.
Czasami ta różnica jest wystarczająco duża, by uzasadnić rozpoczęcie projektu open-source.
2. Uczynienie czegoś ponownie wykorzystywalnym oznacza jego prawidłową abstrakcję
Dobrze zaprojektowany komponent nie powinien być przytwierdzony do konkretnej aplikacji, która go stworzyła.
Zamiast tego musi prezentować czysty, prosty interfejs z zewnątrz, ukrywając przy tym swoją wewnętrzną złożoność.
Ta zasada ma zastosowanie daleko poza światem React Native.
3. Projektowanie komponentów dotyczy również doświadczenia użytkownika
To, że komponent można używać wielokrotnie na poziomie kodu, to nie cała historia.
Równie ważne jest to, jak wygląda interakcja z nim.
Diagnozy, listy na dole ekranu, menu, elementy typu chip, pola wyszukiwania, przyciski radiowe oraz listy podzielone na sekcje to wszystko różne rozwiązania tego samego podstawowego pytania:
Jaki jest najlepszy sposób na dokonanie wyboru?
4. Komponenty wielokrotnego użycia muszą być tworzone z myślą o wydajności
Komponent, który dobrze funkcjonuje z dziesięcioma elementami, może później zostać użyty w miejscu z dziesięcioma tysiącami.
Dokładnie dlatego decyzje dotyczące renderowania list i wirtualizacji powinny być podejmowane na etapie projektowania, a nie jako rozwiązanie dodatkowe po tym, jak w środowisku produkcyjnym pojawią się problemy z wydajnością.
5. Dokumentacja jest kluczową częścią pakietu, a nie dodatkiem
Kod, który działa, to za mało, by uznać pakiet za ukończony.
Rozwijający nadal potrzebują jasnych odpowiedzi na pytania dotyczące:
- sposobu instalacji
- sposobu używania
- propów, które są dostępne
- formy, jaką powinny mieć oczekiwane dane
- sposobu dostosowania jego zachowania i wyglądu
- funkcji, które są faktycznie obsługiwane
Dobra dokumentacja pomaga przezwyciężyć różnicę pomiędzy sytuacją, gdy ktoś myśli:
"I found this package."
a momentem, w którym może z pewnością stwierdzić:
"I can use this package."
Kierunek dalszego rozwoju projektu
Cel stworzenia react-native-paper-select na początku był skromny: komponent wyboru, który naturalnie pasowałby do aplikacji już używających React Native Paper.
Od tamtej pory rozwinął się on w znacznie bardziej funkcjonalny element, obejmujący teraz wiele trybów wyboru, różne style prezentacji, funkcję wyszukiwania, wybór oparty na elementach typu chip, dane podzielone na sekcje oraz renderowanie list z uwzględnieniem wydajności.
Nadal istnieje wiele możliwości dalszego udoskonalania tego komponentu.
Żaden projekt open-source nigdy nie jest w pełni ukończony.
Ciągle pojawiają się nowe urządzenia, sam React Native otrzymuje kolejne aktualizacje, zmieniają się oczekiwania dotyczące projektowania, rozwijają się wymagania związane z dostępnością, ponownie pojawiają się kwestie wydajności, a deweloperzy nadal przesyłają informacje zwrotne, które warto uwzględnić.
To ciągłe rozwoj jest częścią tego, co sprawia, że utrzymywanie takiej biblioteki jest naprawdę angażujące.
Podsumowanie
Praca nad react-native-paper-select pokazała, że projekt open-source nie musi zaczynać się od wielkiej wizji.
Często zaczyna się od małego, konkretnego problemu, na który natrafiamy podczas tworzenia czegoś innego.
Rozwiązujemy go dla siebie.
Następnie okazuje się, że to rozwiązanie może pomóc innym, którzy borykają się z tym samym problemem.
Dlatego je udoskonalamy.
Napisujemy je w odpowiedni sposób.
Upublikowujemy je.
Z upływem czasu mała komponenta wewnętrzna może przekształcić się w pakiet, od którego inni programiści polegają przy swoich projektach.
Taka jest historia stojąca za react-native-paper-select.
Jeśli w twoim projekcie używasz React Native Paper i potrzebujesz elastycznego komponentu do wyboru lub wielokrotnego wyboru, repozytorium znajduje się na GitHub, a sam pakiet jest publikowany na npm.
Zawsze mile widziane są opinie, zgłoszenia błędów oraz wkłady od użytkowników, którzy korzystają z tego pakietu.