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.
- 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:
- https://reactjs.org/docs/code-splitting.html
- https://reactjs.org/docs/fragments.html
- https://medium.com/dailyjs/techniques-for-decomposing-react-components-e8a1081ef5da
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:
- https://github.com/airbnb/javascript/tree/master/react
- https://blog.risingstack.com/react-js-best-practices-for-2016/
- https://gist.github.com/datchley/4e0d05c526d532d1b05bf9b48b174faf
- Thinking in React Facebook React Docs
- You're missing the point of React - by Dan Abramov
- Best Practices for building large React apps - by Alex Lopatin
- Atomic Design - by Brad Frost (not React specific, but applicable)