React - wprowadzenie
Najważniejszą ideą w technologii react jest możliwość tworzenia własnych znaczników (Tags) rozszerzających HTML. Osiągnięto to poprzez przetwarzanie takiego rozszerzonego kodu w JavaScript. Dzięki temu odawane znaczniki (obiekty) są bardziej dynamiczne – mogą zmieniać swój stan i swoje własności. Składnia rozszerzonego języka HTML, zanurzonego w JavaScript nazywa się JSX.
Rozszerzona składnia jest tłumaczona na zwykły JavaScript przy pomocy translatora babel.
Przykład:
Plik: helloworld.jsx
class HelloWorld extends React.Component {
render() {
return <h1>Hello World</h1>
}
}
ReactDOM.render(
<HelloWorld />
, document.getElementById('example00')
);
Definiuje on nowy znacznik HelloWorld. Każdy taki znacznik odpowiada obiektowi zbudowanemu na bazie klasy React.Component.
Funkcja render() zwraca (return) rozszerzony kod html (musi być zawarty w obrębie jednego znacznika - tu h1).
Oczywiście można wykorzystywać wcześniej zdefiniowane znaczniki.
Funkcja ReactDOM.render wstawia wygenerowany znacznik <HelloWorld /> do kodu na stronie html.
Wykorzystany w tym celu zostanjeie element example00.
Przykład strony z takim elementem (index.html):
<!DOCTYPE html>
<html>
<head>
<script src="node_modules/react/dist/react.js"></script>
<script src="node_modules/react-dom/dist/react-dom.js"></script>
</head>
<body>
<div id="example00"></div>
<script src="helloworld.js"></script>
</body>
</html>
Trzeba zwrócić uwagę, że do strony dołączono plik helloworld.js, a nie helloworld.jsx.
Czyli plik po przetworzeniu kompilatorem babel.
Jak to się robi?
1) Najpierw musimy zainstalować kompilator i odpowiednie biblioteki rReacta.
Działają one w środowisku NodeJs.
# pusty package.json:
npm init
# react:
npm install --save-dev react
npm install --save-dev react-dom
# kompilator babel:
npm install --save-dev babel-cli babel-preset-es2015
2) Teraz misimy zainstalować wtyczki do kompilatora [Uwaga! Można ten zapis skrócić do npm i ….]:
# + pluginy:
npm install babel-plugin-transform-react-jsx
3) Tworzymy plik .babelrc
Zawiera on informację co ma być tłumaczone:
{ "presets" : [["es2015", "react"]] }
4) Uruchamiamy kompilator:
# windows:
.\node_modules\.bin\babel --plugins transform-react-jsx index.jsx > helloworld.js
# linux:
./node_modules/.bin/babel --plugins transform-react-jsx helloworld.jsx > helloworld.js
5) Testujemy otwierając w przeglądarce plik index.html
create-react-app
Zazwyczaj wywołanie ReactDOM umieszczamy w odrębnym pliku. Tworzy się też struktury katalogów. W podkatalogu src umieszcza się źródłowe pliki, a w public - wynikową stronę.
Tą podstawową pracę może dla nas wykonać program (w NodeJS) create-react-app.. Program ten tworzy podstawowe struktury plików. Nie trzeba samemu wklejać bibliotek do index.html itd...:
npm install -g create-react-ap
create-react-app example01
cd example01
Aby uruchomić serwer testowy możemy wykorzystać menadżer pakietów NodeJS:
npm start
lub jego odpowiednik (lepiej kontroluje zależności) yarn:
yarn start
Własności
Znaczniki mają własności. Na przykład znacznik <a> ma własność href (<a href=”http://...”>; ...). W React znacznikom odpowiadają obiekty / klasy obiektów (1). Możemy też definiować własne klasy z własnościami używanymi w renderowaniu.
Przykład:
class Tekst extends Component {
render() {
return (
<span>{this.props.normal} <i>{this.props.italic}</i></span>
);
}
}
Objaśnienie:
Przedrostek this. oznacza "ten obiekt" - czyli odnosimy się wewnątrz obiektu do jego własności (props). Własności this.pros.normal i this.props.italic są definiowane przez nas (tekst normalny i kursywą).
Własności nie muszą być deklarowane - wystarczy je używać i pamiętać o zainicjowaniu.
Jak to zrobić? W trakcie wywołania. Tekst zapisany kursywą: <Tekst italic='abc' />
- Odwołanie do kodu JavaScript (tu: własności) w renderowanym kodzie umieszcza się w nawiasach {}.
Przekazywanie kodu jako własności
Załóżmy, że chcemy przekazać do obiektu kod wykonywany po kliknięciu w ten obiekt (znacznik). Zobaczmy to na przykładzie przycisku:
class Przycisk extends Component {
render() {
return (
<button onClick={this.props.akcja}>Kliknij {this.props.opis} </button>
);
}
}
Własność opis jest łańcuchem znaków - jak poprzednio.
Jeśli natomiast chcemy przekazać kod, który ma wywołać się w konkretnych okolicznościach (np. po kliknięciu w przycisk), tworzymy anonimową funkcję zwracającą kod do wykonania (funkcję). Może to być wykonane na 3 sposoby:
() => {alert(‘Klik’);}
function() { alert(‘Klik’);}
this.funkcja_obiektu
Zapis:
() => this.funkcja_obiektu()
jest skrótowym zapisem funkcji bez parametru (mówią o tym nawiasy po lewej).
Wynik funkcji podajemy po znakach '=>'.
Jeśli w ciele funkcji jest wykorzystywane this, to dwie ostatnie metody potrzebują instrukcji bind:
function() { this.setState(klik: true); }.bind(this);
this.funkcja_obiektu.bind(this);
Przykład:
import React, {Component} from 'react';
class Tekst extends Component {
render() {
return ( <span>{this.props.normal} <i>{this.props.italic}</i></span>);
}
}
class Przycisk extends Component {
render() {
return (
<button onClick={this.props.akcja}>Kliknij {this.props.opis} </button>
);
}
}
class App extends Component {
klikniecie() { // funkcja JavaScript
console.log('Klik'); alert('Klik');
}
render() {
return (
<div>
<Tekst italic='kursywa' normal='test' /><br />
<Przycisk akcja={ () => { alert('Klik1'); } } opis='klik1/alert' />
<Przycisk akcja={ function() { alert('klik2'); } }
opis='klik2/function()' />
<Przycisk akcja={ () => this.klikniecie() } opis='klik3/this' />
<Przycisk akcja={ function() { this.klikniecie(); }.bind(this) }
opis='klik4/this/bind' />
</div>
);
}
}
export default App;
Stany
Własności obiektów gromadzone w props nie mogą być zmieniane wewnątrz komponentu2. Zmiany mogą być realizowane poprzez użycie state (stan).
Każda zmiana stanu polega na wyliczeniu nowego stanu (state). Robi się to funkcją setState, która równocześnie powoduje ponowne renderowanie (wyświetlenie) całego obiektu.
Weźmy prosty przykład (state zawiera tylko jedną zmienną: licznik).
state: {
licznik: number,
};
.
klikniecie() {
let nowa_wartosc = this.state.licznik+1;
this.setState({licznik: nowa_wartosc}); // spowoduje ponowny render()
}
Funkcja klikniecie() w powyższym przykładzie kryje pewien problem, Kod może nie zadziałać poprawnie, jeśli ktoś zdąży 2 razy kliknąć zanim React przetworzy operację setState (co może nastąpić asynchronicznie). Zobacz: https://facebook.github.io/react/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
Dlatego zaleca się zamiast podawania nowej, wyliczonej wartości, zdefiniować funkcję wyliczającą tą wartość:
this.setState( (prevState, props) => ({ licznik: prevState.licznik + 1 }));
Zapis „(prevState, props) => …” to funkcja o parametrach prevState i props.
Kompletny przykład:
import React, {Component} from 'react';
import {Button,Text} from 'react-bootstrap';
class App extends Component {
state: {
licznik: number,
};
constructor(props: {}) { // konieczne ustawienie stanu początkowego
super(props);
this.state = {
licznik: 0,
};
}
.
klikniecie() {
console.log('zwieksza');
// źle:
// this.setState({licznik: this.state.licznik+1});
// Dobrze:
this.setState( // spowoduje ponowny render()
(prevState, props) => ({
licznik: prevState.licznik + 1
}));
}
render() {
return (
<div>
<p>Licznik={this.state.licznik.toString()}</p>
<Button bsStyle="primary" onClick={ this.klikniecie.bind(this) } >
Kliknij {this.state.licznik.toString()}</Button><br />
inny zapis:
<Button bsStyle="primary" onClick={ ()=>this.klikniecie() } >
Kliknij {this.state.licznik.toString()}</Button>
</div>
);
}
}
export default App;
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.
Wyjaśnia to przykład:
import React, {Component} from 'react';
import {Button} from 'react-bootstrap';
class Przycisk extends Component {
props : {
opis : ?string,
licznik : ?number,
akcja : ?string
};
render() {
return (
<Button bsStyle="primary" 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;
Znaki zapytania oznaczają możliwość braku (pominięcia) wartości lub użycia null. Zobacz: https://flow.org/en/docs/types/maybe/
Przypisy do rozdziału:
1 Każda utworzona instancja znacznika jest również obiektem klasy w klasycznym rozumieniu. Więc można się odwoływać do this.property (**nie mylić z this.props.property**). Te własności są zazwyczaj wykorzystywane do przechowywania informacji niepowiązanych bezpośrednio z renderowaniem.
2 Komponent nie ma kontroli nad wartością własności, ale nie oznacza to – że jest to stała. React może używać tych samych instancji z innymi własnościami props.