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 wprowadza do pojęcia 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.

Klasy mają kilka dodatkowych cech, które omówimy w kolejnych rozdziałach. Do tego czasu będziemy używać komponentów funkcyjnych ze względu na ich zwięzły zapis.

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 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')
);

Przetestuj kod na CodePen

Podsumujmy, co dzieje się w tym przykładzie:

  1. Wywołujemy ReactDOM.render() z elementem <Welcome name="Sara" />.
  2. React wywołuje komponent Welcome z właściwościami {name: 'Sara'}.
  3. Nasz komponent Welcome jako wynik zwraca element <h1>Cześć, Sara</h1>.
  4. 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, aby Welcome 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')
);

Przetestuj kod na CodePen

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>
  );
}

Przetestuj kod na CodePen

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>
  );
}

Przetestuj kod na CodePen

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, ż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ę komponentem wielokrotnego użytku.

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.