React w praktyce
Bartosz Szczeciński
26.05.2018
@btmpl
medium.com/@baphemot
git clone https://github.com/BTMPL/nodeschool-chat
cd nodeschool-chat
npm install
Renderowanie do DOM
import React from 'react';
import ReactDOM from 'react-dom';
ReactDOM.render(
"Hello World",
document.getElementById('root')
);
Renderowanie do DOM
import React from 'react';
import ReactDOM from 'react-dom';
const App = () => {
return "Hello World";
}
ReactDOM.render(<App />, document.getElementById('root'));
Renderowanie HTML
import React from 'react';
import ReactDOM from 'react-dom';
const App = () => {
return (
<div>
Hello World
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
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/
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>
);
}
Warto pamiętać!
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ą!
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' }
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')
);
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.
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.
Przekaż "od rodzica" do komponentu wartość atrybutu placeholder dla komponentu na imię użytkownika.
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')
);
w HTML
<form onsubmit="alert('Dziekujemy!')">
<input type="text" />
<button>Zaloguj</button>
</form>
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!
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!
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!
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ł?
Czemu?
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>
)
}
}
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
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
Co jeżeli potrzebujemy dostęp do danych
w "czasie rzeczywistym"?
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
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
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
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!
}} />
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!
}} />
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'
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
const messages = [
'Hej!',
'Witaj!',
'Jak się masz?'
];
const List = (props) => {
return (
<div>
{props.messages.join(', ')}
</div>
)
}
ReactDOM.render(
<List messages={messages} />,
document.getElementById('root')
);
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
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.
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
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'));
Ź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'));
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();
}
// ....
}
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>
)
}
}
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>
)
}
}
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
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 }
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>
)
}
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
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!
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