Dobre praktyki

Typowanie

W Javascript nie ma wymogu określania wprost typów zmiennych (typ determinuje ich użycie - tak zwany "duck typing"). Niemniej w złożonych projektach warto rozważyć takie "obostrzenie". Konsekwentne stosowanie typów jest możliwe (a wymuszane rzez użycie Flow).

Poniższy przykład został napisany zgodnie z tymi regułami. Pokazuje on przy okazji ponownie powiązanie stanów z własnościami.

Do renderowania komponentów używa się własności. Natomiast dynamika zmian jest związana ze zmianami stanów. Na podstawie stanu wylicza się nowe wartości własności (props) – ale nie poprzez podstawienie (zmianę zmiennych), tylko powstanie komponentów wizualnych z aktualnymi własnościami.

Zobacz przykład:

import React, {Component} from 'react';

class Przycisk extends Component {

    props : {
        opis : ?string,
        licznik : ?number,
        akcja : ?string
    };

    render() {
        return (
        <button
        onClick={this.props.akcja}>
          Kliknij: {this.props.opis} [{this.props.licznik}] 
        </button>
        );
    }
}

class App extends Component {
    state: {
        licznik: ?number,
    };

    constructor(props: {}) {
        super(props);
        this.state = {
            licznik: 1,
        };
    }

    klikniecie() {
        this.setState(
        (prevState, props) => ({
            licznik: prevState.licznik + 1
        }));
    }

    render() {
        return (
          <div className="App">
            <Przycisk opis="licznik"
            licznik={this.state.licznik} akcja={() => this.klikniecie()} />
          </div>
        );
    }
}
export default App;

Najważniejsze zasady

1) Defiiowane przez użytkownika znaczniki muszą mieć identfikatory zaczynające się od dużej litery.

2) Wyrażenia JavaScript w definicji własności etc... podajemy w nawiasach klamrowych {}. Na przykład: <MyComponent foo={1 + 2 + 3 + 4} />

3) Łańcuchy znaków mogą występować jako wartość własności - bez {}:

<MyComponent message="hello world" />

<MyComponent message={'hello world'} />

Ujęcie w nawiasy nie jest w tym wypadku bez znaczenia - gdyż występuje różnica w obsłudze HTML escapes. Te dwa nie są równoważne:

<MyComponent message="<3" />

<MyComponent message={'<3'} />

4) Własność domyślnie otrzymuje wartość true.

Te dwa przykłady są równoważne:

<MyTextBox autocomplete />

<MyTextBox autocomplete={true} />

5) W definicji własnosci można używać operatora spread:

function App1() {
return <Greeting firstName="Ben" lastName="Hector" />;
}
function App2() {
const props = {firstName: 'Ben', lastName: 'Hector'};
return <Greeting {...props} />;
}

6) Komponent React może zwracać tablicę elementów:

render() {
// No need to wrap list items in an extra element!
return [
// Don't forget the keys :)
<li key="A">First item</li>,
<li key="B">Second item</li>,
<li key="C">Third item</li>,
];
}

7) W definicji potomka można podać wyrażenie JavaScript ujęte w nawiasy {}. Równoważne przykłady:

<MyComponent>foo</MyComponent>
<MyComponent>{'foo'}</MyComponent>

8) Można podać funkcję w definicji potomka

// Calls the children callback numTimes to produce a repeated component
function Repeat(props) {
let items = [];
for (let i = 0; i < props.numTimes; i++) {
items.push(props.children(i));
}
return <div>{items}</div>;
}
function ListOfTenThings() {
return (
<Repeat numTimes={10}>
{(index) =><div key={index}>This is item {index} in the list</div>}
</Repeat>
);
}

9) Booleans, Null, i Undefined są ignorowane

<div />
<div></div>
<div>{false}</div>
<div>{null}</div>
<div>{undefined}</div>
<div>{true}</div>

więcej: https://reactjs.org/docs/jsx-in-depth.html

Poradnik

Wiele elementów programu React można wykonać na więcej niż jeden sposób. Poniższe uwagi dotyczą najważniejszych wyborów jakich musimy w związku z tym dokonać. Lista ta nie jest kompletna, ani nie stanowi niewzruszalnego kanonu. Wydaje się jednak być zgodna z trendami wyznaczanymi przez twórców React/Redux.

1. Styl obiektowy.

Teoretycznie możemy tworzyć komponenty React bez definicji klas wprowadzonych w ES6:

var React = require('react');

React.createClass({
    render: function(){/*implementacja*/}
})

Taki sposób można wybrać, jeśli nie korzystamy z translacji babel.

Drugą możliwością jest użycie komponentów funkcyjnych – czyli funkcji zwracających kod JSX:

let Komponent = (props) => {
  return ( <div>  ...</div> );
}

Ten zapis można skrócić do (jeśli występuje tylko return):

let Komponent = props => ( <div>  ...</div> )

Taka definicja jest możliwa w przypadku prostych komponentów bezstanowych. W pozostałych przypadkach stosujemy notację obiektową:

class Komponent extends Component {
  render() { }
}

2. Użycie Arrow Function

W obsłudze zdarzeń możemy teoretycznie używać zwykłych funkcji (metod) komponentu:

<button onClick={ function() { this.funkcjaOnClick(); }.bind(this) } />

Dużo prościej wygląda jednak taka implementacja:

class App extends Component {

funkcjaOnClick = () => { . implementacja .. }

render() {

  return ( ... <button onClick= { this.funkcjaOnClick }> ... );
}

3. Unikaj hakowania React’a

React to technologia dość świeża i trudno liczyć na to, że implementacja pozostanie bez zmian. Dlatego należy ograniczyć się do tego, co jest opisane w oficjalnej dokumentacji. W szczególności nie należy sięgać wprost do kontekstu komponentu (context), ani bibliotek wewnętrznych. Na przykład można w sieci znaleźć przykłady zmiany tytułu strony z odwołaniem do otoczenia:

import ExecutionEnvironment from 'react/lib/ExecutionEnvironment';

Zdecydowanie lepiej jest użyć odrębnego modułu exenv:

iport { ExecutionEnvironment } from 'exenv';
...

4.W złożonych komponentach nie stosuj przekazywania danych poza props.

Teoretycznie zamiast

render() { return (<Komponent parametr=wartosc />) }

można parametr przekazać przez zmienne globalne itp… To jednak zdecydowanie nie jest dobry pomysł.

5. Stosowanie Flow i konsekwentne typowanie zmiennych i funkcji.

Flow z pewnością ułatwia pisanie kodu. Umożliwia on stosowanie dodatkowych elementów programowania obiektowego – jak definicja typów (słowo type) i ich stosowanie.

  1. Podział odpowiedzialności

Programowanie w React polega na podziale strony na fragmenty drzewa (React/Virtual DOM) obejmujące rodzica wraz z ewentualnymi potomkami.

render() {
return (
    <Parent>
      <ChildA />
      <ChildB />
      <ChildC />
    </Parent>
  );
}}

Idea jednego rodzica wyznacza nam podstawową metodę podziału. Nie możemy na przykład stworzyć "rodzica":

<table>
<tr>
<Kolumny />
</tr>
</table>

bo wymagane jest zazwyczaj wiele równoważnych kolumn w tabeli (istnieje idea wprowadzenie fragmentów i zwartych w pustych tagach: <> … </> – ale na razie nie jest standardem).

Utworzenie komponentów może być też zdeterminowane przez chęć stworzenia szablonów (jak pokazano w rozdziale dotyczącym szablonów), czy stosowania routingu (na przykład dane o historii strony są dostępne wyłącznie wewnątrz<Rouring></Routing>).

Gdy stosujemy Redux – naturalną jest tendencja do przenoszenia logiki działania do reduktora.

Te dwie zasady (logika w redutorze i podział opisu strony w JSX) w znacznym stopniu determinują konstrukcję aplikacji. Dodatkowe moduły można tworzyć dla obsługi działań wykraczających poza UI – na przykład komunikację Ajax.

Dodatkowe informacje:

6) Zarządzanie zależnościami

Problemy zależności wiąże się z używaniem modułów nie związanych z widokami. React nie determinuje postępowania w takich sytuacjach.

Weźmy na przykład funkcję logowania . Powinna ona obejmować:

  • wysyłanie żądanie do backendu z poświadczeniami, aby utworzyć token

  • po otrzymaniu tokena zachowanie go w lokalnej pamięci

  • przekierowanie do ostatnio odwiedzanej strony

Mamy zależności od:

  • usługi backendu do wysyłania żądań i otrzymywania odpowiedzi

  • pamięci lokalnej

  • Historii klienta do przekierowania

Podstawowym narzędziem interakcji między komponentami jest wykorzystanie stanu (state) na wyższym poziomie. Powyższy przykład pochodzi z bloga (https://blog.scottlogic.com/2017/02/28/relogic.html), którego autor dowodzi korzyści z wykorzystania do tego kontekstu (context). Jednak to nie jest działanie „w duchu React’a”. Jeśli używasz Redux - rozwiązaniem może być dekompozycja reducera – jak pokazano w dokumentacji Redux’a: https://redux.js.org/recipes/computing-derived-data

Możemy zdefiniować stany – zależne od siebie i podzielić interakcje zgodnie ze schematem (w tym przykładzie stan b zależ od stanu a):

function state_a(state, action) { }
// stan b zależy od obliczeń w oparciu o stan a
function state_b(state, action, a) { } 

function new_state(state = {}, action) {
let a = state_a(state.a, action);
let b = state_b(state.b, action, a); 
return { a, b };
}

Więcej informacji: