Strona główna / Artykuły

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ą.

Wydawca: REACTAPP.TOP12 min czytania2663 słów

Źródła: I Built a React Native Select Component — and Turned It Into an Open-Source Package: `react-native-paper-select`

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:

  • TextInput
  • Checkbox
  • Dialog

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ę:

  1. Śledzeniem stanu wyboru
  2. Obsługą anulowania wyboru
  3. Pokazywaniem kilku wybranych wartości jednocześnie
  4. Zwracaniem zaktualizowanego zestawu wyborów
  • Zachowywanie synchronizacji wyświetlanej interfejsu z danymi podstawowymi
  • 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.