React w praktyce
Bartosz Szczeciński
26.05.2018
@btmpl
medium.com/@baphemot
Przygotowanie
git clone https://github.com/BTMPL/nodeschool-chat
cd nodeschool-chat
npm install
Co będziemy robić?
Myślenie w komponentach
Punk startowy
Renderowanie do DOM
import React from 'react';
import ReactDOM from 'react-dom';
ReactDOM.render(
"Hello World",
document.getElementById('root')
);
Punk startowy
Renderowanie do DOM
import React from 'react';
import ReactDOM from 'react-dom';
const App = () => {
return "Hello World";
}
ReactDOM.render(<App />, document.getElementById('root'));
Punk startowy
Renderowanie HTML
import React from 'react';
import ReactDOM from 'react-dom';
const App = () => {
return (
<div>
Hello World
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
Punk startowy
Renderowanie HTML
import React from 'react';
import ReactDOM from 'react-dom';
var App = function App() {
return React.createElement(
"div",
null,
"Hello World"
);
};
ReactDOM.render(<App />, document.getElementById('root'));
https://babeljs.io/repl/
Punk startowy
Renderowanie HTML
var App = function App() {
return React.createElement(
"div",
null,
React.createElement(
"b",
null,
"Hello"
),
" World"
);
};
https://babeljs.io/repl/
const App = () => {
return (
<div>
<b>Hello</b> World
</div>
);
}
JSX
Warto pamiętać!
- Komponenty React muszą zaczynać się wielką literą - w innym wypadku renderujemy HTML!
- Każdy komponent powinien zwrócić JSX (lub null).
- Komponent może zwrócić maksymalnie jeden element nadrzędny. React 16 wprowadził możliwość zwracania wielu elementów bez rodzica HTML używając React.Fragment
Ćwiczenie
Utwórz swój własny komponent i spraw, by wygenerował on powyższy widok.
Komponent powinien być funkcją, która zwróci kod JSX. Pamiętaj, że komponent powinien zaczynać się wielką literą!
Przekazywanie danych do komponentu
import React from 'react';
import ReactDOM from 'react-dom';
const Login = (props) => {
console.log(props);
return (
<form>
<input type="text" />
<button>Zaloguj</button>
</form>
)
}
ReactDOM.render(
<Login label="Zaloguj" />,
document.getElementById('root')
);
{ label: 'Zaloguj' }
Przekazywanie danych do komponentu
import React from 'react';
import ReactDOM from 'react-dom';
const Login = (props) => {
return (
<form>
<input type="text" />
<button>{props.label}</button>
</form>
)
}
ReactDOM.render(
<Login label="Zaloguj" />,
document.getElementById('root')
);
Przekazywanie danych do komponentu
Tekst zamieszczony pomiędzy znacznikami { i } w JSX musi być wyrażeniem JavaScript (musi zwracać wartość). Oznacza on niejako "przejdź w tryb JS".
Nie możesz używać w nim bezpośrednio operatorów typu if, else etc. - jeżeli potrzebujesz zwrócić kod warunkowo, możesz użyć operatora potrójnego (ternary operator).
W dużej większości przypadków nazwy parametrów odzwierciedlające atrybuty HTML używają notacji camelCase.
Przekazywanie danych do komponentu
Niektóre nazwy atrybutów HTML nazywają się inaczej
w React, np.:
// HTML
<div class="test"></div>
<label for="imie"></label>
// React
<div className="test"></div>
<label htmlFor="imie"></label>
Wynika to głównie ze wzg. na zapewnienie popranego działania operatora destrukturującego obiekty.
Ćwiczenie
Przekaż "od rodzica" do komponentu wartość atrybutu placeholder dla komponentu na imię użytkownika.
Zdarzenia
import React from 'react';
import ReactDOM from 'react-dom';
const Login = () => {
return (
<form>
<input type="text" />
<button>Zaloguj</button>
</form>
)
}
ReactDOM.render(
<Login />,
document.getElementById('root')
);
Zdarzenia
w HTML
<form onsubmit="alert('Dziekujemy!')">
<input type="text" />
<button>Zaloguj</button>
</form>
Zdarzenia
const Login = () => {
return (
<form onSubmit={alert('Dziękujemy!')}>
<input type="text" />
<button>Zaloguj</button>
</form>
)
}
Pamiętasz, że kod w {} powinien być wyrażeniem? Co stanie się jeżeli wywołamy poniższy kod?
Kod zostanie wywołany w momencie wyrenderowania komponentu a jego zwrócona wartość zostanie przekazana do handlera onSubmit!
Zdarzenia
const Login = () => {
return (
<form onSubmit={(e) => {
e.preventDefault();
}}>
<input type="text" />
<button>Zaloguj</button>
</form>
)
}
Powstrzymaj formularz przed wysłaniem
i przeładowaniem strony - możemy użyć do tego zwykłego JS!
Ćwiczenie
Po przesłaniu formularza (klikając na guzik? klikając enter w polu formularza?) wyświetl komunikat używając alert()
Pamiętaj o poprawnej notacji parametrów: onZdarzenie
Pamiętaj, że do parametru musisz przekazać referencję do funkcji, nie sam jej kod!
Zdarzenia
const Login = () => {
return (
<form onSubmit={(e) => {
e.preventDefault();
alert(document.querySelector('input').value);
}}>
<input type="text" />
<button>Zaloguj</button>
</form>
)
}
Ok, ale jak dostać się do wartości pola?
Ale czy jest to dobry pomysł?
Reactowy sposób
Czemu?
- Struktura DOM i trudność w odwołaniu się do elementu; zmniejszenie możliwości ponownego wykorzystania komponentu
- Zaprzeczenie idei przepływu danych w React
- A co jeżeli w ogóle nie ma DOM? React Native, React XP
Reactowy sposób
Komponenty Stanowe
class Login extends React.Component {
render() {
return (
<form onSubmit={(e) => {
e.preventDefault();
alert(document.querySelector('input').value);
}}>
<input type="text" />
<button>Zaloguj</button>
</form>
)
}
}
Komponenty Stanowe - referencje
class Login extends React.Component {
render() {
return (
<form onSubmit={(e) => {
e.preventDefault();
alert(this.input.value);
}}>
<input type="text" ref={ref => this.input = ref} />
<button>Zaloguj</button>
</form>
)
}
}
Formularze niekontrolowane
Komponenty Stanowe - referencje
class Login extends React.Component {
render() {
return (
<form onSubmit={(e) => {
e.preventDefault();
alert(this.input.value);
}}>
<input type="text" ref={ref => this.input = ref} />
<button>Zaloguj</button>
</form>
)
}
}
Formularza kontrolowane i stan
Komponenty Stanowe - stan
Co jeżeli potrzebujemy dostęp do danych
w "czasie rzeczywistym"?
Komponenty Stanowe - stan
class Login extends React.Component {
state = {
user: 'Bartek'
}
render() {
return (
<form onSubmit={(e) => {
e.preventDefault();
}}>
<input type="text" value={this.state.user} />
<button>Zaloguj</button>
</form>
)
}
}
Formularza kontrolowane i stan
Problem: nie można zmienić wartości w polu
Komponenty Stanowe - stan
render() {
return (
<form onSubmit={(e) => {
e.preventDefault();
}}>
<input type="text"
value={this.state.user}
onChange={e => {
this.state.user = e.target.value
}}
/>
<button>Zaloguj</button>
</form>
)
}
Formularza kontrolowane i stan
Niestety, wciąż nie można zmienić wartości
Komponenty Stanowe - stan
render() {
return (
<form onSubmit={(e) => {
e.preventDefault();
}}>
<input type="text" value={this.state.user} onChange={e => {
this.setState({
user: e.target.value
})
}} />
<button>Zaloguj</button>
</form>
)
}
this.setState
Komponenty Stanowe - stan
this.setState - ważne informacje
this.setState jest jedynym sposobem zmiany stanu komponentu innym niż jego początkowe zdefiniowanie
<input type="text" value={this.state.user} onChange={e => {
this.state.user = e.target.value; // ŹLE!
this.state = { user: e.target.value }; // ŹLE!
this.setState({
user: e.target.value
}) // DOBRZE!
}} />
Komponenty Stanowe - stan
this.setState - ważne informacje
this.setState powinno być wywołane z obiektem oczekiwanych zmian - jeżeli nie chcesz czegoś zmieniać, nie zawieraj w obiekcie!
// zakładając, że
state = {
user: 'Bartek',
message: 'Hello World!'
};
<input type="text" value={this.state.user} onChange={e => {
this.setState({
user: e.target.value
}); // zmieniam this.state.user, this.state.message bez zmian!
}} />
Komponenty Stanowe - stan
this.setState - ważne informacje
this.setState jest funkcją asynchroniczną, zmiany stanu nie są natychmiastowe ze wzg. na optymalizację aplikacji
// zakładając, że
state = {
user: 'Bartek',
};
this.setState({
user: 'Marcin'
});
alert(this.state.user); // 'Bartek'
Ćwiczenie
Używając formularzy niekontrolowanych lub kontrolowanych wyświetl imię (używając alert)
po kliknięciu "Zaloguj".
Jeżeli nic nie wpisałeś w pole, nie pokazuj żadnego alertu!
Formularze niekontrolowane: parametr ref przyjmuje funkcję, która wywołana jest z referencją na element DOM
Formularze kontrolowane: Twoja klasa powinna dziedziczyć po klasie React.Component, pamiętaj, że nazwy this.state
i this.setState są narzucone przez React. Kod JSX zwróć z funkcji nazwanej render
Komponenty Stanowe - cykl życia
Renderowanie listy komponentów
const messages = [
'Hej!',
'Witaj!',
'Jak się masz?'
];
const List = (props) => {
return (
<div>
{props.messages.join(', ')}
</div>
)
}
ReactDOM.render(
<List messages={messages} />,
document.getElementById('root')
);
Renderowanie listy komponentów
const List = (props) => {
return (
<div>
{props.messages.map(message => {
return <div>{message}</div>
})}
</div>
)
}
https://developer.mozilla.org/pl/docs/Web/JavaScript/Referencje/Obiekty/Array/map
Renderowanie listy komponentów
const List = (props) => {
return (
<div>
{props.messages.map(message => {
return <div key={message}>{message}</div>
})}
</div>
)
}
React używa specjalnego parametru o nazwie "key" do określenia, czy komponent należy zaktualizować czy utworzyć na nowo.
key powinien być unikalnym (tylko w danej pętli!) ciągiem tekstowym. Indeks pętli powinien być używany
w ostateczności.
Ćwiczenie
Założywszy istnienie następującej tabeli danych:
Wygeneruj poniższy układ:
const messages = [
{ id: 1, author: 'Bartek', message: 'Hej!' },
{ id: 2, author: 'Bartek', message: 'Jak się masz?' },
];
Utwórz kopię komponentu Login i utwórz nowy komponent! Login jeszcze się przyda!
Tablicę messages przekaż jako parametr do komponentu, do wygenerowania układu użyj na niej metody .map
Ćwiczenie
Utwórz nowy komponent (ChatUI?) jak na poniższym przykładzie i wyrenderuj go "pod" komponentem listy (nie w niej!)
A może przerobić komponent Login tak, by przyjmował jako parametr tekst do wyświetlenia na guziku?
const List = (props) => {
return (
<div>
{props.messages.map(message => {
return <div><b>{message.author}</b>: {message.message}</div>
})}
</div>
)
}
class ChatUI extends React.Component {
state = { text: '' }
handleSubmit = (e) => e.preventDefault();
render() {
return (
<form onSubmit={this.handleSubmit}>
<input type="text" value={this.state.text} onChange={e => {
this.setState({ text: e.target.value })
}} />
<button>Napisz</button>
</form>
)
}
}
ReactDOM.render(<div>
<List messages={messages} />
<ChatUI />
</div>, document.getElementById('root'));
Przekazywanie danych pomiędzy rodzeństwem oraz dzieckiem i rodzicem
Źródło
Cel
Właściciel
class Chat extends React.Component {
state = { messages }
render() {
return (
<div>
<List messages={this.state.messages} />
<ChatUI />
</div>
)
}
}
ReactDOM.render(<Chat />, document.getElementById('root'));
Przekazywanie danych pomiędzy rodzeństwem oraz dzieckiem i rodzicem
Jak przekazać dane z dziecka do rodzica?
Callback!
class Chat extends React.Component {
state = { messages }
render() {
return (
<div>
<List messages={this.state.messages} />
<ChatUI onSubmit={t => alert(t) />
</div>
)
}
}
class ChatUI extends React.Component {
handleSubmit = (e) => {
this.props.onSubmit(this.state.text);
e.preventDefault();
}
// ....
}
Przekazywanie danych
z dziecka do rodzica
Ćwiczenie
Po przesłaniu wiadomości, dodaj ją do listy.
Pamiętaj, że do aktualizacji stanu komponentu używamy this.setState
Aby połączyć tablice użyj niemutujących operacji, np:
const source = [1,2,3];
const copy = source.concat(4);
// lub
const copy = [
...source,
4
];
class Chat extends React.Component {
state = { messages }
render() {
return (
<div>
<List messages={this.state.messages} />
<ChatUI onSubmit={text => {
this.setState({
messages: [
...this.state.messages,
{
id: this.state.messages.length,
author: 'Ja',
message: text
}
]
});
}} />
</div>
)
}
}
Przekazywanie danych
z dziecka do rodzica
Ćwiczenie
Poproś użytkownika o podanie imienia, a kiedy to zrobi pokaż czat. Kiedy doda wiadomość, użyj jego imienia.
Dodaj pole username do stanu komponentu rodzica
i w zależności od tego, czy jest undefiend czy też podane wyświetlaj Login lub listę chatu i pole na wiadomość.
Komponent Login musi przekazać imię do rodzica, dokładnie tak samo jak komponent na podawanie treści wiadomości.
class Chat extends React.Component {
state = {
messages,
username: undefined
}
render() {
if (!this.state.myName) {
return <Login onSubmit={name => this.setState({
username: name
})} />
}
return (
<div>Chat oraz jego UI</div>
)
}
}
Renderowanie warunkowe
Stylowanie komponentów
const List = (props) => {
return (
<div>
{props.messages.map(message => {
return (
<div style={{
borderBottom: '1px solid #d3d3d3',
padding: '5px 0'
}}>
<b style={{ color: 'blue' }}>
{message.author}
</b>: {message.message}
</div>
);
})}
</div>
)
}
style jest obiektem, który zawiera deklaracje CSS
z użyciem notacji camelCase
Stylowanie komponentów
const List = (props) => {
return (
<div>
{props.messages.map(message => {
return (
<div className="message">
<b>{message.author}</b>: {message.message}
</div>
);
})}
</div>
)
}
import "style.css";
// style.css
.message {
border-bottom: 1px solid #d3d3d3;
padding: 10px 0;
}
.message b { color: blue }
Stylowanie komponentów
import styled from 'styled-components';
const Message = styled.div`
border: 1px solid #d3d3d3;
padding: 5px 0;
b { color: blue }
`;
const List = (props) => {
return (
<div>
{props.messages.map(message => {
return (
<Message>
<b>{message.author}</b>: {message.message}
</Message>
);
})}
</div>
)
}
Ćwiczenie
Używając dowolnej metody zmień wygląd aplikacji wg. własnego upodobania.
Jeżeli chcesz użyć styled-components zainstaluj je używając npm install styled-components - jeżeli po tej komendzie aplikacja przestanie działać, wywołaj ponownie npm install
Komunikacja z innymi uczestnikami!
W pliku utils/api.js znajduje się prekonfigurowane API, pozwalające na połączenie się
z serwerem websocket. Możesz wykorzystać je do połączenia się z innymi uczestnikami!
1. Pamiętaj o dodaniu biblioteki npm install socket.io
2. dołącz api do aplikacji - jest to domyślny eksport modułu
3. wywołaj metodę api.open() przy starcie aplikacji (może w lifecycle?)
4. do wysłania wiadomości użyj api.send(nick, wiadomosc)
5. wiadomości z serwera przekazywane są do callbacku metody api.listen(callback)
api.listen((msg) => {
console.log(msg);
});
Jeżeli nie masz możliwości skorzystać z api zaimportuj mock (eksport nazwany). Nie używa on socketów, a jedynie lokalne "echo".
Już skończone? W obiekcie msg jest też string z datą - możesz dodać jej wyświetlanie!
Publikowanie aplikacji
W celu zbudowania wersji aplikacji, którą możesz umieścić na serwerze wywołaj komendę
npm run build
Po tej operacji w katalogu build znajdą się wszystkie pliki potrzebne do uruchomienia Twojej aplikacji.
Jeżeli chcesz łatwo spróbować zdeployować aplikację on-line, zainstaluj narzędzie surge:
npm install surge --global
a następnie przejdź do katalogu build i wywołaj surge
React w praktyce
By btmpl
React w praktyce
- 1,067