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ć!

  1. Komponenty React muszą zaczynać się wielką literą - w innym wypadku renderujemy HTML!
     
  2. Każdy komponent powinien zwrócić JSX (lub null).
     
  3. 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?

  1. Struktura DOM i trudność w odwołaniu się do elementu; zmniejszenie możliwości ponownego wykorzystania komponentu
     
  2. Zaprzeczenie idei przepływu danych w React
     
  3. 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