React
React - start
Facebook 2013. Utworzenie środowiska deweloperskiego w kilku krokach:
Ten artykuł to notatki sporządzone przy okazji kursu React od podstaw. Teoria i praktyka autorstwa Bartka Borowczyka. Który to kurs serdecznie polecam, jak zresztą wszystkie kursy robione przez Bartka - więcej: Websamuraj - Kursy online z programowania i tworzenia stron.
"The Concepts You Should Know in React"
Niezbędnik
- Środowisko uruchomieniowe Node.js, zawiera m.in. silnik V8. Domyślnie dołączany jest menadżer pakietów npm. Instalacja zależna od systemu operacyjnego jest opisana dokładnie na stronie (install Node.js 12 in Ubuntu 19.04 | NodeSource Node.js Binary Distributions / Debian and Ubuntu based distributions (deb) / Manual installation). Składniki:
- Menedżer pakietów: domyślnie w Node.js zawarty jest npm. Innym popularnym menedżerem pakietów jest yarn. Gant Laborde "NPM vs Yarn Cheat Sheet"
- libuv - biblioteka udostępniająca funkcje systemu operacyjnego, m in zapewnia asynchroniczność; również jest integralną częścią Node.js
- Repl.it - Online Nodejs Editor and IDE - interfejs poleceń Node.js; tutaj udostępniony online i można poćwiczyć
- nodemon Node.js monitor, to już się przydaje do pracy z samym Node.js i frameworkami, które go używają, do Reacta nie jest potrzebny.
- Edytor, np Visual Studio Code z dodatkami: Live Server, Prettier, snippety (np. Simple React Snippets, by Burke Holland), Material Icon, itp. Extensions for the Visual Studio family of products
- W przeglądarce dodatki/rozszerzenia: React Developer Tools i JSON Viewer.
- Last but not least sam React i kompilator Babel; tak jak opisane jest poniżej z CDN lub React App.
Czyli podsumowując, z powyższego oprócz rzeczy, które i tak są zainstalowane takie jak przeglądarka czy edytor jedynym elementem do zainstalowania jest Node.js.
Dodatkowe narzędzia
- webpack, zaawansowany menedżer pakietów, którego zadaniem jest bundling czyli kompilacja wszystkich plików w zoptymalizowany zestaw plików czytany przez przeglądarkę.
- Bazy danych: MongoDB (MongoDB Atlas cloud MongoDB service.), Firebase.
- Framework Express.
- Heroku - Cloud Application Platform
- Netlify - All-in-one platform for automating modern web projects
- Narzędzia JSON: JSONPlaceholder | JSONLint.
- API: darmowe źródła do testowania - przeniesione do artykułu o Node.js
- Przy okazji: HTTP Cats What is Docker?, UFO Test: Framerates, GreenSock - GSAP, "How to Turn Your Website into a Mobile App with 7 Lines of JSON"
React na trzy sposoby
W dokumentacji jest napisane, że Reacta można użyć na trzy sposoby:
- narzędzia online: Codepen, CodeSandbox, Glitch, StackkBlitz - to się przydaje na początek
- Dodać do pliku w postaci CDN-ów
- Stworzyć w środowisku deweloperskim React App
Dodawanie do plików
- Przykładowy plik do obejrzenia
- CDN - są tam dwie biblioteki React (React i React DOM) oraz Babel; z linków małpę i numer wersji można usunąć wtedy dociągnie się najnowsza
Jeżeli otworzymy przykładowy plik w przeglądarce i widzimy Hello World, a w konsoli obiekt React, to znaczy że wszystko jest w porządku.
Koniecznie trzeba podać typ skryptu w przypadku kompilatora Babel, w każdym innym można pominąć.
<script type="text/babel">
Błąd: "Cross origin requests are only supported for HTTP." wynika z otwarcia pliku jako //file; trzeba użyć localhosta.
Tak wygląda plik HTML z dodanymi CDN-ami React.
<body>
<div id="root">
<!-- kontener na aplikację react -->
</div>
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel" src="App.js"></script>
</body>
Moduł React
class App extends React.Component {
state = {
state_element: state_value
}
handleEvent = () => {
this.setState({
state_element: this.state.state_element
})
}
render() {
return (
<>
{/* komentarz w nawiasach */}
<button disabled={this.state.order ? false : true} onClick={this.handleEvent}>-</button>
<span> {this.state.state_element} </span>
<button disabled={this.state.storage === this.state.order ? true : false} onClick={this.handleEvent}>+</button>
</>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'))
Powyżej są wersje deweloperskie; wersja produkcyjna jest tu: CDN Links.
Create React App
Jest to bardziej zaawansowana metoda. Bardziej wymagająca, ale daje więcej możliwości. Przede wszystkim umożliwia stworzenie aplikacji z dołączonymi dowolnymi bibliotekami. W całości po ściągnięciu i rozpakowaniu takie środowisko zajmuje około 150 MB. Po skończeniu pracy eksportuje się wersję produkcyjną, która jest zminimalizowana i zoptymalizowana.
- React App | ReactDOMServer
- "Optimal file structure for React applications" | "A general and flexible project structure that works for all projects in any ecosystem"
- "Create a React app from scratch with Webpack and Babel"
Poniższe polecenia są z manuala. Pierwsze tworzy środowisko, drugim wchodzimy do katalogu, trzecim uruchamiamy serwer.
npx create-react-app my-app
cd my-app
npm start
-
Jeżeli na końcu polecenia jest kropka to tworzy aplikację w bieżącym katalogu.
-
Domyślnie używa yarna, ale można ot zmienić 'npx create-react-app --use-npm .'
-
Polecenie uruchamiające musi być wydane w katalogu aplikacji. Domyślny adres serwera to [ http://localhost:3000/ ]
-
Zatrzymanie serwera Ctrl+c.
-
W katalogu aplikacji cztery katalogi:
-
/.git - repozytorium git
-
/node_modules - katalog z domyślnymi i doinstalowanymi modułami
-
/public - pliki dla serwera, do wyświetlenia na stronie, nie jest pakietowane
-
package.json - konfiguracja, zależności, co jest zainstalowane i wersje pakietów
-
/src - tutaj pracujemy
- index.js - główny plik aplikacji, wszystkie importy i połączenia, generuje stronę
- index.css - CSS
- /components - można utworzyć osobny katalog dla komponentów, dla większego porządku
- App.js - plik komponentu
- App.css
-
.gitignore
-
package.json
-
package-lock.json
-
README.md
Przykładowy plik index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './components/App';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
serviceWorker.unregister();
Przykładowy plik App.js:
import React, { Component } from 'react';
import './App.css';
class App extends Component {
render() {
return (
<div className="App" >
<p>no elo </p>
</div>
)
}
}
export default App;
Instalowanie dodatkowej paczki:
npm install nazwa_biblioteki
Żeby biblioteka działała trzeba ją importować, moduły importuje się wskazując ścieżkę:
import Nazwa from 'nazwa_biblioteki';
import Nazwa_komponentu './Komponent';
Nazwy określa się przy imporcie i można je zmieniać z domyślnych, ale nazwy pobieranych muszą się zgadzać. Jeżeli App ma końcówkę .js można ją pominąć, wszystkie inne trzeba wpisać. Na końcu musi być polecenie eksportu, żeby inne moduły i główna strona mogły korzystać z zawartości pliku. Nazwa pliku powinna być zgodna z nazwą klasy, którą zawiera.
Składniki:
- serwer
- Babel - tłumaczy na starszą wersję.
- Webpack pakietuje (bandluje) pliki. Dynamicznie utworzony /static/js/bundle.js - cały js wynikowy.
- Eslint poprawność kodu, wskazywanie błędów składni.
Komponenty klasowe i funkcyjne
Aplikacja React składa się z komponentów - trochę przypominają moduły JS. Nazwy wszystkich komponentów piszemy zaczynając od dużej litery
Każdy komponent posiada trzy cechy:
- rodzaj - tag
- właściwości tzw. propsy, czyli obiekty przekazujące dane; komponent posiada je zawsze, jeśli nie są zdeklarowane to są nullem, a domyślną wartością atrybutu bez przypisanej wartości jest true
- treść czyli dzieci (children) elementy zagnieżdzone, które również mogą być komponentami
Komponent można zapisać na dwa sposoby:
- używając funkcji React.createelement()
- JSX
Tak wygląda przykładowy komponent React, jak widać do funkcji podajemy trzy parametry określające te trzy cechy komponentu: rodzaj propsy i treść:
let component = React.createElement(
"div",
{ style: { color: "black" } },
\`Treść przykładowego komponentu\`
);
ReactDOM.render(welcome, document.querySelector("#root"));
W praktyce sposób ten nie jest stosowany, bo dużo prościej da się to uzyskać JSX-em. Nie ma żadnego powodu, żeby stosowac powyższy zapis, popatrz na niego, wiedz, że istnieje i zapomnij. Wszystko poniżej zapisane jest w JSX.
JSX jest zwykłym JavaScriptem i obrazki importujemy do niego jako obiekty, czyli w nawiasach klamrowych. Jest to funkcja wyświetlająca rezultat React.CreateElement - stąd domyślny parametr children jako props. JavaScript umieszczamy w nim w wąsach.
JSX może mieć tylko jedno dziecko, jako wrappera, który nie dodaje, żadnych znaczników do kodu można użyć React fragment:
<React.Fragment>
// starsza wersja
</React.Fragment>
<>
// młodsza, skrócona
</>
Istnieją dwa rodzaje komponentów:
- klasowe (stanowe) klasa ES6, która:
- jest rozszerzeniem React.Component
- może przechowywać stan (stany, właściwości mające ulec zmianie) oraz zmieniać go metodą setState() umieszczoną wewnątrz funkcji - ta metoda zmienia stan i tylko ona dokonuje ponownego renderowania zawartości komponentu
- posiada cykl życia (lifecycle, np. componentDidMount)
- W zasadzie dane spływają z góry drzewa komponentów, ale w komponencie podrzędnym można zmienić stan w komponencie nadrzędnym za pośrednictwem funkcji uruchamianej przez propsy z komponentu podrzędnego
- domyślne wartości propsów można przekazać w wartości statycznej klasie static defaultProps{ }
- przy zmianie stanu (this.state), może generować jakiś element na nowo z nowymi propsami, dostępne są this.props; zmiany stanu można przekazać do innych komponentów za pomocą obiektu props
- posiada logikę, taki komponent wykonuje jakieś działanie
- ciało funkcji zawarte jest w metodzie render(), która musi coś zwracać, czyli musi mieć return
- jeżeli jest komponentem podrzędnym zawsze odbiera propsy, nie trzeba ich deklarować
- funkcyjne (do wersji 16.8: bezstanowe) jest to prosta funkcja JS, więc nie można użyć setState(); najczęściej funkcja strzałkowa. Są prostsze, łatwiejsze w edycji i czytaniu. Ich stosowanie sprzyja oddzieleniu kontenera od komponentów prezentacji. Przyjmują dane i wyświetlają je: UI. Używają propsów, ale tylko od odczytu. Cykl życia nie ma zastosowania. Jeżeli jedynym zadaniem komponentu jest wyświetlenie czegoś (renderowanie) powinien to być komponent funkcyjny. Klasyczny przykład to button. W wersji 16.8 (6 lutego 2019 - ReactJS "Introducing Hooks") pojawiły się hooki (działają tylko w komponencie funkcyjnym), które umożliwiają zarządzanie stanami. Spowodowało to poważne ograniczenie stosowania komponentów klasowych, bardziej złożonych i trudniejszych w utrzymaniu.
Struktura aplikacji wygląda następująco: komponenty są zagnieżdżone jeden w drugim w hierarchii "drzewka", z tym że może być wiele instancji danego komponentu w tej hierarchii. Na samej górze są komponenty klasowe zawierające stan, który może być zmieniany przez funkcję w komponencie stanowym za pomocą this.setState({ })
, wartości tych stanów są przekazywane w dół hierarchii przez propsy do komponentów funkcyjnych. Docelowo cały interfejs powinien być zbudowany z komponentów funkcyjnych, których zadaniem jest wyświetlenie treści i elementów interakcyjnych interfejsu. Komponent funkcyjny tylko wyświetla treść, wszystkie zmiany wprowadzone przez użytkownika przekazuje propsami do funkcji w komponencie klasowym, tylko w komponentach klasowych są funkcje, najważniejsze są te zmieniające stan. Domyślnie na szczycie tej hierarchii jest App.js - konwencja taka sama jak index.html. Komponent powinien być klasowy tylko jeśli jest taka konieczność. Powinno być ich jak najmniej i dzięki hookom można w ogóle się ich pozbyć.
Komponenty funkcyjne są zagnieżdżone w komponentach klasowych (lub w innych komponentach funkcyjnych) w postaci tagów (dlatego, żeby nie zaszła pomyłka nazwy zaczynają się od dużej litery), wewnątrz tych tagów przekazujemy propsy (skrót od properties czyli właściwości komponentu). Służą do przekazywania danych pomiędzy komponentami - jak argumenty przekazywane do funkcji. Propsy tworzą atrybuty przekazane z chwilą wywołania komponentu. Domyślne wartości propsów można też przekazać w App.defaultProps = { }
- poza komponentem funkcyjnym. Uwaga: key nie jest przekazywane do propsa. Dla komponentów funkcyjnych są tylko do odczytu, nie podlegają zmianie. W propsach można przekazać też funkcje zawarte w komponentach klasowych.
I tak wygląda podstawowy bieg danych w aplikacji React. Dzięki propsom funkcje zawarte w komponentach klasowych są uruchamiane (także z parametrem) z komponentów funkcyjnych. Funkcje te dzięki metodzie this.setState() modyfikują stany i każda zmiana tych stanów na bieżąco jest uaktualniana i również przez propsy nowa wartość przekazywana jest w dół do komponentów funkcyjnych modyfikując treść. Wszystko to dzieje się z punktu widzenia użytkownika dzieje się natychmiast (bo aplikacja jest załadowana w przeglądarce - chyba, że musi mieć połączenie z bazą danych) i odbywa się bez przeładowania strony, zmieniany jest tylko ten element, który zmieniły propsy.
Należy pamiętać, że kod jest wykonywany asynchronicznie, a uaktualnianie state jest na końcu. Oznacza to, że nawet jeśli kod nadający nową wartość stanu (dajmy na to stateParam) jest przed elementem, który jej używa, to i tak zmiana stanu nastąpi dopiero po wykonaniu wszystkich zadań, co możemy sprawdzić dając console.log(stateParam) na końcu funkcji. Zmiana stanu z setState({ })
jest zawsze ostatnia w kolejce i wszystkie inne zadania zostaną wykonane wcześniej. Czyli w funkcji zawsze dostaniemy poprzedni stateParam (albo inaczej mówiąc stare propsy). Tak działa React i to się nie zmieni, to feature nie bug. Jeżeli potrzebujemy aktualnego propsa możemy użyć funkcyjnego setState, czyli takiego, który przekazuje do wewnątrz nie obiekt, a funkcję. W funkcji tej przekazujemy wszystkie stany (najłatwiej to sprawdzić w console.log) i jest obojętne jakiej nazwy użyjemy, ale przyjęło się używać prevState. Tu w przykładzie użyta jest dodatkowa funkcja, która generuje nową wartość propsa na podstawie wartości dwóch innych - i obie są aktualne. Używa się tego jeżeli zmieniamy poprzedni stan nie znając jego wartości - rodzaj przełącznika. Nie jest potrzebne w funkcjach, które po prostu ustawiają określoną wartość.
functionToGetFreshProps = () => {
this.setState(prevState => ({
stateParam1 : this.functionMakingValue(prevState.stateParam2, prevState.stateParam3)
}));
};
Komponent klasowy
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
Komponent funkcyjny
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
- "React Functional or Class Components: Everything you need to know"
- Cem Eygi "React Functional Components, Props, and JSX – React.js Tutorial for Beginners"
- Eduardo Vedes "How to become a pro with React setState() in 10 minutes"
- Kingsley Silas "Understanding React `setState`"
- "Functional Components vs Class Components"
- "Understanding React Components (Part 1): Working with Functional Components"
- "Functional vs Class Components in React - Everything you need to know"
- Naga Chaitanya Konada: "Creating React Components - different syntaxes"
- Mehul Mohan "React Server Components Explained"
Ze względu na architekturę aplikacji dla bardziej precyzyjnego zarządzania zasięgiem (scope) komponenty dzielimy na dwa rodzaje:
- publiczny - wykorzystywant wielokrotnie w innych komponentach naszej aplikacji, udostępniamy aplikacji
- prywatny - wykorzystywany jest wyłącznie przez jeden komponent nadrzędny
Import i export
Do wymiany danych pomiędzy modułami, który jest niezbędny żeby przetwarzać dane poza zasięgiem (scope) funkcji, używamy importów i exportów. Mamy ich dwa rodzaje:
- CommonJS (CJS) używany kiedyś w Node.
const package = require('module-name')
- ES modules, który jest aktualnym standardem w React
import package from 'module-name'
React lifecycle methods diagram
- Ruth M. Pardee "Passing Data Between React Components"
- Flavio Copes "Introduction to ES Modules"
- Lin Clark "ES modules: A cartoon deep-dive"
- rwieruch: "How to use CSS Modules in React?"
Mamy dwa rodzaje eksportu:
- Named Export wymusza użycie klamry oraz identycznej nazwy w exporcie i imporcie, może być ich więcej niż jednen na moduł
- Default Export może być tylko jeden na moduł i wymaga parametru default. Jeżeli jest tylko jeden export zaimportować można pod inną nazwą
CSS
Inline
<p style={{border: '1px solid #999', fontSize: 20}}>treść</p>
Zamiast tak wpisanych można umieścić zdefiniowany przez const (przed render) obiekt. To daje możliwość zrobienia warunków.
const style_p = {border: '1px solid #999', fontSize: 20}
<p style={warunki ? style_p : inne_style }>treść</p>
Najczęściej style importuje się z arkuszy podając ścieżkę względną. Stylowaniu podlegają także importowane komponenty. Wszystkie są globalne, tzn podlegają bandlowaniu podczas kompilacji.
import '../sciezka/wzgledna/arkusz.css';
Bootstrap, instalacja:
npm install bootstrap
Import, najlepiej w pliku App.js, wtedy wszędzie można używać klas bootstrapowych.
import 'ścieżka/dostępu/bootstrap.min.css';
SASS
npm install node-sass
- Dmitry Nozhenko "9 Ways To Implement CSS in React JS"
- Cristian Vasta "Style Your Application Using Sass with React"
React Router
Instalacja. Dla aplikacji webowych w katalogu aplikacji:
npm install react-router-dom
Dla React Native
npm install react-router-native
Diagnostyka
npm audit
npm audit fix
Komponenty
- BrowserRouter (też BrowserRouter as Router)
- Link i NavLink
- Route
- Switch
- Redirect
Import poszczególnych komponentów wygląda tak:
import { BrowserRouter, NavLink, Route, Switch } from "react-router-dom";
Style CSS. Link i NavLink przypisują automatycznie styl active dla aktywnego elementu. NavLink ma dodatkowe możliwości: można użyć styli wpisanych na poniższe dwa sposoby:
activeClassName="home_selected"
activeStyle={{ backgroundColor: "gray" }}
- React Router
- Artykuły "Server Rendering with React and React Router" | "React Router and Client-Side Routing"
- "APIs in React Tutorial - Recipe App using React Router" [YT 1:33:10]
Moduły
Składnia CommonJS (używane m in przez Node.js)
const users = require('sciezka/nazwa_pliku');
module.exports = () => {/* _ */};
Składnia ES6
import users from 'sciezka/nazwa_pliku';
export default () => {/* _ */};