Komponenty i właściwości
Komponenty pozwalają podzielić interfejs użytkownika na niezależne, pozwalające na ponowne użycie części i myśleć o każdej z nich osobno. Ta strona zawiera wprowadzenie do idei komponentów. W osobnym rozdziale opisaliśmy szczegółową dokumentację API komponentów.
Koncepcyjnie, komponenty są jak javascriptowe funkcje. Przyjmują one arbitralne wartości na wejściu (nazywane “właściwościami” (ang. props)) i zwracają reactowe elementy opisujące, co powinno się pojawić na ekranie.
Komponenty funkcyjne i klasowe
Najprostszym sposobem na zdefiniowanie komponentu jest napisanie javascriptowej funkcji:
function Welcome(props) {
return <h1>Cześć, {props.name}</h1>;
}
Ta funkcja jest poprawnym reactowym komponentem, ponieważ przyjmuje pojedynczy argument “props” (który oznacza “właściwości”, z ang. properties), będący obiektem z danymi, i zwraca reactowy element. Takie komponenty nazywamy “komponentami funkcyjnymi”, gdyż są one dosłownie javascriptowymi funkcjami.
Do zdefiniowania komponentu możesz również użyć klasy ze standardu ES6:
class Welcome extends React.Component {
render() {
return <h1>Cześć, {this.props.name}</h1>;
}
}
Obydwa powyższe komponenty są równoważne z punktu widzenia Reacta.
Komponenty funkcyjne i klasowe mają kilka dodatkowych cech, które omówimy w kolejnych rozdziałach.
Renderowanie komponentu
Poprzednio napotykaliśmy reactowe elementy, które reprezentowały znaczniki DOM:
const element = <div />;
Elementy mogą również reprezentować komponenty zdefiniowane przez użytkownika:
const element = <Welcome name="Sara" />;
Kiedy React widzi element reprezentujący komponent zdefiniowany przez użytkownika, przekazuje do niego JSX-owe atrybuty i potomków jako jeden obiekt. Obiekt ten nazywamy “właściwościami” komponentu.
Dla przykładu, poniższy kod renderuje na stronie napis “Cześć, Sara”:
function Welcome(props) { return <h1>Cześć, {props.name}</h1>;
}
const element = <Welcome name="Sara" />;ReactDOM.render(
element,
document.getElementById('root')
);
Podsumujmy, co dzieje się w tym przykładzie:
- Wywołujemy
ReactDOM.render()
z elementem<Welcome name="Sara" />
. - React wywołuje komponent
Welcome
z właściwościami{name: 'Sara'}
. - Nasz komponent
Welcome
jako wynik zwraca element<h1>Cześć, Sara</h1>
. - React DOM w optymalny sposób aktualizuje drzewo DOM, aby odpowiadało elementowi
<h1>Cześć, Sara</h1>
.
Wskazówka: Zawsze zaczynaj nazwy komponentów od dużej litery.
React traktuje komponenty zaczynające się od małej litery jako tagi drzewa DOM. Na przykład,
<div />
reprezentuje HTML-owy znacznik ‘div’, ale już<Welcome />
reprezentuje komponent i wymaga, abyWelcome
było w zasięgu (ang. scope).Aby dowiedzieć się więcej o uzasadnieniu tej konwencji, przeczytaj dogłębną analizę składni JSX.
Kompozycja komponentów
Komponenty przy zwracaniu wyniku mogą mogą odwoływać się do innych komponentów. Pozwala to używać tej samej abstrakcji komponentu na dowolnym poziomie szczegółowości. Przycisk, formularz, okno dialogowe, ekran - w aplikacjach reactowych tego typu składniki są zwykle reprezentowane przez dedykowane komponenty.
Możemy dla przykładu stworzyć komponent App
, który wielokrotnie renderuje komponent Welcome
:
function Welcome(props) {
return <h1>Cześć, {props.name}</h1>;
}
function App() {
return (
<div>
<Welcome name="Sara" /> <Welcome name="Cahal" /> <Welcome name="Edite" /> </div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
Nowe aplikacje reactowe na samej górze drzewa zazwyczaj renderują pojedynczy komponent App
. Jeśli jednak musisz zintegrować Reacta z istniejącą aplikacją, możesz zacząć od samego dołu, dodając niewielkie komponenty (np. Button
) i stopniowo przepisywać całą strukturę aż do samej góry.
Wyodrębnianie komponentów
Nie bój się dzielenia komponentów na mniejsze części.
Rozważ poniższy komponent Comment
:
function Comment(props) {
return (
<div className="Comment">
<div className="UserInfo">
<img className="Avatar"
src={props.author.avatarUrl}
alt={props.author.name}
/>
<div className="UserInfo-name">
{props.author.name}
</div>
</div>
<div className="Comment-text">
{props.text}
</div>
<div className="Comment-date">
{formatDate(props.date)}
</div>
</div>
);
}
Przyjmuje on obiekt author
, napis text
i datę date
jako właściwości i zwraca strukturę opisującą komentarz na portalu mediów społecznościowych.
Zmiana tego komponentu czy ponowne użycie jego poszczególnych części może okazać się skomplikowane z powodu całego tego zagnieżdżenia. Rozbijmy go zatem na kilka mniejszych komponentów.
Najpierw wydzielmy komponent Avatar
:
function Avatar(props) {
return (
<img className="Avatar" src={props.user.avatarUrl} alt={props.user.name} /> );
}
Avatar
nie musi wiedzieć, że jest renderowany wewnątrz komponentu Comment
. Dlatego też daliśmy jego właściwości bardziej ogólną nazwę user
zamiast author
.
Zalecamy nadawanie nazw właściwościom z punktu widzenia komponentu, a nie kontekstu, w którym jest używany.
Możemy teraz uprościć nieco komponent Comment
:
function Comment(props) {
return (
<div className="Comment">
<div className="UserInfo">
<Avatar user={props.author} /> <div className="UserInfo-name">
{props.author.name}
</div>
</div>
<div className="Comment-text">
{props.text}
</div>
<div className="Comment-date">
{formatDate(props.date)}
</div>
</div>
);
}
Następnie wydzielmy komponent UserInfo
, który wyrenderuje Avatar
obok nazwy użytkownika:
function UserInfo(props) {
return (
<div className="UserInfo"> <Avatar user={props.user} /> <div className="UserInfo-name"> {props.user.name} </div> </div> );
}
To pozwala nam uprościć Comment
jeszcze bardziej:
function Comment(props) {
return (
<div className="Comment">
<UserInfo user={props.author} /> <div className="Comment-text">
{props.text}
</div>
<div className="Comment-date">
{formatDate(props.date)}
</div>
</div>
);
}
Wyodrębnianie komponentów może z początku wydawać się żmudnym zajęciem, ale posiadanie palety pozwalających na ponowne użycie komponentów jest opłacalne w większych aplikacjach. Dobrą praktyczną zasadą jest to, że jeśli część twojego interfejsu użytkownika jest używana wielokrotnie (np. Button
, Panel
, Avatar
) lub jest ona dostatecznie skomplikowana sama w sobie (np. App
, FeedStory
, Comment
), jest ona dobrym kandydatem do stania się oddzielnym komponentem.
Właściwości są tylko do odczytu
Bez względu na to, czy zadeklarujesz komponent jako funkcję czy klasę, nie może on nigdy modyfikować swoich właściwości. Rozważ następującą funkcję sum
:
function sum(a, b) {
return a + b;
}
Funkcje tego typu nazywane są “czystymi” (ang. pure function), dlatego że nie próbują one zmieniać swoich argumentów i zawsze zwracają ten sam wynik dla tych samych argumentów.
W przeciwieństwie do poprzedniej funkcji, ta poniżej nie jest “czysta”, ponieważ zmienia swój argument.
function withdraw(account, amount) {
account.total -= amount;
}
React jest bardzo elastyczny, ale ma jedną ścisłą zasadę:
Wszytkie komponenty muszą zachowywać się jak czyste funkcje w odniesieniu do ich właściwości.
Rzecz jasna, interfejsy użytkownika w aplikacjach są zwykle dynamiczne, zmieniają się w czasie. W kolejnym rozdziale wprowadzimy nowe pojęcie “stanu”. Stan pozwala komponentom reactowym na zmianę swojego wyniku w czasie, w odpowiedzi na akcje użytkownika, żądania sieciowe itp. bez naruszania powyższej zasady.