LEKCJA 16 - REACT NATIVE (p) - komponenty funkcyjne - hooks

LEKCJA 16 - REACT NATIVE (p) - komponenty funkcyjne - hooks
ukryj menu
SPEC
aktualizacja: 2022-02-04 08:55:27
0. Komponenty funkcyjne

film na YouTube pokazujący ćwiczenia 1-9

https://youtu.be/Ag6HntQeP3Akopiuj

opis

Komponenty Reacta można pisać za pomocą klas i od wersji 16.8 za pomocą funkcji
Dzisiaj skupiamy się na komponentach funkcyjnych
Pracujemy w nowej aplikacji create-react-app, zmieniając tylko index.js tak jak w pierwszych czterech zajęciach w tym roku
Każde kolejne ćwiczenie to kolejny plik App01,2,3...
Dla zachowania spójności kodu, posługujemy się funkcjami strzałkowymi, choć nie jest to konieczne


plik index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App01'; // tu podmieniamy

ReactDOM.render(
  <App />,
  document.getElementById('root')
);kopiuj




1. ćw 01: komponenty funkcyjne

zamiast klasy, można napisać funkcję, której jedynym zadaniem jest zwrócić zawartość html

plik App01.js

import Item01 from "./Item01"

const App = () => {

  return (
    <div >
      <Item01 />
      <Item01 />
      <Item01 />
    </div>
  );
}

export default App;kopiuj

plik Item01.js


const Item = () => {

    const show = () => {
        alert("test")
    }

    return (
        <div>
            <h1>Item01</h1>
            <div>
                <button onClick={show}>show alert</button>
            </div>
        </div>
    )
}

export default Itemkopiuj


jak widać funkcje wywołujemy bez słowa kluczowego this

2. ćw 02: props-y w komponencie funkcyjnym

w App02 propsy dodajemy jak zwykle:

<Item02 title="ReactJS" info="easy" />
<Item02 title="ExpressJS" info="lightweight" />
<Item02 title="NextJS" info="serverside" />kopiuj

a odczytujemy w Item02 podając je jako argument funkcji

const Item = (props) => {

    return (
        <div>
            <h1>{props.title}</h1>
            <h3>{props.info}</h3>           
        </div>
    )
}

export default Itemkopiuj

lub destrukturyzując obiekt props od razu w funkcji komponentu

const Item = ({title,info}) => {

    return (
        <div>
            <h1>{title}</h1>
            <h3>{info}</h3>           
        </div>
    )
}

export default Itemkopiuj

lub destrukturyzując jeszcze inaczej


const Item = (props) => {
    const {title, info} = props
    return (
        <div>
            <h1>{title}</h1>
            <h3>{info}</h3>           
        </div>
    )
}

export default Itemkopiuj




zadanie: dokończ działanie ćwiczenia jak na filmie

3. ćw 03: useState() - visiblity

wobec braku klasy, nie można użyć dotychczasowego sposobu przechowywania stanu komponentu
nie ma obiektu state i funkcji setState
zamiast nich są tzw hooki
Hooki to funkcje których nazwa zgodnie z konwencją zaczyna się od use
W większości przypadków wystarczy nam znajomość dwóch podstawowych useState() oraz useEffect()

useState() pozwala nam używać stanu w funkcji (odpowiednik this.state i this.setState) i wygląda ogólne jak poniżej

const [state, setState] = useState(default_value)kopiuj

hook ten zwraca tablicę z wartością stanu i funkcją za pomocą której będziemy aktualizować wartość

plik App03.js


import { useState } from 'react';
import Item03 from "./Item03"

const App = () => {


  // visible - wartość do zmieniania za pomocą useState()
  // setVisible - funkcja dokonująca zmiany
  // true - początkowa wartość visible

  const [visible, setVisible] = useState(true)

  const setVis = (val) => {
    setVisible(val)
  }

  return (
    <div >
      <button onClick={() => setVis(true)}>visible</button>
      <button onClick={() => setVis(false)}>invisible</button>
      <Item03 visible={visible} />

    </div>
  );
}

export default App;kopiuj




plik Item03.js

tu wykorzystujemy propa do pokazywania/ukrywania itema

const Item= (props) => {
    return (
        <div style={{ visibility: props.visible === true ? "visible" : "hidden" }}>
            <h1>ITEM 03</h1>
        </div>
    )
}

export default Itemkopiuj


4. ćw 04: useState() - spread operator - działania na tablicach

ćwiczenie zakłada wykonywanie wielu działań na tej samej tablicy, która jest wyjściowym state w tej aplikacji
drugi cel to wykorzystanie spread operatora ... do kopiowania zawartości tablicy
zamieszczam pierwsze trzy funkcje, reszta jest zadaniem
całość napisana jest tak aby był jeden schemat pracy z wszystkimi funkcjami

dokumentacja spread operatora

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax

plik App04.js

import { useState } from 'react';
import Item04 from "./Item04"

const App = () => {

  //wyjsciowa tablica

  const INIT_LIST = [];

  // state

  const [list, setList] = useState(INIT_LIST)

  const addToEnd = () => {
    setList(() => {
      return [...list, Math.floor(Math.random() * 1000)] // nowa tablica z dodanym elementem na końcu
    })
  }

  const addToStart = () => {
    setList(() => {
      return [Math.floor(Math.random() * 1000), ...list] // nowa tablica z dodanym elementem na początku
    })
  }

  const delFirst = () => {
    setList(() => {
      return [...list.filter((item, i) => i !== 0)] // nowa przefiltrowana tablica
    })
  }

 
  return (
    <div >
      <h1>04: useState() - spread operator - działania na tablicach</h1>
      <button onClick={() => addToEnd()}>dodaj na koniec</button>
      <button onClick={() => addToStart()}>dodaj na początek</button>
      <button onClick={() => addValue(5)}>dodaj 5</button>
      <button onClick={() => delFirst()}>usuń pierwszy</button>
      <button onClick={() => delLast()}>usuń ostatni</button>
      <button onClick={() => delAll()}>usuń wszystkie</button>
      <br />
      {
        list.map((element, i) => {
          return <Item04 key={i} val={element} />
        })

      }

    </div>
  );
}

export default App;kopiuj


plik Item04.js

item służy tylko do wyświetlenia przekazanych danych

const Item = (props) => {

    return (
        <div>
            <h1>ITEM</h1>
            <h4>{props.val}</h4>
        </div>
    )
}

export default Itemkopiuj



5. ćw 05: wywołanie funkcji w rodzicu

idea taka sama jak w zawsze: należy wywołać w dziecku Item05 funkcję napisaną w rodzicu App05
funkcja ma usuwać dane z tablicy rodzica

plik App05.js

import { useState } from 'react';
import Item05 from "./Item05"


const App = () => {

  //wyjsciowa tablica

  const INIT_LIST = [];

  //state

  const [list, setList] = useState(INIT_LIST)

  const addToEnd = () => {
   // jak porzednio
  }

  const addToStart = () => {
    // jak poprzednio
  }


  const delAll = () => {
   // jak poprzednio
  }


  const delSelected = (val) => {
    let x = val
    setList(() => {
      return [...list.filter((item, i) => i !== x)]
    })
  }


  return (
    <div >
      <button onClick={() => addToEnd()}>dodaj na koniec</button>
      <button onClick={() => addToStart()}>dodaj na początek</button>
      <button onClick={() => delAll()}>usuń wszystkie</button>
      <br />
      {
        list.map((element, i) => {
          return <Item05 key={i} val={element} index={i} delSelected={delSelected} />
        })

      }

    </div>
  );
}

export default App;
kopiuj

plik Item05.js


w itemie mamy funkcję wywołującą funkcję rodzica

const Item= (props) => {

   // wywołanie funkcji rodzica

    const deleteItem = () => {
        props.delSelected(props.index)

    }

    return (
        <div>
            <h1>ITEM</h1>
            <h4>{props.val}</h4>
            <div>
                <button onClick={deleteItem}>delete item</button>
            </div>
        </div>
    )
}

export default Itemkopiuj






6. ćw 06: useState() - obiekt w state

cel ćwiczenia to praca ze state zawierającym obiekt a nie wartość
w obiekcie mamy różne dane: wartość, tablicę i obiekt
chcemy zmienić poszczególne dane, nie ruszając pozostałych
znów wykorzystamy spread operator do kopiowania zawartości obiektu

plik App06.js


import { useState } from 'react';
import Item06 from "./Item06"

const App = () => {

  // state

  const INIT_OBJ = {
    value: 1000,
    array: [1, 2, 3],
    object: { a: 1, b: 2 }
  }

  const [state, updateState] = useState(INIT_OBJ);



  const update = (val) => {

    switch (val) {
      case 0:
        updateState({
          ...state,
          value: 2000
        })
        break;
      case 1:
        updateState({
          ...state,
          array: [4, 5, 6]
        })
        break;
      case 2:
        updateState({
          ...state,
          object: { c: 3, d: 4 }
        })
        break;
      default:
        break;
    }
  }

  return (
    <div >
      <button onClick={() => update(0)}>change value</button>
      <button onClick={() => update(1)}>change array</button>
      <button onClick={() => update(2)}>change object</button>
      <br />
      <Item06 data={state.value} />
      <Item06 data={state.array} />
      <Item06 data={state.object} />
    </div>
  );
}

export default App;kopiuj






plik Item06.js

Służy tylko do wyświetlenia przekazanych z rodzica danych


const Item = (props) => {
    return (
        <div>
            <h1>{JSON.stringify(props.data)}</h1>
        </div>
    )
}

export default Itemkopiuj




7. zadanie 07: dialog - wywołanie funkcji w rodzicu

tym razem nie ma kodu, zadanie 07 jest widoczne na filmie
założenia:
- mamy pliki App07, Item07, Dialog
- można dodawać na początek i koniec tablicy
- przed usunięciem elementu tablicy, pojawia się overlay w postaci własnego komponentu z butonami OK, Cancel



8. useEffect() - porównanie pracy komponentu klasowego i funkcyjnego

Jak użyć componentDidMount(), albo componentDidUpdate() w komponentach funkcyjnych?
Potrzebny jest hook useEffect(), który pozwala na przeprowadzanie efektów ubocznych (odpowiednik componentDidMount(), componentDidUpdate(), componentWillUnmount...)

ogólna składnia


useEffect(
  () => {

    /* Co się ma wykonać */

    return () => {

      /* Porządkowanie po wykonanym efekcie
       * czyszczenie timeoutów intervali, subskrypcji itd.
       * na przykład clearInterval(), clearTimeout()...
       */

    };
  },
  [
    /* tablica wartości których zmiana spowoduje wykonanie efektu */
  ]
);kopiuj



Poniżej dokładna analiza na przykładzie komponentu klasowego i jego odpowiednika w postaci komponentu funkcyjnego

a) komponent klasowy w całości

plik App08.js


import React, { Component } from "react";
import './App.css';

class App extends Component {
  constructor() {
    super();

    this.state = {
      count: 0,
      timer: 0,
    };

  }

  startInterval() {
    this.setState({
      timer: this.state.count,
    });
    this.interval = setInterval(() => {
      this.setState({
        timer: this.state.timer - 1,
      });
    }, 1000);
  }

  componentDidMount() {
    this.startInterval();
  }

  componentDidUpdate(_, prevState) {
    if (this.state.timer <= 0) {
      clearInterval(this.interval);
    }

    if (prevState.count !== this.state.count) {
      clearInterval(this.interval);
      this.startInterval();
    }
  }

  componentWillUnmount() {
    clearInterval(this.interval);
  }

  increment = () => {
    this.setState({
      count: this.state.count + 1,
    });
  }

  render() {
    return (
      <div>
        <button className="btn" onClick={this.increment}>START</button>
        <h3>Czas: {this.state.timer}</h3>
        <h3>Count: {this.state.count}</h3>

      </div>
    );
  }
}

kopiuj
export default App;kopiuj

Powyższy komponent ma następujące działanie:
Z każdym kliknięciem zwiększamy wartość count
Przy każdej zmianie count, zmienna timer otrzymuje taką samą wartość jak count, po czym zaczyna się odliczanie
Uwaga! przy starcie nowego odliczania należy usunąć poprzednie przy pomocy clearInterval


b) Identyczne działanie przy pomocy funkcji otrzymamy w ten sposób - uwaga na komentarze

plik App09.js

import React, { useState, useEffect } from "react";


const App = () => {

  const [count, setCount] = useState(0);
  const [timer, setTimer] = useState(0);

  const increment = () => {
    setCount(count + 1);
  };

  useEffect(() => {

    setTimer(count); // Ustawienie wartości początkowej timera

    let secondsPassed = 0;

    // Startujemy interwał
    const interval = setInterval(() => {

      // Sprawdzamy czy odliczanie się nie zakończyło, jeśli tak to usuwamy interwał

      if (count <= secondsPassed) {
        clearInterval(interval);
        return;
      }

      secondsPassed++; // Zwiększenie liczby upłyniętych sekund
      setTimer(count - secondsPassed); // Ustawienie wartości timera
    }, 1000);

    // Zanim wystartuje kolejny interwał, musimy usunąć poprzedni

    return () => {
      clearInterval(interval);
    };
  }, [count]); // Podając count do listy zależności zaznaczamy, że efekt ma wykonać się przy każdej zmianie wartości

  return (
    <div>
      <button onClick={increment}>START</button>
      <h3>Czas: {timer}</h3>
      <h3>Count: {count}</h3>
    </div>
  );
};

export default App;kopiuj





c) inna wersja powyższego przykładu

plik App10.js

import React, { useState, useEffect } from "react";

const App = () => {
  const [count, setCount] = useState(0);
  const [timer, setTimer] = useState(0);

  const increment = () => {
    setCount(count + 1);
  };

  useEffect(() => {
    setTimer(count);
  }, [count]);

  useEffect(() => {
    if (timer <= 0) return;
    const timeout = setTimeout(() => setTimer(timer - 1), 1000);

    return () => {
      clearTimeout(timeout);
    };
  }, [timer]);
  return (
    <div>
      <button onClick={increment}>START</button>
      <h3>Czas: {timer}</h3>
      <h3>Count: {count}</h3>

    </div>
  );
};

export default App;kopiuj



d) informacje dodatkowe

Jeśli chcemy, aby efekt wykonywał się przy jakiejkolwiek zmianie (tak jak componentDidUpdate()),
to nie podajemy drugiego argumentu

useEffect(() => {
  ...
kopiuj
});kopiuj

Jeśli chcemy aby efekt wykonał się tylko raz w momencie zamontowania komponentu (tak jak componentDidMount()), jako drugi argument podajemy pustą tablicę

useEffect(() => {
  ...
}, []);kopiuj