Poznajemy pierwszy generyczny komponent w React z TypeScriptem. Kontynuacja lekcji poprzednich. Do dzieła!
Ok, tworzymy List.tsx w components i piszemy:
import { ReactNode } from 'react'
interface ListProps<T> {
items: T[],
render: (item: T) => ReactNode
}
Jak widać jeden z propów musi być funkcją. Już to znamy. Ok, teraz jak ten RFC wygląda:
const List = <T,>({ items, render }: ListProps<T>) => {
return (
<ul>
{items.map((item, i) => (
<li key={i}>
{render(item)}
</li>
))}
</ul>
)
}
export default List;
Ten przecinek to nie błąd. Zauważmy, że zarówno TypeScript jak i React ma swoje zastosowanie dla znaków < oraz >, do tematu zaraz wrócimy.
Użycie tego RFC (to z internetu przykład):
<List items={["☕ Coffee", "🌮 Tacos", "💻 Code"]} render={(item: string) => <span className="bold">{item}</span>} />
Jak już ułoży się nam w głowie jak to działa (pamiętajmy o imporcie w App.tsx) to kilka uwag. Po pierwsze, możemy tak ten problem z przecinkiem rozwiązać:
const List = <T extends {}>({ items, render }: ListProps<T>) => {
return (
<ul>
{items.map((item, i) => (
<li key={i}>
{render(item)}
</li>
))}
</ul>
)
}
export default List;
Generalnie extends {} (nie mylić z extends object) oznacza, że wartości null i undefined nie przechodzą. Ale samo słówko extends tutaj już naprowadza Reacta, że robimy generyczny typ, a nie żaden JSX i nie potrzeba tego przecinka, React wie o co chodzi.
Bo JSX też można przypisywać do zmiennych/stałych i dlatego ten zapis jest problematyczny, React nie ma możliwości wiedzieć, o co nam chodzi.
Oczywiście teraz nie mamy takiego problemu:
function List<T>({ items, render }: ListProps<T>) {
return (
<ul>
{items.map((item, i) => (
<li key={i}>
{render(item)}
</li>
))}
</ul>
)
}
export default List;
No i jeszcze taka mała rzecz, a mianowicie takie funkcje render nadają się do przekazywania dynamic components w React:
<List items={["☕ Coffee", "🌮 Tacos", "💻 Code"]} render={(item: string) => <Counter/>} />
Tutaj akurat trochę bez sensu (item jest nieużyty) 3 razy dostaniemy countera, ale chodzi mi o koncepcję. Render może też zwracać JSX.Element, nie ma problemu.
Mówię o tym, bo wcześniej miałem mega przygody, aby jakoś ogarnąć używanie dynamic components w React + TS. I ciągle coś mnie prześladowało (no dobra, spędziłem nad tym może 10 minut) aż w końcu ogarnąłem, że można to tak robić, czyli przez funkcję.
Więcej Reacta niedługo, do następnego razu!