Przekazywanie wartości do komponentu
Komponenty reactowe używają właściwości (ang. props, od “properties”) do komunikowania się między sobą. Każdy komponent nadrzędny może przekazać informacje do własnych potomków poprzez właściwości. Właściwości mogą kojarzyć się z atrybutami HTML-owymi, jednak różnica polega na tym, że przez właściwości można przekazywać dowolne wartości javascriptowe, w tym obiekty, tablice czy funkcje.
W tej sekcji dowiesz się
- Jak przekazać wartości do komponentu
- Jak odczytać właściwości komponentu
- Jak określić domyślną wartość dla właściwości
- Jak przekazać kod JSX-owy do komponentu
- Jak właściwości zmieniają się w czasie
Właściwości, które możesz już znać
Właściwości (ang. props) to informacje, które przekazujemy znacznikowi JSX-owemu. Na przykład, znacznikowi <img>
możemy przekazać właściwości className
, src
, alt
, width
czy height
:
function Avatar() { return ( <img className="avatar" src="https://i.imgur.com/1bX5QH6.jpg" alt="Lin Lanying" width={100} height={100} /> ); } export default function Profile() { return <Avatar />; }
Właściwości, które możesz przekazać do znacznika <img>
, są predefiniowane (ReactDOM przestrzega standardu HTML). Jednak do własnych komponentów, np. <Avatar>
, możesz przekazać dowolne właściwości!
Przekazywanie wartości do komponentu
W poniższym kodzie komponent Profile
nie przekazuje swojemu potomkowi Avatar
żadnych wartości:
export default function Profile() {
return <Avatar />;
}
Aby dodać do komponentu Avatar
właściwości, wystarczą dwa kroki.
Krok 1: Przekaż właściwości do komponentu potomnego
Najpierw przekażmy do komponentu Avatar
jakieś wartości. Na przykład, niech będą to person
(obiekt) oraz size
(liczba):
export default function Profile() {
return (
<Avatar person={{name: 'Lin Lanying', imageId: '1bX5QH6'}} size={100} />
);
}
Teraz możemy odczytać te wartości wewnątrz komponentu Avatar
.
Krok 2: Odczytaj wartości wewnątrz komponentu potomnego
Aby odczytać te właściwości, wypiszmy ich nazwy oddzielone przecinkiem i zapisane wewnątrz ({
oraz })
zaraz po słowach function Avatar
. Dzięki temu będziemy mogli odwołać się do nich jak do zmiennych.
function Avatar({person, size}) {
// tutaj można używać person i size
}
Teraz wystarczy dodać do komponentu Avatar
logikę, która używa właściwości person
i size
do renderowania - i gotowe!
To, co wyrenderuje Avatar
, możemy kontrolować na wiele różnych sposobów, przekazując różne wartości dla właściwości. Spróbuj zmienić którąś z nich!
import { getImageUrl } from './utils.js'; function Avatar({person, size}) { return ( <img className="avatar" src={getImageUrl(person)} alt={person.name} width={size} height={size} /> ); } export default function Profile() { return ( <div> <Avatar size={100} person={{ name: 'Katsuko Saruhashi', imageId: 'YfeOqp2', }} /> <Avatar size={80} person={{ name: 'Aklilu Lemma', imageId: 'OKS67lh', }} /> <Avatar size={50} person={{ name: 'Lin Lanying', imageId: '1bX5QH6', }} /> </div> ); }
Właściwości pozwalają myśleć o komponentach nadrzędnych i potomnych jako o bytach niezależnych. Możemy, na przykład, zmienić wartości przekazywane przez właściwości person
i size
w Profile
i nie musimy wiedzieć, jak Avatar
z nich korzysta. Podobnie możemy zmienić sposób użycia tych wartości w Avatar
bez patrzenia na kod Profile
.
Możesz myśleć o właściwościach jak o “pokrętłach”, którymi można sterować. Pełnią taką samą rolę co argumenty w funkcjach - tak naprawdę właściwości są jedynym argumentem dla komponentu! Funkcyjne komponenty reactowe przyjmują jeden argument - obiekt props
:
function Avatar(props) {
let person = props.person;
let size = props.size;
// ...
}
Zwykle jednak nie ma potrzeby korzystać z samego obiektu props
, dlatego zazwyczaj się je destrukturyzuje na poszczególne właściwości.
Określanie domyślnej wartości dla właściwości
Jeśli chcesz nadać właściwości domyślną wartość, która będzie użyta za każdym razem, gdy nie przekażemy żadnej wartości do komponentu, możesz to zrobić dodając do zapisu destrukturyzującego symbol =
i podając po nim wartość domyślną:
function Avatar({person, size = 100}) {
// ...
}
Teraz gdy wyrenderujemy <Avatar person={...} />
bez podawania właściwości size
, zostanie ona ustawiona na wartość 100
.
Wartość domyślna jest używana tylko wtedy, gdy właściwość size
zostanie pominięta lub otrzyma wartość size={undefined}
. Jeśli jednak przekażesz size={null}
lub size={0}
, domyślna wartość nie zostanie użyta.
Przekazywanie właściwości za pomocą operatora rozwinięcia
Niekiedy przekazywanie właściwości może okazać się bardzo uciążliwe:
function Profile({person, size, isSepia, thickBorder}) {
return (
<div className="card">
<Avatar
person={person}
size={size}
isSepia={isSepia}
thickBorder={thickBorder}
/>
</div>
);
}
Ogólnie rzecz biorąc, nie ma niczego złego w powtarzającym się kodzie - czasami może to nawet pozytywnie wpłynąć na jego czytelność. Z reguły jednak zależy nam na zwięzłości. Niektóre komponenty przekazują potomkom wszystkie swoje właściwości, jak to ma miejsce w przypadku Profile
i Avatar
poniżej. Z racji tego, że Profile
nie korzysta z żadnej z właściwości, warto użyć operatora rozwinięcia (ang. spread operator):
function Profile(props) {
return (
<div className="card">
<Avatar {...props} />
</div>
);
}
To sprawi, że wszystkie właściwości komponentu Profile
trafią do Avatar
bez konieczności wypisywania każdej z nich.
Używaj operatora rozwinięcia z umiarem. Jeśli nagminnie używasz go w niemal każdym komponencie, to coś jest nie tak. Zwykle świadczy to o potrzebie podzielenia komponentów i przekazania potomków jako JSX. Ale o tym za chwilę!
Przekazywanie potomków jako JSX
Dość często można spotkać takie oto zagnieżdżenie wbudowanych znaczników przeglądarkowych:
<div>
<img />
</div>
W podobny sposób można także zagnieździć własne komponenty:
<Card>
<Avatar />
</Card>
Kiedy zagnieżdżasz jakiś kod wewnątrz znacznika JSX, komponent nadrzędny do tego kodu otrzyma go jako wartość we właściwości children
. Dla przykładu, poniższy komponent Card
otrzyma właściwość children
ustawioną na <Avatar />
i wyrenderuje ją wewnątrz kontenera div
:
import Avatar from './Avatar.js'; function Card({children}) { return <div className="card">{children}</div>; } export default function Profile() { return ( <Card> <Avatar size={100} person={{ name: 'Katsuko Saruhashi', imageId: 'YfeOqp2', }} /> </Card> ); }
Spróbuj zastąpić <Avatar>
wewnątrz <Card>
jakimś tekstem, aby zobaczyć na własne oczy, że komponent Card
może opakowywać dowolną treść. Nie musi on “wiedzieć”, co renderuje. Ten wzorzec ma szerokie spektrum zastosowań i z pewnością spotkasz się z nim jeszcze nieraz.
Komponent z właściwością children
można sobie wyobrazić jako taki z “dziurą”, którą komponent nadrzędny może “zapełnić” dowolnym kodem JSX. Dość często stosuje się children
w komponentach opakowujących coś wizualnie: panelach, siatkach itp.
Autor ilustracji Rachel Lee Nabors
Jak właściwości zmieniają się w czasie
Komponent Clock
przedstawiony poniżej otrzymuje od swojego “rodzica” dwie właściwości: color
oraz time
. (Celowo pominęliśmy tu kod rodzica, ponieważ korzysta on ze stanu, o którym będzie mowa w dalszych rozdziałach.)
Spróbuj zmienić kolor, wybierając opcję z poniższej listy rozwijanej:
export default function Clock({ color, time }) { return ( <h1 style={{ color: color }}> {time} </h1> ); }
Ten przykład pokazuje, że komponent może otrzymywać wartości właściwości zmienne w czasie. Właściwości nie są zawsze statyczne! Tutaj wartość dla time
zmienia się co sekundę, a dla color
w momencie wybrania opcji z listy rozwijanej. Właściwości odzwierciedlają dane komponentu w określonym momencie, a nie tylko na początku.
Warto jednak pamiętać, że właściwości są niemutowalne (ang. immutable) — określenie to pochodzi z informatyki i oznacza “niezmienność”. Kiedy komponent chce zmienić swoje właściwości (na przykład w odpowiedzi na interakcję użytkownika lub nowe dane), musi “poprosić” swojego “rodzica”, aby ten przekazał mu inne wartości - czyli nowy obiekt! Wtedy stare właściwości zostaną zapomniane, a niedługo potem silnik JavaScriptu odzyska zajmowaną przez nie pamięć.
Nie próbuj “zmieniać właściwości”. Kiedy zechcesz zareagować na dane wprowadzone przez użytkownika (jak np. zmiana wybranego koloru), musisz “ustawić stan”, o czym nauczysz się w rozdziale pt. Stan - Pamięć komponentu.
Powtórka
- Aby przekazać właściwości, dodaj je do kodu JSX, tak jak to robisz z atrybutami w HTML-u.
- Aby odczytać wartości właściwości, użyj destrukturyzacji
function Avatar({ person, size })
. - Możesz ustawić domyślną wartość, np.
size = 100
, która zostanie użyta, gdy właściwość nie ma wartości lub jest ona ustawiona naundefined
. - Możesz przekazać wszystkie właściwości za pomocą operatora rozwinięcia
<Avatar {...props} />
; ale nie nadużywaj tego sposobu! - Zagnieżdżony kod JSX, jak np.
<Card><Avatar /></Card>
, zostanie przekazany do komponentuCard
jako właściwośćchildren
. - Właściwości są jak niezmienialne “migawki” z danego momentu w czasie: każde renderowanie komponentu dostarcza nową wersję właściwości.
- Nie można zmieniać wartości właściwości. Jeśli potrzebujesz interaktywności, musisz ustawiać stan.
Wyzwanie 1 z 3: Wyodrębnij komponent
Ten komponent Gallery
zawiera bardzo podobny kod dla dwóch profili. Wyodrębnij z niego komponent Profile
, aby zmniejszyć powtarzalność w kodzie. Następnie pomyśl, jakie właściwości należy przekazać do Profile
.
import { getImageUrl } from './utils.js'; export default function Gallery() { return ( <div> <h1>Wybitni naukowcy</h1> <section className="profile"> <h2>Maria Skłodowska-Curie</h2> <img className="avatar" src={getImageUrl('szV5sdG')} alt="Maria Skłodowska-Curie" width={70} height={70} /> <ul> <li> <b>Profesja: </b> fizyka i chemia </li> <li> <b>Nagrody: 4 </b> (Nagroda Nobla w dziedzinie fizyki, Nagroda Nobla w dziedzinie chemii, Medal Davy'ego, Medal Matteucciego) </li> <li> <b>Odkrycia: </b> polon (pierwiastek) </li> </ul> </section> <section className="profile"> <h2>Katsuko Saruhashi</h2> <img className="avatar" src={getImageUrl('YfeOqp2')} alt="Katsuko Saruhashi" width={70} height={70} /> <ul> <li> <b>Profesja: </b> geochemia </li> <li> <b>Nagrody: 2 </b> (Nagroda Miyake w dziedzinie geochemii, Nagroda Tanaki) </li> <li> <b>Odkrycia: </b> metoda pomiaru dwutlenku węgla w wodzie morskiej </li> </ul> </section> </div> ); }