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
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
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
<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
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
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()
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
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
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
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
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
// 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
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
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
a) komponent klasowy w całości
plik App08.js
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ść countPrzy 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
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
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
useEffect(() => {
...
}, []);kopiuj