Przydatne interfejsy API React do tworzenia elastycznych komponentów za pomocą TypeScript
Opublikowany: 2022-03-10 Czy kiedykolwiek korzystałeś bezpośrednio React.createElement
? A co z React.cloneElement
? React to coś więcej niż tylko przekształcenie JSX w HTML. Dużo więcej i aby pomóc Ci zwiększyć swoją wiedzę na temat mniej znanych (ale bardzo przydatnych) interfejsów API, z którymi jest dostarczana biblioteka React. Omówimy kilka z nich i niektóre z ich przypadków użycia, które mogą drastycznie poprawić integrację i użyteczność Twoich komponentów.
W tym artykule omówimy kilka przydatnych interfejsów API React, które nie są tak powszechnie znane, ale niezwykle przydatne dla twórców stron internetowych. Czytelnicy powinni mieć doświadczenie ze składnią React i JSX. Znajomość maszynopisu jest pomocna, ale nie konieczna. Czytelnicy odejdą ze wszystkim, co muszą wiedzieć, aby znacznie ulepszyć komponenty React podczas korzystania z nich w aplikacjach React.
React.cloneElement
Większość programistów może nigdy nie słyszała o cloneElement
ani nigdy go nie używała. Został wprowadzony stosunkowo niedawno, aby zastąpić przestarzałą funkcję cloneWithProps
. cloneElement
klonuje element, pozwala również łączyć nowe właściwości z istniejącym elementem, modyfikować je lub nadpisywać według własnego uznania. Otwiera to niezwykle potężne możliwości tworzenia światowej klasy interfejsów API dla komponentów funkcjonalnych. Spójrz na podpis.
function cloneElement( element, props?, ...children)
Oto skrócona wersja maszynopisu:
function cloneElement( element: ReactElement, props?: HTMLAttributes, ...children: ReactNode[]): ReactElement
Możesz wziąć element, zmodyfikować go, a nawet nadpisać jego dzieci, a następnie zwrócić go jako nowy element. Spójrz na poniższy przykład. Załóżmy, że chcemy utworzyć składnik TabBar linków. To może wyglądać mniej więcej tak.
export interface ITabbarProps { links: {title: string, url: string}[] } export default function Tabbar(props: ITabbarProps) { return ( <> {props.links.map((e, i) => <a key={i} href={e.url}>{e.title}</a> )} </> ) }
TabBar to lista linków, ale potrzebujemy sposobu na zdefiniowanie dwóch elementów danych, tytułu linku i adresu URL. Więc będziemy potrzebować struktury danych przekazywanej z tymi informacjami. Więc nasz programista stworzyłby nasz komponent w ten sposób.
function App() { return ( <Tabbar links={[ {title: 'First', url: '/first'}, {title: 'Second', url: '/second'}] } /> ) }
To świetnie, ale co, jeśli użytkownik chce renderować elementy button
a
elementów? Cóż, moglibyśmy dodać kolejną właściwość, która mówi komponentowi, jaki typ elementu ma zostać wyrenderowany.
Ale możesz zobaczyć, jak szybko stanie się to nieporęczne, musielibyśmy obsługiwać coraz więcej właściwości, aby obsługiwać różne przypadki użycia i przypadki brzegowe, aby uzyskać maksymalną elastyczność.
Oto lepszy sposób, używając React.cloneElement
.
Zaczniemy od zmiany naszego interfejsu, aby odwoływał się do typu ReactNode
. Jest to typ ogólny, który obejmuje wszystko, co React może wyrenderować, zazwyczaj elementy JSX, ale może również zawierać łańcuchy, a nawet null
. Jest to przydatne do wyznaczenia, że chcesz zaakceptować komponenty React lub JSX jako argumenty wbudowane.
export interface ITabbarProps { links: ReactNode[] }
Teraz prosimy użytkownika, aby dał nam kilka elementów React, a my wyrenderujemy je tak, jak chcemy.
function Tabbar(props: ITabbarProps) { return ( <> {props.links.map((e, i) => e // simply return the element itself )} </> ) }
Jest to całkowicie prawidłowe i odda nasze elementy. Ale zapominamy o kilku rzeczach. Po pierwsze, key
! Chcemy dodać klucze, aby React mógł sprawnie renderować nasze listy. Chcemy również zmienić nasze elementy, aby dokonać niezbędnych przekształceń, tak aby pasowały do naszego stylu, na przykład className
i tak dalej.
Możemy to zrobić za pomocą React.cloneElement
i innej funkcji React.isValidElement
do sprawdzania, czy argument jest zgodny z tym, czego oczekujemy!
React.isValidElement
Ta funkcja zwraca wartość true
, jeśli element jest prawidłowym elementem React i React może go renderować. Oto przykład modyfikacji elementów z poprzedniego przykładu.
function Tabbar(props: ITabbarProps) { return ( <> {props.links.map((e, i) => isValidElement(e) && cloneElement(e, {key: `${i}`, className: 'bold'}) )} </> ) }
Tutaj dodajemy kluczową właściwość do każdego przekazywanego elementu i jednocześnie pogrubiamy każdy link! Możemy teraz zaakceptować dowolne elementy React jako takie rekwizyty:
function App() { return ( <Tabbar links={[ <a href='/first'>First</a>, <button type='button'>Second</button> ]} /> ) }
Możemy nadpisać dowolne rekwizyty ustawione na elemencie i łatwo zaakceptować różnego rodzaju elementy, dzięki czemu nasz komponent jest znacznie bardziej elastyczny i łatwy w użyciu.
“
Zaletą jest to, że gdybyśmy chcieli ustawić niestandardowy moduł obsługi onClick
dla naszego przycisku, moglibyśmy to zrobić. Akceptowanie samych elementów React jako argumentów to potężny sposób na zapewnienie elastyczności projektowi komponentów.
useState
Setter Function
Użyj haków! Hak useState
jest niezwykle przydatny i jest fantastycznym interfejsem API do szybkiego budowania stanu w twoich komponentach, takich jak:
const [myValue, setMyValue] = useState()
Ze względu na środowisko wykonawcze JavaScript może mieć pewne problemy. Pamiętasz zamknięcia?
W niektórych sytuacjach zmienna może nie być poprawną wartością ze względu na kontekst, w którym się znajduje, na przykład w pętlach for często lub zdarzeniach asynchronicznych. Wynika to z zakresu leksykalnego. Po utworzeniu nowej funkcji zakres leksykalny zostaje zachowany. Ponieważ nie ma nowej funkcji, zakres leksykalny newVal
nie jest zachowywany, a zatem wartość jest faktycznie wyłuskiwana do czasu jej użycia.
setTimeout(() => { setMyValue(newVal) // this will not work }, 1000)
To, co musisz zrobić, to wykorzystać ustawiacz jako funkcję. Tworząc nową funkcję, odwołanie do zmiennych jest zachowane w zakresie leksykalnym, a currentVal jest przekazywany przez sam hook useState Reacta.
setTimeout(() => { setMyValue((currentVal) => { return newVal }) }, 1000)
Zapewni to poprawną aktualizację wartości, ponieważ funkcja ustawiająca jest wywoływana we właściwym kontekście. To, co React robi tutaj, to wywołanie funkcji w odpowiednim kontekście, aby nastąpiła aktualizacja stanu React. Może to być również użyte w innych sytuacjach, w których pomocne jest działanie na bieżącej wartości, React wywołuje funkcję z pierwszym argumentem jako bieżącą wartością.
Uwaga : Aby uzyskać dodatkowe informacje na temat async i domknięć, polecam lekturę „ useState
Lazy Initialization and Function Updates” autorstwa Kent C. Dodds.
Funkcje wbudowane JSX
Oto demo Codepen funkcji inline JSX:
Niezupełnie React API na słowo.
JSX obsługuje funkcje wbudowane i może być naprawdę przydatny do deklarowania prostej logiki ze zmiennymi wbudowanymi, o ile zwraca element JSX.
“
Oto przykład:
function App() { return ( <> {(() => { const darkMode = isDarkMode() if (darkMode) { return ( <div className='dark-mode'></div> ) } else { return ( <div className='light-mode'></div> ) // we can declare JSX anywhere! } })()} // don't forget to call the function! </> ) }
Tutaj deklarujemy kod wewnątrz JSX, możemy uruchomić dowolny kod i wszystko, co musimy zrobić, to zwrócić funkcję JSX do renderowania.
Możemy uczynić to warunkowym lub po prostu wykonać jakąś logikę. Zwróć uwagę na nawiasy otaczające funkcję inline. Również szczególnie tutaj, gdy wywołujemy tę funkcję, moglibyśmy nawet przekazać do tej funkcji argument z otaczającego kontekstu, gdybyśmy chcieli!
})()}
Może to być przydatne w sytuacjach, w których chcesz działać na strukturze danych kolekcji w bardziej złożony sposób niż standardowe .map
pozwala na umieszczenie wewnątrz elementu JSX.
function App() { return ( <> {(() => { let str = '' for (let i = 0; i < 10; i++) { str += i } return (<p>{str}</p>) })()} </> ) }
Tutaj możemy uruchomić kod, aby przejść przez zbiór liczb, a następnie wyświetlić je w wierszu. Jeśli używasz statycznego generatora witryn, takiego jak Gatsby, ten krok również zostanie wstępnie obliczony.
component extends type
Ta funkcja, niezwykle przydatna przy tworzeniu komponentów przyjaznych autouzupełnianiu, umożliwia tworzenie komponentów, które rozszerzają istniejące HTMLElements
lub inne komponenty. Głównie przydatne do poprawnego wpisywania elementów interfejsu w Typescript, ale rzeczywista aplikacja jest taka sama dla JavaScript.
Oto prosty przykład, powiedzmy, że chcemy nadpisać jedną lub dwie właściwości elementu button
, ale nadal dać programistom możliwość dodania innych właściwości do przycisku. Na przykład ustawienie type='button'
lub type='submit'
. Oczywiście nie chcemy odtwarzać całego elementu przycisku, chcemy po prostu rozszerzyć jego istniejące właściwości i być może dodać jeszcze jedną właściwość.
import React, { ButtonHTMLAttributes } from 'react'
Najpierw importujemy React i klasę ButtonHTMLAttributes
, typ, który obejmuje właściwości elementu HTMLButtonElement
. Możesz przeczytać kod źródłowy tego typu interfejsu tutaj:
Jak widać, zespół React ponownie zaimplementował wszystkie internetowe interfejsy API w TypeScript, dzięki czemu można go sprawdzić.
Następnie deklarujemy nasz interfejs w ten sposób, dodając naszą właściwość status
.
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { status?: 'primary' | 'info' | 'danger' }
I na koniec robimy kilka rzeczy. Używamy destrukturyzowania ES6, aby wydobyć rekwizyty, na których nam zależy ( status
i children
), i zadeklarować inne właściwości jako rest
. A w naszych danych wyjściowych JSX zwracamy element przycisku ze strukturą ES6, aby dodać dodatkowe właściwości do tego elementu.
function Button(props: ButtonProps) { const { status, children, ...rest } = props // rest has any other props return ( <button className={`${status}`} {...rest} // we pass the rest of the props back into the element > {children} </button> ) }
Więc teraz programista może dodać podporę type
lub jakąkolwiek inną podporę, którą zwykle miałby przycisk. Dodaliśmy dodatkowy atrybut, który wykorzystaliśmy w className
do ustawienia stylu przycisku.
Oto cały przykład:
import React, { ButtonHTMLAttributes } from 'react' export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { status?: 'primary' | 'info' | 'danger' } export default function Button(props: ButtonProps) { const { status, children, ...rest } = props return ( <button className={`${status}`} {...rest} > {children} </button> ) }
To świetny sposób na tworzenie wewnętrznych komponentów wielokrotnego użytku, które są zgodne z Twoimi wytycznymi dotyczącymi stylu, bez przebudowy całych elementów HTML! Możesz po prostu nadpisać całe właściwości, takie jak ustawienie nazwy className
na podstawie stanu lub zezwolić na przekazywanie dodatkowych nazw klas.
import React, { ButtonHTMLAttributes } from 'react' export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { status?: 'primary' | 'info' | 'danger' } export default function Button(props: ButtonProps) { const { status, children, className, ...rest } = props return ( <button className={`${status || ''} ${className || ''}`} {...rest} > {children} </button> ) }
Tutaj bierzemy właściwość className
przekazaną do naszego elementu Button i wstawiamy ją z powrotem, z kontrolą bezpieczeństwa w przypadku, gdy właściwość jest undefined
.
Wniosek
React to niezwykle potężna biblioteka i nie bez powodu szybko zyskała popularność. Zapewnia doskonały zestaw narzędzi do tworzenia wydajnych i łatwych w utrzymaniu aplikacji internetowych. Jest niezwykle elastyczny, a jednocześnie bardzo rygorystyczny, co może być niezwykle przydatne, jeśli wiesz, jak go używać. To tylko kilka interfejsów API, które są godne uwagi i są w dużej mierze pomijane. Wypróbuj je w swoim następnym projekcie!
Aby dowiedzieć się więcej o najnowszych interfejsach React API, hookach, polecam lekturę useHooks(). Ściągawka Typescript zawiera również kilka świetnych informacji na temat haków React i Typescript.