03:15 β Π’Π΅ΠΎΡΠΈΡ
Π Π΅Π°ΠΊΡ - ΡΡΠΎ Π±ΠΈΠ±Π»ΠΈΠΎΡΠ΅ΠΊΠ° Π΄Π»Ρ ΡΠΎΠ·Π΄Π°Π½ΠΈΡ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»ΡΡΠΊΠΈΡ ΠΈΠ½ΡΠ΅ΡΡΠ΅ΠΉΡΠΎΠ². Π’ΠΎ Π΅ΡΡΡ ΡΡΠΎ ΠΎΠ·Π½Π°ΡΠ°Π΅Ρ, ΡΡΠΎ ΠΌΡ ΠΌΠΎΠΆΠ΅ΠΌ ΠΏΠΈΡΠ°ΡΡ ΠΈΠ½ΡΠ΅ΡΡΠ΅ΠΉΡΡ Π½Π° Π½ΡΠΌ Π½Π΅ ΡΠΎΠ»ΡΠΊΠΎ Π΄Π»Ρ Π±ΡΠ°ΡΠ·Π΅ΡΠ°, Π½ΠΎ ΠΈ Π΄Π»Ρ ΠΌΠΎΠ±ΠΈΠ»ΠΎΠΊ, ΡΠ°ΠΊ ΠΊΠ°ΠΊ ΠΎΠ½ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅Ρ ΡΠ²ΠΎΠΉ Π²ΠΈΡΡΡΠ°Π»ΡΠ½ΡΠΉ DOM.
ΠΡ ΠΈΠΌΠ΅Π΅ΠΌ Π΄Π²Π΅ ΠΎΡΠ½ΠΎΠ²Π½ΡΠ΅ ΠΊΠΎΠ½ΡΠ΅ΠΏΡΠΈΠΈ ΡΠ°ΠΉΡΠΎΠ²:
- MPA (Multi Page Application) - ΡΠ°ΠΉΡ ΡΠΎΡΡΠΎΠΈΡ ΠΈΠ· Π½Π΅ΡΠΊΠΎΠ»ΡΠΊΠΈΡ ΡΡΡΠ°Π½ΠΈΡ, ΠΏΡΠΈ ΠΏΠ΅ΡΠ΅Ρ ΠΎΠ΄Π΅ Π½Π° ΠΊΠΎΡΠΎΡΡΠ΅ ΠΌΡ ΠΏΠΎΠ΄Π³ΡΡΠΆΠ°Π΅ΠΌ ΠΈΡ Π΄Π°Π½Π½ΡΠ΅ ΡΠ΅Π»ΠΈΠΊΠΎΠΌ
- SPA (Single Page Application) - Π²Π΅ΡΡ ΡΠ°ΠΉΡ ΡΠ°ΡΠΏΠΎΠ»Π°Π³Π°Π΅ΡΡΡ Π½Π° ΠΎΠ΄Π½ΠΎΠΉ ΡΡΡΠ°Π½ΠΈΡΠ΅ ΠΈ ΠΏΡΠΈ ΠΏΠ΅ΡΠ΅Ρ ΠΎΠ΄Π΅ Π½Π° Π΄ΡΡΠ³ΡΡ ΡΡΡΠ°Π½ΠΈΡΡ Π² Π½ΡΠΌ ΠΌΠ΅Π½ΡΡΡΡΡ ΡΠΎΠ»ΡΠΊΠΎ ΠΊΠΎΠ½ΠΊΡΠ΅ΡΠ½ΡΠ΅ Π΄Π°Π½Π½ΡΠ΅
Π Π΅Π°ΠΊΡ ΠΎΡΠ½ΠΎΠ²Π°Π½ Π½Π° ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ½ΠΎΠΌ ΠΏΠΎΠ΄Ρ
ΠΎΠ΄Π΅, ΠΊΠΎΠ³Π΄Π° ΡΡΡΠ°Π½ΠΈΡΠ° ΡΡΡΠΎΠΈΡΡΡ ΠΈΠ· ΠΎΡΠ΄Π΅Π»ΡΠ½ΡΡ
ΠΊΠΈΡΠΏΠΈΡΠΈΠΊΠΎΠ², ΠΊΠΎΡΠΎΡΡΠ΅ ΠΌΡ ΠΌΠΎΠΆΠ΅ΠΌ ΠΏΠΎΠ²ΡΠΎΡΠ½ΠΎ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ. Π’Π°ΠΊ ΠΆΠ΅ ΠΎΠ½ ΠΏΠΎΠ·Π²ΠΎΠ»ΡΠ΅Ρ ΡΠΎΡΡΠ΅Π΄ΠΎΡΠΎΡΠΈΡΡΡΡ Π½Π° Π½Π°ΠΏΠΈΡΠ°Π½ΠΈΠΈ Π»ΠΎΠ³ΠΈΠΊΠΈ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ Π±Π΅Π· ΡΠ°Π±ΠΎΡΡ ΡΠΎ ΡΠ»ΡΡΠ°ΡΠ΅Π»ΡΠΌΠΈ ΡΠΎΠ±ΡΡΠΈΠΉ, Π½Π΅ΠΏΠΎΡΡΠ΅Π΄ΡΡΠ²Π΅Π½Π½ΠΎΠΉ ΡΠ°Π±ΠΎΡΡ Ρ DOM (querySelector
ΠΈ ΠΏΠΎΠ΄ΠΎΠ±Π½ΡΠ΅ ΠΎΠΏΠ΅ΡΠ°ΡΠΈΠΈ) - ΡΠ΅Π°ΠΊΡ Π±Π΅ΡΡΡ ΡΡΡ ΡΠ°Π±ΠΎΡΡ Π½Π° ΡΠ΅Π±Ρ.
ΠΠΎ Π²ΡΠ΅ΠΌΡ ΡΠ²ΠΎΠ΅ΠΉ ΡΠ°Π±ΠΎΡΡ ΡΠ΅Π°ΠΊΡ ΡΡΡΠΎΠΈΡ Π΄Π²Π° ΡΠ²ΠΎΠΈΡ Π΄Π΅ΡΠ΅Π²Π° ΠΈ ΠΏΠ΅ΡΠ΅Π½ΠΎΡΡΡ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΡ Π½Π° ΠΊΠΎΠ½Π΅ΡΠ½ΠΎΠ΅ ΡΡΠ΅ΡΡΠ΅:
- ΠΠ΅ΡΠ²ΠΎΠ΅ - Π΄Π΅ΡΠ΅Π²ΠΎ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠΎΠ² ΡΠ΅Π°ΠΊΡΠ° - ΠΊΠΎΠ³Π΄Π° Π² Π½ΡΠΌ ΠΏΡΠΎΠΈΡΡ ΠΎΠ΄ΡΡ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΡ, ΠΎΠ½ΠΈ ΠΏΠΎΠΏΠ°Π΄Π°ΡΡ Π½Π° Π²ΡΠΎΡΠΎΠ΅ Π΄Π΅ΡΠ΅Π²ΠΎ, ΠΌΠ΅ΠΆΠ΄Ρ ΠΊΠΎΡΠΎΡΡΠΌΠΈ ΠΏΡΠΎΠΈΡΡ ΠΎΠ΄ΠΈΡ ΡΡΠ°Π²Π½Π΅Π½ΠΈΠ΅
- ΠΡΠΎΡΠΎΠ΅ - Π²ΠΈΡΡΡΠ°Π»ΡΠ½ΠΎΠ΅ Π΄Π΅ΡΠ΅Π²ΠΎ Π΄Π»Ρ ΡΡΠ°Π²Π½Π΅Π½ΠΈΡ
- Π’ΡΠ΅ΡΡΠ΅ - ΡΡΠΎ ΠΊΠΎΠ½Π΅ΡΠ½ΡΠΉ DOM Π±ΡΠ°ΡΠ·Π΅ΡΠ°, Π² ΠΊΠΎΡΠΎΡΠΎΠ΅ ΠΈ Π²Π½ΠΎΡΡΡΡΡ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΡ ΠΏΠΎΡΠ»Π΅ ΡΡΠ°Π²Π½Π΅Π½ΠΈΡ (ΡΠ°Π·Π° ΡΠ΅Π½Π΄Π΅ΡΠΈΠ½Π³Π°, Π·Π° ΠΊΠΎΡΠΎΡΡΡ ΠΎΡΠ²Π΅ΡΠ°Π΅Ρ React DOM ΠΈΠ»ΠΈ React Native)
ΠΠ΅Ρ Π°Π½ΠΈΠ·ΠΌ ΡΠΎΠ³Π»Π°ΡΠΎΠ²Π°Π½ΠΈΡ (Reconciliation) ΠΎΡΡΡΠ΅ΡΡΠ²Π»ΡΠ΅Ρ ΡΡΠ°Π²Π½Π΅Π½ΠΈΠ΅ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠΎΠ² Π΄Π΅ΡΠ΅Π²Π° ΡΠ΅Π°ΠΊΡΠ°. Π’Π°ΠΊ ΠΆΠ΅ ΡΠ΅Π°ΠΊΡ Π΄Π΅Π»ΠΈΡ ΠΎΠΏΠ΅ΡΠ°ΡΠΈΠΈ ΠΏΠΎ ΠΏΡΠΈΠΎΡΠΈΡΠ΅ΡΠ½ΠΎΡΡΠΈ ΠΈ Π±ΠΎΠ»Π΅Π΅ ΠΏΡΠΈΠΎΡΠΈΡΠ΅Π½ΡΠ΅ Π·Π°Π΄Π°ΡΠΈ ΠΎΠ½ Π²ΡΠΏΠΎΠ»Π½ΡΠ΅Ρ Π±ΡΡΡΡΠ΅Π΅.
11:40 β ΠΠ°ΡΠ°Π»ΠΎ ΡΠ°Π·ΡΠ°Π±ΠΎΡΠΊΠΈ. Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΏΡΠΎΠ΅ΠΊΡΠ°
Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΡΠ΅Π°ΠΊΡ-ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ
npx create-react-app .
ΠΠ°ΠΏΡΡΠΊ ΠΊΠΎΠΌΠΏΠΈΠ»ΡΡΠΈΠΈ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ
npm start
ΠΠ°ΡΠ°Π»ΡΠ½ΠΎΠΉ ΡΡΡΠ°Π½ΠΈΡΠ΅ΠΉ, ΠΊΠΎΡΠΎΡΠ°Ρ Π·Π°ΠΏΡΡΠΊΠ°Π΅Ρ Π²Π΅ΡΡ ΡΠ΅Π½Π΄Π΅Ρ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ ΡΠ²Π»ΡΠ΅ΡΡΡ index.js
, ΠΊΠΎΡΠΎΡΡΠΉ ΡΠ΅Π½Π΄Π΅ΡΠΈΡΡΡ Π² root Π΄ΠΈΠ²Π΅ index.html
Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠ°
public > index.html
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
</body>
src > index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<App/>,
document.getElementById('root')
);
16:10 β Π§ΡΠΎ ΡΠ°ΠΊΠΎΠ΅ JSX?
JSX - ΡΡΠΎ ΠΏΡΠ΅ΠΏΡΠΎΡΠ΅ΡΡΠΎΡ, ΠΊΠΎΡΠΎΡΡΠΉ babel ΠΏΠ΅ΡΠ΅Π²ΠΎΠ΄ΠΈΡ Π² ΠΎΠ±ΡΡΠ½ΡΠΉ JS
18:11 β ΠΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ App. Π Π°Π±ΠΎΡΠ° Ρ ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ΠΌ. UseState
ΠΠ°Π΄Π°ΡΠ°: Π½Π°ΠΌ Π½ΡΠΆΠ½ΠΎ ΡΠ΄Π΅Π»Π°ΡΡ ΡΡΡΡΡΠΈΠΊ, ΠΊΠΎΡΠΎΡΡΠΉ ΠΏΡΠΈ Π½Π°ΠΆΠ°ΡΠΈΠΈ Π½Π° ΠΊΠ½ΠΎΠΏΠΊΡ Π±ΡΠ΄Π΅Ρ ΡΠ²Π΅Π»ΠΈΡΠΈΠ²Π°ΡΡ Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅.
Π ΠΏΡΠΈΠΌΠ΅ΡΠ΅ Π½ΠΈΠΆΠ΅ Π Π΅Π°ΠΊΡ Π½Π΅ ΠΏΠΎΠ½ΠΈΠΌΠ°Π΅Ρ, ΡΡΠΎ Π½ΡΠΆΠ½ΠΎ ΠΎΠ±Π½ΠΎΠ²Π»ΡΡΡ Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅ Π² ΠΎΠΏΡΠ΅Π΄Π΅Π»ΡΠ½Π½ΠΎΠΌ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ΅, ΡΠ°ΠΊ ΠΊΠ°ΠΊ ΠΌΡ ΠΏΠΎΠ΄ΠΎΠ±Π½ΠΎΠΉ ΡΡΠ½ΠΊΡΠΈΠ΅ΠΉ ΠΎΡΠΏΡΠ°Π²Π»ΡΠ΅ΠΌ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠ΅ Π·Π½Π°ΡΠ΅Π½ΠΈΡ Π² JS (clg
ΠΏΠΎΠΊΠ°ΠΆΠ΅Ρ, ΡΡΠΎ Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅ ΠΌΠ΅Π½ΡΠ΅ΡΡΡ Π²Π½ΡΡΡΠΈ JS), Π° Π½Π΅ Π² Π΄Π΅ΡΠ΅Π²ΠΎ Π Π΅Π°ΠΊΡΠ°.
ΠΠ°ΠΌ Π½ΡΠΆΠ½ΠΎ Π±ΡΠ΄Π΅Ρ Π²ΡΠ·Π²Π°ΡΡ Π² ΡΠ΅Π°ΠΊΡΠ΅ ΠΏΠ΅ΡΠ΅ΡΠ΅Π½Π΄Π΅Ρ Π½ΡΠΆΠ½ΠΎΠ³ΠΎ Π½Π°ΠΌ Π·Π½Π°ΡΠ΅Π½ΠΈΡ Π½Π° ΡΡΡΠ°Π½ΠΈΡΠ΅.
Π₯ΡΠΊ useState()
Π²ΠΎΠ·Π²ΡΠ°ΡΠ°Π΅Ρ Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅ ΡΒ ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ΠΌ ΠΈΒ ΡΡΠ½ΠΊΡΠΈΡ Π΄Π»Ρ Π΅Π³ΠΎ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΡ.
ΠΠΎΒ Π²ΡΠ΅ΠΌΡ ΠΏΠ΅ΡΠ²ΠΎΠ½Π°ΡΠ°Π»ΡΠ½ΠΎΠ³ΠΎ ΡΠ΅Π½Π΄Π΅ΡΠΈΠ½Π³Π° Π²ΠΎΠ·Π²ΡΠ°ΡΠ°Π΅ΠΌΠΎΠ΅ ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ (state
) ΡΠΎΠ²ΠΏΠ°Π΄Π°Π΅Ρ ΡΠΎΒ Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅ΠΌ, ΠΏΠ΅ΡΠ΅Π΄Π°Π½Π½ΡΠΌ Π²Β ΠΊΠ°ΡΠ΅ΡΡΠ²Π΅ ΠΏΠ΅ΡΠ²ΠΎΠ³ΠΎ Π°ΡΠ³ΡΠΌΠ΅Π½ΡΠ° (initialState
).
Π€ΡΠ½ΠΊΡΠΈΡΒ setState
Β ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΡΡΡ Π΄Π»Ρ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΡ ΡΠΎΡΡΠΎΡΠ½ΠΈΡ. ΠΠ½Π° ΠΏΡΠΈΠ½ΠΈΠΌΠ°Π΅Ρ Π½ΠΎΠ²ΠΎΠ΅ Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅ ΡΠΎΡΡΠΎΡΠ½ΠΈΡ ΠΈΒ ΡΡΠ°Π²ΠΈΡ Π²Β ΠΎΡΠ΅ΡΠ΅Π΄Ρ ΠΏΠΎΠ²ΡΠΎΡΠ½ΡΠΉ ΡΠ΅Π½Π΄Π΅Ρ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ°.
export const State = (): JSX.Element => {
let [counter, setCounter] = useState<number>(0);
return (
<div>
<Link href={'/'}>
<Button buttonType={'ghost'}>ΠΠ±ΡΠ°ΡΠ½ΠΎ</Button>
</Link>
<div className={styles.wrapper}>
<h2 className={styles.title}>Π‘ΡΡΡΡΠΈΠΊ:</h2>
<h1 className={styles.num}>{counter}</h1>
<Button
buttonType={'gray'}
className={styles.reduce}
onClick={() => setCounter(counter--)}
>
Π£ΠΌΠ΅Π½ΡΡΠΈΡΡ
</Button>
<Button
buttonType={'purple'}
className={styles.increase}
onClick={() => setCounter(counter++)}
>
Π£Π²Π΅Π»ΠΈΡΠΈΡΡ
</Button>
</div>
</div>
);
};
ΠΡΠΈ ΡΠ²Π΅Π»ΠΈΡΠ΅Π½ΠΈΠΈ Π·Π½Π°ΡΠ΅Π½ΠΈΡ ΡΡΡΡΡΠΈΠΊΠ°, ΡΠΈΡΠ»ΠΎ ΡΠ²Π΅Π»ΠΈΡΠΈΠ²Π°Π΅ΡΡΡ, Π° ΠΏΡΠΈ ΡΠΌΠ΅Π½ΡΡΠ΅Π½ΠΈΠΈ - ΡΠΌΠ΅Π½ΡΡΠ°Π΅ΡΡΡ.
22:25 β Π£ΠΏΡΠ°Π²Π»ΡΠ΅ΠΌΡΠΉ ΠΈΠ½ΠΏΡΡ
Π£ΠΏΡΠ°Π²Π»ΡΠ΅ΠΌΡΠΉ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ - ΡΡΠΎ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ, Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅ ΠΊΠΎΡΠΎΡΠΎΠ³ΠΎ ΠΌΡ ΠΌΠΎΠΆΠ΅ΠΌ ΠΈΠ·ΠΌΠ΅Π½ΠΈΡΡ, ΠΈΠ·ΠΌΠ΅Π½ΠΈΠ² ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅
export const ControlledInput = (): JSX.Element => {
let [value, setValue] = useState<string>('ΠΠ½Π°ΡΠ΅Π½ΠΈΠ΅');
return (
<div>
<Link href={'/'}>
<Button buttonType={'ghost'}>ΠΠ±ΡΠ°ΡΠ½ΠΎ</Button>
</Link>
<div className={styles.wrapper}>
<h1>{value}</h1>
<Input
value={value}
placeholder={'ΠΠΈΡΠΈ Π² ΠΌΠ΅Π½Ρ:)'}
onChange={e => setValue(e.target.value)}
/>
</div>
</div>
);
};
ΠΡ ΡΠ²ΡΠ·Π°Π»ΠΈ ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ <h1>
Ρ ΡΠ΅ΠΌ, ΡΡΠΎ Π½Π°Ρ
ΠΎΠ΄ΠΈΡΡΡ Π² ΠΈΠ½ΠΏΡΡΠ΅
24:07 β ΠΠ΅ΡΠ²ΡΠΉ ΡΡΠ½ΠΊΡΠΈΠΎΠ½Π°Π»ΡΠ½ΡΠΉ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ
- ΠΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΡ ΠΌΡ ΡΠΎΠ·Π΄Π°ΡΠΌ Π² ΠΏΠ°ΠΏΠΊΠ΅
components
- Π€Π°ΠΉΠ» ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ° ΠΈ ΡΡΠ½ΠΊΡΠΈΡ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ° Π²ΡΠ΅Π³Π΄Π° ΠΈΠΌΠ΅Π½ΡΡΡΡΡ Π² PascalCase
- ΠΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΡ Π²ΡΠ΅Π³Π΄Π° Π΄ΠΎΠ»ΠΆΠ½Ρ Π²ΠΎΠ·Π²ΡΠ°ΡΠ°ΡΡ
JSX.Element
Π ΡΠ΅ΠΏΠ΅ΡΡ Π΄Π°Π½Π½ΡΠΉ ΡΡΠ½ΠΊΡΠΈΠΎΠ½Π°Π»ΡΠ½ΡΠΉ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ <Button>
ΠΌΠΎΠΆΠ½ΠΎ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ Π² Π»ΡΠ±ΠΎΠΌ ΠΌΠ΅ΡΡΠ΅ ΠΏΡΠΎΠ΅ΠΊΡΠ°. ΠΡΠΈΡ
ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠΎΠ² ΠΌΠΎΠΆΠ½ΠΎ Π½Π°Π²ΡΡΠ°Π²Π»ΡΡΡ ΡΠΊΠΎΠ»ΡΠΊΠΎ ΡΠ³ΠΎΠ΄Π½ΠΎ ΠΈ ΠΎΠ½ΠΈ Π±ΡΠ΄ΡΡ Π½Π΅Π·Π°Π²ΠΈΡΠΈΠΌΡΠΌΠΈ Π΄ΡΡΠ³ ΠΎΡ Π΄ΡΡΠ³Π°
import { Button } from '@/components'; // ΠΈΠΌΠΏΠΎΡΡΠΈΡΡΠ΅ΠΌ ΠΊΠ½ΠΎΠΏΠΊΡ
function Home() {
return (
<div className={styles.wrapper}>
<h1>React ΡΡΠ½Π΄Π°ΠΌΠ΅Π½ΡΠ°Π»ΡΠ½ΡΠΉ</h1>
<div className={styles.links}>
<Link href={'fundamentals/state'}>
<Button buttonType={'purple'}>Π‘ΠΎΡΡΠΎΡΠ½ΠΈΡ React</Button>
</Link>
<Link href={'fundamentals/controlledInput'}>
{/* Π΄ΠΎΠ±Π°Π²Π»ΡΠ΅ΠΌ ΠΊΠ½ΠΎΠΏΠΊΡ Π½Π° ΡΡΡΠ°Π½ΠΈΡΡ */}
<Button buttonType={'purple'}>Π£ΠΏΡΠ°Π²Π»ΡΠ΅ΠΌΡΠΉ ΠΈΠ½ΠΏΡΡ</Button>
<Button buttonType={'purple'}>Π£ΠΏΡΠ°Π²Π»ΡΠ΅ΠΌΡΠΉ ΠΈΠ½ΠΏΡΡ</Button>
<Button buttonType={'purple'}>Π£ΠΏΡΠ°Π²Π»ΡΠ΅ΠΌΡΠΉ ΠΈΠ½ΠΏΡΡ</Button>
<Button buttonType={'purple'}>Π£ΠΏΡΠ°Π²Π»ΡΠ΅ΠΌΡΠΉ ΠΈΠ½ΠΏΡΡ</Button>
<Button buttonType={'purple'}>Π£ΠΏΡΠ°Π²Π»ΡΠ΅ΠΌΡΠΉ ΠΈΠ½ΠΏΡΡ</Button>
</Link>
</div>
</div>
);
}
26:40 β ΠΠ΅ΡΠ²ΡΠΉ ΠΊΠ»Π°ΡΡΠΎΠ²ΡΠΉ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ
ΠΠ»Π°ΡΡΠΎΠ²ΡΠΉ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅Ρ Π²Π½ΡΡΡΠΈ ΡΠ΅Π±Ρ ΠΌΠ΅ΡΠΎΠ΄Ρ
import React, { Component } from 'react';
import styles from './ClassCounter.module.scss';
import cn from 'classnames';
import { ClassCounterProps } from './ClassCounter.props';
import { Button } from '@/components';
export class ClassCounter extends Component<any, any> {
constructor(props: ClassCounter) {
super(props);
this.state = {
count: 0,
};
// Π·Π΄Π΅ΡΡ ΠΌΡ Π΄ΠΎΠ»ΠΆΠ½Ρ Π²Π΅ΡΠ½ΡΡΡ ΠΏΠΎΡΠ΅ΡΡΠ½Π½ΡΠΉ ΠΊΠΎΠ½ΡΠ΅ΠΊΡΡ Π²ΡΠΏΠΎΠ»Π½Π΅Π½ΠΈΡ Π΄Π»Ρ ΠΌΠ΅ΡΠΎΠ΄ΠΎΠ²
this.increment = this.increment.bind(this);
this.decrement = this.decrement.bind(this);
}
increment(): void {
this.setState({
count: this.state.count + 1,
});
}
decrement(): void {
this.setState({
count: this.state.count - 1,
});
}
render() {
return (
<div>
<h1>{this.state.count}</h1>
<Button buttonType={'purple'} onClick={this.increment}>
inc
</Button>
<Button buttonType={'gray'} onClick={this.decrement}>
dec
</Button>
</div>
);
}
}
Π ΡΠ°ΠΊ Π²ΡΠ³Π»ΡΠ΄ΠΈΡ ΠΊΠ°ΡΠ½ΡΠ΅Ρ:
Π‘ΠΎΠΊΡΠ°ΡΠ΅Π½ΠΈΠ΅ ΠΏΡΡΠ΅ΠΉ ΠΈΠΌΠΏΠΎΡΡΠΎΠ² Π΄ΠΎ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠΎΠ²
Π’Π°ΠΊ ΠΆΠ΅ Π½Ρ Π½ΡΠΆΠ½ΠΎ Π·Π°Π±ΡΠ²Π°ΡΡ, ΡΡΠΎ ΠΏΡΠΈ ΡΠΎΠ·Π΄Π°Π½ΠΈΠΈ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ° Π² ΠΏΠ°ΠΏΠΊΠ΅ components
, ΠΌΡ ΠΌΠΎΠΆΠ΅ΠΌ ΡΠΊΡΠΏΠΎΡΡΠΈΡΠΎΠ²Π°ΡΡ ΡΠ΄ΠΎΠ±Π½ΠΎ ΡΡΠΈ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΡ ΠΈΠ· ΠΏΠ°ΠΏΠΊΠΈ, ΡΡΠΎΠ±Ρ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ Π² Π΄ΡΡΠ³ΠΈΡ
ΠΏΠ°ΠΏΠΊΠ°Ρ
(pages
ΠΈΠ»ΠΈ page-components
)
components / index.ts
export * from './Button/Button';
export * from './Divider/Divider';
export * from './Input/Input';
export * from './Select/Select';
export * from './Paragraph/Paragraph';
export * from './ClassCounter/ClassCounter';
export * from './PostItem/PostItem';
export * from './PostForm/PostForm';
export * from './PostFilter/PostFilter';
export * from './PostList/PostList';
ΠΠΎΡΠ»Π΅ ΠΏΠΎΠ΄ΠΎΠ±Π½ΠΎΠ³ΠΎ ΡΠΊΡΠΏΠΎΡΡΠ°, ΠΌΡ ΡΠΌΠΎΠΆΠ΅ΠΌ ΠΏΠΎΠ»ΡΡΠ°ΡΡ Π΄ΠΎΡΡΡΠΏ ΠΊ Π΄Π°Π½Π½ΡΠΌ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ°ΠΌ, ΠΏΡΠΎΡΡΠΎ ΠΎΠ±ΡΠ°ΡΠΈΠ²ΡΠΈΡΡ ΡΠ΅ΡΠ΅Π·: import { Π½ΡΠΆΠ½ΡΠΉ_ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ } from './components'
30:25 β Π§ΡΠΎ ΡΠ°ΠΊΠΎΠ΅ Ρ ΡΠΊΠΈ? useState, useEffect
Π₯ΡΠΊ - ΡΡΠΎ ΡΡΠ½ΠΊΡΠΈΡ, ΠΊΠΎΡΠΎΡΡΡ ΠΏΡΠ΅Π΄ΠΎΡΡΠ°Π²Π»ΡΠ΅Ρ React Π΄Π»Ρ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΡ Π² ΡΡΠ½ΠΊΡΠΈΠΎΠ½Π°Π»ΡΠ½ΡΡ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ°Ρ ΠΈΠ»ΠΈ Π² ΡΠ²ΠΎΠΈΡ ΡΠΎΠ±ΡΡΠ²Π΅Π½Π½ΡΡ Ρ ΡΠΊΠ°Ρ
- Π₯ΡΠΊΠΈ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΡΡΡΡ ΡΠΎΠ»ΡΠΊΠΎ Π½Π° Π²Π΅ΡΡ Π½Π΅ΠΌ ΡΡΠΎΠ²Π½Π΅ Π²Π»ΠΎΠΆΠ΅Π½Π½ΠΎΡΡΠΈ. ΠΡ Π½Π΅Π»ΡΠ·Ρ Π²ΠΊΠ»Π°Π΄ΡΠ²Π°ΡΡ Π² ΡΡΠ»ΠΎΠ²ΠΈΡ, ΡΠΈΠΊΠ»Ρ ΠΈ Π΄ΡΡΠ³ΠΈΠ΅ ΡΡΠ½ΠΊΡΠΈΠΈ.
31:10 β Π‘ΡΠΈΠ»ΠΈ. CSS. ΠΠ»Π°ΡΡΡ
ΠΠ»Ρ Π½Π°ΠΈΠΌΠ΅Π½ΠΎΠ²Π°Π½ΠΈΡ ΠΊΠ»Π°ΡΡΠΎΠ² Π² React ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΡΡ Π°ΡΡΠΈΠ±ΡΡ className
, ΡΠ°ΠΊ ΠΊΠ°ΠΊ ΡΠ»ΠΎΠ²ΠΎ class
ΡΠΆΠ΅ Π·Π°ΡΠ΅Π·Π΅ΡΠ²ΠΈΡΠΎΠ²Π°Π½ΠΎ ΠΏΠΎΠ΄ ΠΊΠ»Π°ΡΡΡ.
ΠΡ ΠΌΠΎΠΆΠ΅ΠΌ ΠΏΡΠΎΡΡΠΎ ΠΈΠΌΠ΅Π½ΠΎΠ²Π°ΡΡ ΠΊΠ»Π°ΡΡΡ ΡΡΠΈΠ»Π΅ΠΉ Π½Π°ΡΠΈΡ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠΎΠ² ΡΡΠ°Π½Π΄Π°ΡΡΠ½ΡΠΌ ΡΠΏΠΎΡΠΎΠ±ΠΎΠΌ
Π ΠΌΠΎΠΆΠ΅ΠΌ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ ΠΌΠΎΠ΄ΡΠ»ΠΈ Π΄Π»Ρ ΠΎΠΏΠΈΡΠ°Π½ΠΈΡ ΡΡΠΈΠ»Π΅ΠΉ. Π ΡΡΠΎΠΌ ΡΠ»ΡΡΠ°Π΅ Π½ΡΠΆΠ½ΠΎ:
- Π Π½Π°Π·Π²Π°Π½ΠΈΠΈ ΡΠ°ΠΉΠ»Π° ΡΡΠΈΠ»Π΅ΠΉ ΡΠΊΠ°Π·Π°ΡΡ
.module
- Π
className
ΡΠΊΠ°Π·Π°ΡΡ ΠΊΠ»Π°ΡΡ ΡΠ΅ΡΠ΅Π· ΡΠΎΡΠΊΡ ΠΎΡ ΠΈΠΌΠΏΠΎΡΡΠΈΡΠΎΠ²Π°Π½Π½ΡΡ ΡΡΠΈΠ»Π΅ΠΉ
Π’Π°ΠΊ ΠΆΠ΅ ΡΡΠΈΠ»ΠΈ ΠΌΠΎΠΆΠ½ΠΎ ΡΠΊΠ°Π·ΡΠ²Π°ΡΡ ΡΠ΅ΡΠ΅Π· Π°ΡΡΠΈΠ±ΡΡ style
, Π²Π½ΡΡΡΡ ΠΊΠΎΡΠΎΡΠΎΠ³ΠΎ ΠΌΡ ΠΏΠ΅ΡΠ΅Π΄Π°ΡΠΌ ΠΎΠ±ΡΠ΅ΠΊΡ ΡΠΎ ΡΡΠΈΠ»ΡΠΌΠΈ
34:30 β Props. ΠΡΠ³ΡΠΌΠ΅Π½ΡΡ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ°.
Π‘Π²ΠΎΠΉΡΡΠ²Π°, ΠΊΠΎΡΠΎΡΡΠ΅ ΠΌΡ ΠΏΠ΅ΡΠ΅Π΄Π°ΡΠΌ Π² ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ, Π½Π°Π·ΡΠ²Π°ΡΡΡΡ props
Π ΡΠ°ΠΌΠΊΠ°Ρ React ΠΏΡΠΈ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠΈ Π΅Π³ΠΎ Π²ΠΌΠ΅ΡΡΠ΅ Ρ TS ΠΌΡ ΠΎΠ±ΡΠ·Π°Π½Ρ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ ΠΈΠ½ΡΠ΅ΡΡΠ΅ΠΉΡΡ Π΄Π»Ρ Π½Π°ΡΠΈΡ ΠΏΠΎΠ»ΡΡΠ°Π΅ΠΌΡΡ ΠΏΡΠΎΠΏΡΠΎΠ² (ΡΡΠΎΠ±Ρ Π²ΡΠ΅Π³Π΄Π° ΠΏΠΎΠ½ΠΈΠΌΠ°ΡΡ, ΡΡΠΎ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ ΠΏΠΎΠ»ΡΡΠ°Π΅Ρ).
ΠΠ½ΡΠ΅ΡΡΠ΅ΠΉΡΡ Π΄Π»Ρ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠΎΠ² ΠΌΡ ΠΎΠ±ΡΡΠ½ΠΎ ΡΠ°ΡΡΠΈΡΡΠ΅ΠΌ Ρ ΠΏΠΎΠΌΠΎΡΡΡ DetailedHTMLProps
ΠΈ ΡΡΠΎΡΠ½ΡΠ΅ΠΌ, ΠΊΠ°ΠΊΠΈΠ΅ Π°ΡΡΠΈΠ±ΡΡΡ ΠΎΠ½ Π΄ΠΎΠ»ΠΆΠ΅Π½ ΠΏΡΠΈΠ½ΠΈΠΌΠ°ΡΡ ΡΠ΅ΡΠ΅Π· HTMLAttributes
.
Π Π΄Π°Π½Π½ΠΎΠΌ ΡΠ»ΡΡΠ°Π΅, ΠΌΡ ΡΠ°ΡΡΠΈΡΡΠ΅ΠΌ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ ΠΎΡ Π΄ΠΈΠ²Π°, ΡΡΠΎΠ±Ρ Π±ΡΠ»Π° Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡΡ Π² Π½Π΅Π³ΠΎ ΠΏΠ΅ΡΠ΅Π΄Π°ΡΡ className
.
Π’Π°ΠΊ ΠΆΠ΅ ΠΌΡ ΡΠΊΠ°Π·ΡΠ²Π°Π΅ΠΌ children
Ρ ΡΠΈΠΏΠΎΠΌ ReactNode
- ΡΡΠΎ ΡΠ΅ Π΄Π°Π½Π½ΡΠ΅, ΠΊΠΎΡΠΎΡΡΠ΅ ΠΌΡ Π²ΠΊΠ»Π°Π΄ΡΠ²Π°Π΅ΠΌ ΠΌΠ΅ΠΆΠ΄Ρ ΠΎΡΠΊΡΡΠ²Π°ΡΡΠΈΠΌ ΠΈ Π·Π°ΠΊΡΡΠ²Π°ΡΡΠΈΠΌ ΡΠ΅Π³ΠΎΠΌ
PostItem.props.ts
import { DetailedHTMLProps, HTMLAttributes, ReactNode } from 'react';
export interface PostItemProps
extends DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
children: ReactNode;
title: string;
}
ΠΠΌΠ΅ΡΡΠΎ ΠΏΡΠΈΠ½ΠΈΠΌΠ°Π΅ΠΌΠΎΠ³ΠΎ ΠΎΠ±ΡΠ΅ΠΊΡΠ° Π΄Π΅ΡΡΡΡΡΠΊΡΡΡΠΈΠ·Π°ΡΠΈΠΈ { language, children }
ΠΌΡ Π±Ρ ΠΌΠΎΠ³Π»ΠΈ ΠΏΡΠΎΡΡΠΎ Π½Π°ΠΏΠΈΡΠ°ΡΡ props
, Π½ΠΎ Π²ΡΡΠ°ΡΠΈΡΡ ΡΡΠ°Π·Ρ Π½ΡΠΆΠ½ΡΠ΅ Π·Π½Π°ΡΠ΅Π½ΠΈΡ - ΡΡΠΎ ΡΠ°ΠΌΡΠΉ ΠΎΠΏΡΠΈΠΌΠ°Π»ΡΠ½ΡΠΉ ΡΠΏΠΎΡΠΎΠ± Π²Π·Π°ΠΈΠΌΠΎΠ΄Π΅ΠΉΡΡΠ²ΠΈΡ, ΡΡΠΎΠ±Ρ ΡΡΠ°Π·Ρ Π²ΠΈΠ΄Π΅ΡΡ ΠΏΠΎΠ»ΡΡΠ°Π΅ΠΌΡΠ΅ ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡΡ
Π’Π°ΠΊ ΠΆΠ΅, ΡΡΠΎΠ±Ρ ΠΏΠ΅ΡΠ΅Π΄Π°ΡΡ ΡΡΠ°Π·Ρ Π²ΡΠ΅ ΠΎΡΡΠ°Π»ΡΠ½ΡΠ΅ ΠΏΡΠΎΠΏΡΡ, ΠΊΠΎΡΠΎΡΡΠ΅ ΠΌΡ Π²Π»ΠΎΠΆΠΈΠΌ Π² ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ, ΠΌΠΎΠΆΠ½ΠΎ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ ...props
ΠΏΡΠΈ ΠΏΠΎΠ»ΡΡΠ΅Π½ΠΈΠΈ props
ΠΈ Π² ΡΠ°ΠΌΠΎΠΌ JSX ΡΠΊΠ°Π·Π°ΡΡ, ΡΡΠΎ ΠΌΡ Π²ΡΠΊΠ»Π°Π΄ΡΠ²Π°Π΅ΠΌ Π²ΡΠ΅ ΠΏΡΠΎΠΏΡΡ Π² ΡΡΠΎΡ ΡΠ»Π΅ΠΌΠ΅Π½Ρ: <div {...props}>
. Π’Π°ΠΊΠΎΠΉ ΠΏΠΎΠ΄Ρ
ΠΎΠ΄ Π±ΠΎΠ»Π΅Π΅ Π°ΠΊΡΡΠ°Π»Π΅Π½, ΠΊΠΎΠ³Π΄Π° ΠΌΡ ΡΠΎΠ·Π΄Π°ΡΠΌ ΡΠ²ΠΎΠΈ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΡ ΠΊΠ½ΠΎΠΏΠΎΠΊ, ΠΈΠ½ΠΏΡΡΠΎΠ² ΠΈ ΠΎΡΡΠ°Π»ΡΠ½ΡΡ
ΠΏΡΠΎΡΡΡΡ
ΡΠ»Π΅ΠΌΠ΅Π½ΡΠΎΠ².
PostItem.tsx
export const PostItem = ({ title, children, className, ...props }: PostItemProps) => {
return (
<div className={cn(styles.wrapper, className)} {...props}>
<div className={styles.post}>
<div className={styles.post__content}>
<h2>{title}</h2>
<Paragraph size={'l'}>{children}</Paragraph>
</div>
<Button buttonType={'purple'} className={styles.post__button}>
Π£Π΄Π°Π»ΠΈΡΡ ΠΏΠΎΡΡ
</Button>
</div>
</div>
);
};
ΠΠ΅ΡΠ΅Π΄Π°ΡΡΡΡ ΠΏΡΠΎΠΏΡΡ ΡΠΎΠ²Π½ΠΎ ΡΠ°ΠΊΠΈΠΌ ΠΆΠ΅ ΠΎΠ±ΡΠ°Π·ΠΎΠΌ, ΠΊΠ°ΠΊ ΠΈ Π°ΡΡΠΈΠ±ΡΡΡ. Π ΡΠ»ΡΡΠ°Π΅ Ρ TS ΠΊΠΎΠΌΠΏΠΈΠ»ΡΡΠΎΡ Π½Π°ΠΌ ΠΏΠΎΠ΄ΡΠΊΠ°ΠΆΠ΅Ρ, ΠΊΠ°ΠΊΠΈΠ΅ Π·Π½Π°ΡΠ΅Π½ΠΈΡ ΠΌΡ ΠΌΠΎΠΆΠ΅ΠΌ Π²ΡΡΠ°Π²ΠΈΡΡ Π² Π΄Π°Π½Π½ΡΠΉ ΡΠ»Π΅ΠΌΠ΅Π½Ρ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ
PostList.tsx
export const Posts = () => {
const [postsData, setPostsData] = useState([
{ id: 'asd1', title: 'Javascript', body: 'ΠΡΡΡΠΈΠΉ ΡΠ·ΡΠΊ Π½Π° ΠΠ΅ΠΌΠ»Π΅' },
{ id: 'adsgsa2', title: 'C#', body: 'ΠΡΡΡΠΈΠΉ ΡΠ·ΡΠΊ Π½Π° ΠΠ΅ΠΌΠ»Π΅' },
{ id: 'fsdagha3', title: 'Python', body: 'ΠΡΡΡΠΈΠΉ ΡΠ·ΡΠΊ Π½Π° ΠΠ΅ΠΌΠ»Π΅' },
]);
return (
<div>
{postsData.map(p => (
<PostItem key={p.id} title={p.title}>
{p.body}
</PostItem>
))}
</div>
);
};
Π’Π°ΠΊ ΠΆΠ΅ ΠΌΡ ΠΌΠΎΠΆΠ΅ΠΌ Π²ΡΠ²Π΅ΡΡΠΈ props
Π² ΠΊΠΎΠ½ΡΠΎΠ»Ρ ΠΈ ΡΠ²ΠΈΠ΄ΠΈΠΌ, ΡΡΠΎ ΡΡΠΎ ΠΏΡΠΎΡΡΠΎ ΠΎΠ±ΡΠ΅ΠΊΡ Ρ Π΄Π°Π½Π½ΡΠΌΠΈ, ΠΊΠΎΡΠΎΡΡΠ΅ ΠΌΡ ΠΏΠ΅ΡΠ΅Π΄Π°Π»ΠΈ Π² ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ ΠΈΠ·Π²Π½Π΅
36:55 β Π Π°Π±ΠΎΡΡ ΡΠΎ ΡΠΏΠΈΡΠΊΠ°ΠΌΠΈ. ΠΡΠ΅ΠΎΠ±ΡΠ°Π·ΠΎΠ²Π°Π½ΠΈΠ΅ ΠΌΠ°ΡΡΠΈΠ²Π° ΠΎΠ±ΡΠ΅ΠΊΡΠΎΠ² Π² ΠΌΠ°ΡΡΠΈΠ² React ΡΠ»Π΅ΠΌΠ΅Π½ΡΠΎΠ²
ΠΠΎΠ³Π΄Π° Π½Π°ΠΌ Π½ΡΠΆΠ½ΠΎ Π²ΡΠ²Π΅ΡΡΠΈ ΠΌΠ°ΡΡΠΈΠ² ΡΠ»Π΅ΠΌΠ΅Π½ΡΠΎΠ² Ρ ΠΎΠΏΡΠ΅Π΄Π΅Π»ΡΠ½Π½ΠΎΠΉ ΡΡΡΡΠΊΡΡΡΠΎΠΉ, ΠΌΡ ΠΌΠΎΠΆΠ΅ΠΌ Π²ΠΎΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡΡΡ ΡΡΠ½ΠΊΡΠΈΡΠΌΠΈ JS Π²Π½ΡΡΡΠΈ JSX. ΠΠ»Ρ ΡΡΠΎΠ³ΠΎ ΡΡΠ½ΠΊΡΠΈΠΈ Π½ΡΠΆΠ½ΠΎ Π²ΠΏΠΈΡΠ°ΡΡ Π²Π½ΡΡΡΡ { }
ΡΠΊΠΎΠ±ΠΎΠΊ.
ΠΠ»Ρ ΠΏΠ΅ΡΠ΅Π±ΠΎΡΠ° ΠΌΠ°ΡΡΠΈΠ²Π° ΠΌΠΎΠΆΠ½ΠΎ Π²ΠΎΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡΡΡ ΡΡΠ½ΠΊΡΠΈΠ΅ΠΉ map()
.
ΠΠΎΠ³Π΄Π° ΠΌΡ ΡΠΎΠ·Π΄Π°ΡΠΌ ΡΠΏΠΈΡΠΊΠΈ, ΠΎΠ±ΡΠ·Π°ΡΠ΅Π»ΡΠ½ΠΎ Π΄Π»Ρ Π²ΡΠ΅Ρ
ΡΠ»Π΅ΠΌΠ΅Π½ΡΠΎΠ² Π½ΡΠΆΠ½ΠΎ ΡΠΊΠ°Π·Π°ΡΡ ΡΠ½ΠΈΠΊΠ°Π»ΡΠ½ΡΠΉ ΠΊΠ»ΡΡ ΠΈ ΠΏΠ΅ΡΠ΅Π΄Π°ΡΡ Π΅Π³ΠΎ ΡΠ΅ΡΠ΅Π· Π°ΡΡΠΈΠ±ΡΡ key
. ΠΠ»Ρ ΠΊΠ»ΡΡΠ° ΠΎΠ±ΡΡΠ½ΠΎ Π½Π΅ ΡΡΠΎΠΈΡ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ ΠΈΠ½Π΄Π΅ΠΊΡ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠ° Π² ΠΌΠ°ΡΡΠΈΠ²Π΅ - ΡΡΠΎ ΠΏΠ»ΠΎΡ
Π°Ρ ΠΏΡΠ°ΠΊΡΠΈΠΊΠ°, ΡΠ°ΠΊ ΠΊΠ°ΠΊ ΠΎΠ½ ΠΌΠΎΠΆΠ΅Ρ ΠΏΠΎΠΌΠ΅Π½ΡΡΡΡΡ ΠΏΠΎΡΠ»Π΅ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΡ ΡΠ°Π·ΠΌΠ΅ΡΠ° ΠΌΠ°ΡΡΠΈΠ²Π°. Π Π΅ΠΊΠΎΠΌΠ΅Π½Π΄ΡΠ΅ΡΡΡ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ ΠΊΠ°ΠΊΠΎΠΉ-Π»ΠΈΠ±ΠΎ ΡΡΠ°ΡΠΈΡΠ½ΡΠΉ ΠΈΠ½Π΄Π΅ΠΊΡ. ΠΡΠΎ ΠΏΠΎΠΌΠΎΠΆΠ΅Ρ ΡΠ΅Π°ΠΊΡΡ Π·Π°ΠΏΠΎΠΌΠ½ΠΈΡΡ ΡΠ»Π΅ΠΌΠ΅Π½Ρ ΠΌΠ°ΡΡΠΈΠ²Π° ΠΈ Π½Π΅ ΠΏΠ΅ΡΠ΅ΡΠΈΡΠΎΠ²ΡΠ²Π°ΡΡ Π²ΡΠ΅ Π²ΡΠ²Π΅Π΄Π΅Π½Π½ΡΠ΅ ΡΠ»Π΅ΠΌΠ΅Π½ΡΡ.
PostList.tsx
export const Posts = () => {
const [postsData, setPostsData] = useState([
{ id: 'asd1', title: 'Javascript', body: 'ΠΡΡΡΠΈΠΉ ΡΠ·ΡΠΊ Π½Π° ΠΠ΅ΠΌΠ»Π΅' },
{ id: 'adsgsa2', title: 'C#', body: 'ΠΡΡΡΠΈΠΉ ΡΠ·ΡΠΊ Π½Π° ΠΠ΅ΠΌΠ»Π΅' },
{ id: 'fsdagha3', title: 'Python', body: 'ΠΡΡΡΠΈΠΉ ΡΠ·ΡΠΊ Π½Π° ΠΠ΅ΠΌΠ»Π΅' },
]);
return (
<div>
{postsData.map(p => (
<PostItem key={p.id} title={p.title}>
{p.body}
</PostItem>
))}
</div>
);
};
Π ΠΏΡΠΈΠΌΠ΅ΡΠ½ΠΎ ΡΠ°ΠΊ ΠΌΡ ΠΏΠΎΠ»ΡΡΠΈΠΌ ΠΈΡΠΎΠ³ΠΎΠ²ΡΠΉ ΠΌΠ°ΡΡΠΈΠ² Π½Π°ΡΠΈΡ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠΎΠ²
42:30 β Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ UI Π±ΠΈΠ±Π»ΠΈΠΎΡΠ΅ΠΊΠΈ. ΠΠ΅ΡΠ²ΡΠ΅ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΡ. CSS ΠΌΠΎΠ΄ΡΠ»ΠΈ. ΠΡΠΎΠΏΡ children
ΠΠ±ΡΡΠ½ΠΎ Π² ΡΠ²ΠΎΠ΅ΠΉ ΡΠ°Π±ΠΎΡΠ΅ ΠΏΡΠΈΠ΄ΡΡΡΡ ΡΠ°ΡΡΠΎ ΡΠΎΠ·Π΄Π°Π²Π°ΡΡ ΡΠ²ΠΎΡ UI-Π±ΠΈΠ±Π»ΠΈΠΎΡΠ΅ΠΊΡ ΠΏΠΎΠ΄ ΠΊΠ°ΠΆΠ΄ΡΠΉ ΡΠ°ΠΉΡ, ΠΊΠΎΡΠΎΡΡΠΉ ΠΌΡ Π±ΡΠ΄Π΅ΠΌ ΡΠ΅Π°Π»ΠΈΠ·ΠΎΠ²ΡΠ²Π°ΡΡ.
ΠΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ ΠΊΠ½ΠΎΠΏΠΊΠΈ:
Button.tsx
import React from 'react';
import styles from './Button.module.scss';
import cn from 'classnames';
import { ButtonProps } from './Button.props';
export const Button = ({
buttonType = 'gray',
className,
children,
...props
}: ButtonProps): JSX.Element => {
return (
<button
{/* Π² Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠΈ ΠΎΡ ΠΏΠ΅ΡΠ΅Π΄Π°Π½Π½ΠΎΠ³ΠΎ Π°ΡΡΠΈΠ±ΡΡΠ° ΡΡΠΈΠ»Ρ, Π±ΡΠ΄Π΅Ρ ΠΏΡΠΈΡΠ²Π°ΠΈΠ²Π°ΡΡΡΡ ΡΠ²ΠΎΠΉ ΡΡΠΈΠ»Ρ Π΄Π»Ρ ΠΊΠ½ΠΎΠΏΠΊΠΈ */}
className={cn(styles.button, className, {
[styles.gray]: buttonType == 'gray',
[styles.ghost]: buttonType == 'ghost',
[styles.purple]: buttonType == 'purple',
})}
{/* ΡΡΠ΄Π° Π±ΡΠ΄ΡΡ ΠΏΠ΅ΡΠ΅Π΄Π°Π²Π°ΡΡΡΡ Π²ΡΠ΅ ΠΎΡΡΠ°Π»ΡΠ½ΡΠ΅ ΠΏΡΠΎΠΏΡΡ, ΠΊΠΎΡΠΎΡΡΠ΅ ΠΌΡ ΠΏΡΠΈΠΏΠΈΡΠ΅ΠΌ ΠΊ ΠΊΠ½ΠΎΠΏΠΊΠ΅ */}
{...props}
>
{/* ΡΡΡ Π±ΡΠ΄Π΅Ρ Π½Π°Ρ
ΠΎΠ΄ΠΈΡΡΡΡ Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅ ΠΏΠ΅ΡΠ΅Π΄Π°Π½Π½ΠΎΠ΅ ΠΌΠ΅ΠΆΠ΄Ρ ΡΠ΅Π³Π°ΠΌΠΈ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ° */}
{children}
</button>
);
};
Π’ΡΡ ΠΌΡ ΠΎΠΏΠΈΡΠ΅ΠΌ ΡΠ΅ ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡΡ, ΠΊΠΎΡΠΎΡΡΠ΅ Π΄ΠΎΠ»ΠΆΠ½Π° ΠΏΡΠΈΠ½ΠΈΠΌΠ°ΡΡ Π² ΡΠ΅Π±Ρ ΠΊΠ½ΠΎΠΏΠΊΠ°. Π§ΡΠΎΠ±Ρ ΠΎΠΏΠΈΡΠ°ΡΡ ΠΏΠΎΠ»ΡΡΠ°Π΅ΠΌΡΠ΅ Π°ΡΡΠΈΠ±ΡΡΡ, ΠΌΡ Π΄ΠΎΠ»ΠΆΠ½Ρ ΡΠ°ΡΡΠΈΡΠΈΡΡ ΠΈΠ½ΡΠ΅ΡΡΠ΅ΠΉΡ ΠΎΡ DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>
, Π³Π΄Π΅ ΠΌΡ ΡΠ°ΡΡΠΈΡΡΠ΅ΠΌΡΡ ΠΎΡ Button
.
Button.props.ts
import { ButtonHTMLAttributes, DetailedHTMLProps, ReactNode } from 'react';
export interface ButtonProps
extends DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement> {
children: ReactNode;
buttonType: 'gray' | 'purple' | 'ghost';
}
Π‘ΡΠΈΠ»ΠΈ ΠΊΠ½ΠΎΠΏΠΊΠΈ:
Button.module.css
.button {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
padding: 20px;
width: 100%;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: 700;
color: white;
cursor: pointer;
}
.gray {
grid-area: reduce;
background: var(--anti-accent);
transition: all 0.2s;
&:hover {
background: var(--anti-accent-hover);
transform: translateY(-4px);
}
&:active {
background: var(--anti-accent-clicked);
transform: translateY(4px);
}
}
.purple {
grid-area: increase;
background: var(--primary);
transition: all 0.2s;
&:hover {
background: var(--primary-hover);
transform: translateY(-4px);
}
&:active {
background: var(--primary-clicked);
transform: translateY(4px);
}
}
.ghost {
position: absolute;
top: 20px;
left: 20px;
width: 100px;
height: 20px;
grid-area: increase;
background: none;
transition: all 0.2s;
border: 2px dashed var(--anti-accent);
border-radius: 2px;
&:hover {
background: var(--anti-accent-hover);
transform: translateY(-4px);
}
&:active {
transform: translateY(4px);
}
}
50:00 β ΠΡΠ΅Π΄ΠΎΡΠ²ΡΠ°ΡΠ°Π΅ΠΌ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ ΡΡΡΠ°Π½ΠΈΡΡ ΠΏΡΠΈ submit ΡΠΎΡΠΌΡ
Π§ΡΠΎΠ±Ρ ΠΏΡΠ΅Π΄ΠΎΡΠ²ΡΠ°ΡΠΈΡΡ ΡΡΠ°Π±Π°ΡΡΠ²Π°Π½ΠΈΠ΅ Π΄Π΅ΡΠΎΠ»ΡΠ½ΠΎΠΉ ΠΏΠ΅ΡΠ΅Π·Π°Π³ΡΡΠ·ΠΊΠΈ ΡΡΡΠ°Π½ΠΈΡΡ, Π½ΡΠΆΠ½ΠΎ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ Π½Π° ΠΈΠ²Π΅Π½ΡΠ΅ Π΄Π°Π½Π½ΠΎΠ³ΠΎ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠ° ΠΎΡΠΊΠ»ΡΡΠ΅Π½ΠΈΠ΅ ΠΏΠΎΠ²Π΅Π΄Π΅Π½ΠΈΡ Π±ΡΠ°ΡΠ·Π΅ΡΠ° preventDefault()
const addNewPost = (event): void => {
event.preventDefault();
};
50:45 β Ρ ΡΠΊ useRef. ΠΠΎΡΡΡΠΏ ΠΊ DOM ΡΠ»Π΅ΠΌΠ΅Π½ΡΡ. ΠΠ΅ΡΠΏΡΠ°Π²Π»ΡΠ΅ΠΌΡΠΉ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ
ΠΠΈΠΆΠ΅ ΠΏΡΠΈΠ²Π΅Π΄Π΅Π½Ρ ΠΏΡΠΈΠΌΠ΅ΡΡ ΡΠΏΡΠ°Π²Π»ΡΠ΅ΠΌΠΎΠ³ΠΎ ΠΈ Π½Π΅ΡΠΏΡΠ°Π²Π»ΡΠ΅ΠΌΠΎΠ³ΠΎ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ°:
- Π£ΠΏΡΠ°Π²Π»ΡΠ΅ΠΌΡΠΉ:
- Π£ΠΏΡΠ°Π²Π»ΡΠ΅ΠΌΡΠΉ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ ΠΈΠΌΠ΅Π΅Ρ ΠΏΠΎΠ΄ΠΊΠΎΠ½ΡΡΠΎΠ»ΡΠ½ΠΎΠ΅ Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅
- ΠΡΠΎ ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ ΡΠ²ΡΠ·Π°Π½ΠΎ Ρ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠΎΠΌ ΡΠ΅ΡΠ΅Π· Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅
- ΠΠ΅ΡΠΏΡΠ°Π²Π»ΡΠ΅ΠΌΡΠΉ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ:
- ΠΠ΅ ΠΈΠΌΠ΅Π΅Ρ ΠΏΠΎΠ΄ΠΊΠΎΠ½ΡΡΠΎΠ»ΡΠ½ΠΎΠ³ΠΎ Π·Π½Π°ΡΠ΅Π½ΠΈΡ
- ΠΠ»Ρ ΠΏΠΎΠ»ΡΡΠ΅Π½ΠΈΡ Π΄ΠΎΡΡΡΠΏΠ° ΠΊ Π½Π΅ΠΌΡ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΡΡΡ ΠΎΡΠ΄Π΅Π»ΡΠ½ΡΠΉ Ρ
ΡΠΊ
useRef
ΠΠΎΡΡΠ΄ΠΎΠΊ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΡ ΡΠ΅ΡΠ°:
- ΠΠ½ΠΈΡΠΈΠ°Π»ΠΈΠ·ΠΈΡΡΠ΅ΠΌ
useRef
- ΠΠ΅ΡΠ΅Π΄Π°ΡΠΌ Π² Π°ΡΡΠΈΡΠ±ΡΡ
ref
ΠΏΡΠΎΠΈΠ½ΠΈΡΠΈΠ°Π»ΠΈΠ·ΠΈΡΠΎΠ²Π°Π½Π½ΡΠΉuseRef
- ΠΠ±ΠΎΡΠ°ΡΠΈΠ²Π°Π΅ΠΌ ΡΠ°ΠΌ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ Π² ΡΠ²ΠΎΠ΅ΠΉ Π²Π½ΡΡΡΠ΅Π½Π½Π΅ΠΉ ΡΠ΅Π°Π»ΠΈΠ·Π°ΡΠΈΠΈ Π²
forwardRef
- ΠΡΠΎΠΊΠΈΠ΄ΡΠ²Π°Π΅ΠΌ
ref
Π²Π½ΡΡΡΡ ΡΠ΅Π°Π»ΠΈΠ·Π°ΡΠΈΠΈ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ°
export const PostList = () => {
const [postsData, setPostsData] = useState([
{ id: 'asd1', title: 'Javascript', body: 'ΠΡΡΡΠΈΠΉ ΡΠ·ΡΠΊ Π½Π° ΠΠ΅ΠΌΠ»Π΅' },
{ id: 'adsgsa2', title: 'C#', body: 'ΠΡΡΡΠΈΠΉ ΡΠ·ΡΠΊ Π½Π° ΠΠ΅ΠΌΠ»Π΅' },
{ id: 'fsdagha3', title: 'Python', body: 'ΠΡΡΡΠΈΠΉ ΡΠ·ΡΠΊ Π½Π° ΠΠ΅ΠΌΠ»Π΅' },
]);
const [title, setTitle] = useState<string>('');
// Ρ
ΡΠΊ ΡΠ΅ΡΠ΅ΡΠ΅Π½ΡΠ° Π½Π° ΠΎΠ±ΡΠ΅ΠΊΡ DOM-Π΄Π΅ΡΠ΅Π²Π°
const bodyInputRef = useRef<HTMLInputElement>(null);
const addNewPost = (event: any): void => {
event.preventDefault();
// Π²ΡΠ²ΠΎΠ΄ΠΈΠΌ ΡΠ΅ΠΊΡΡΠΈΠΉ ΠΎΠ±ΡΠ΅ΠΊΡ, Π΅ΡΠ»ΠΈ ΠΎΠ½ Π΅ΡΡΡ ?
console.log(bodyInputRef.current?.value);
};
return (
<div className={styles.wrapper}>
<div className={styles.formBlock}>
<form className={styles.form}>
{/* ΡΠΏΡΠ°Π²Π»ΡΠ΅ΠΌΡΠΉ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ */}
<Input
value={title}
onChange={e => setTitle(e.target.value)}
className={styles.form__input}
type='text'
placeholder={'ΠΠ°Π·Π²Π°Π½ΠΈΠ΅ ΠΏΠΎΡΡΠ°'}
/>
{/* Π½Π΅ΡΠΏΡΠ°Π²Π»ΡΠ΅ΠΌΡΠΉ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ */}
<Input
// Π½Π°Π²Π΅ΡΠΈΠ²Π°Π΅ΠΌ ΡΡΡΠ»ΠΊΡ ΡΠ΅ΡΠ°
ref={bodyInputRef}
className={styles.form__input}
type='text'
placeholder={'ΠΠΏΠΈΡΠ°Π½ΠΈΠ΅ ΠΏΠΎΡΡΠ°'}
/>
<Button
className={styles.form__button}
buttonType={'purple'}
onClick={addNewPost}
>
ΠΠΎΠ±Π°Π²ΠΈΡΡ ΠΏΠΎΡΡ
</Button>
</form>
</div>
<div className={styles.list}>
{postsData.map(p => (
<PostItem key={p.id} title={p.title}>
{p.body}
</PostItem>
))}
</div>
</div>
);
};
ΠΠ°Π»Π΅Π΅ ΡΠ°ΠΌ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ <Input>
Π½ΡΠΆΠ½ΠΎ ΠΎΠ±Π΅ΡΠ½ΡΡΡ Π² forwardRef
ΠΏΠΎΠ»Π½ΠΎΡΡΡΡ (Π²ΡΡ ΡΡΠ½ΠΊΡΠΈΡ ΠΎΠ±Π΅ΡΠ½ΡΡΡ Π²Π½ΡΡΡΡ ( )
ΡΠΊΠΎΠ±ΠΎΠΊ) ΠΈ ΠΏΠ΅ΡΠ΅Π΄Π°ΡΡ Π²Π½ΡΡΡΡ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ° Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡΠ΅Π»ΡΠ½ΠΎΠ΅ ΡΠ²ΠΎΠΉΡΡΠ²ΠΎ ref
. Π‘Π°ΠΌΠΎ ΡΠ²ΠΎΠΉΡΡΠ²ΠΎ ref
Π½ΡΠΆΠ½ΠΎ Π²Π»ΠΎΠΆΠΈΡΡ Π² ΠΊΠ°ΡΠ΅ΡΡΠ²Π΅ Π°ΡΡΠΈΠ±ΡΡΠ° Π²Π½ΡΡΡΡ Π½Π°ΡΠ΅Π³ΠΎ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ°
Input.tsx
// ΠΎΠ±ΠΎΡΠ°ΡΠΈΠ²Π°Π΅ΠΌ ΡΡΠ½ΠΊΡΠΈΡ ΠΏΠΎΠ»Π½ΠΎΡΡΡΡ Π² forwardRef ΠΈ Π²ΠΊΠ»Π°Π΄ΡΠ²Π°Π΅ΠΌ Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡΠ΅Π»ΡΠ½ΡΠΉ ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡ ref
export const Input = forwardRef<HTMLInputElement, InputProps>(
({ className, ...props }: InputProps, ref: ForwardedRef<HTMLInputElement>): JSX.Element => {
// Π·Π΄Π΅ΡΡ ΠΏΠ΅ΡΠ΅Π΄Π°ΡΠΌ Π² ΠΊΠ°ΡΠ΅ΡΡΠ²Π΅ Π°ΡΡΠΈΠ±ΡΡΠ° ΡΡΡΠ»ΠΊΡ ΡΠ΅ΡΠ° ref={ref}
return <input ref={ref} className={cn(className, styles.input)} {...props} />;
},
);
Π ΠΏΠΎ ΠΈΡΠΎΠ³Ρ, ΠΌΡ ΡΠΌΠΎΠΆΠ΅ΠΌ ΡΠ²ΠΈΠ΄Π΅ΡΡ Π² ΠΊΠΎΠ½ΡΠΎΠ»ΠΈ, ΡΡΠΎ ΡΠ΅Ρ Π΄Π°ΡΡ Π½Π°ΠΌ Π΄ΠΎΡΡΡΠΏ ΠΊ Π·Π½Π°ΡΠ΅Π½ΠΈΡ Π΄Π°Π½Π½ΠΎΠ³ΠΎ ΠΈΠ½ΠΏΡΡΠ°
Π ΠΏΠΎΡΡΠΎΠΌΡ Π²Π΅ΡΠ½ΡΠΌ ΠΎΠ±ΡΠ°ΡΠ½ΠΎ ΠΎΠ±ΡΡΠ½ΠΎΠ΅ Π²Π·Π°ΠΈΠΌΠΎΠ΄Π΅ΠΉΡΡΠ²ΠΈΠ΅ Ρ Π΄Π΅ΡΠ΅Π²ΠΎΠΌ. ΠΠ΄Π½Π°ΠΊΠΎ ΡΡΡ ΡΠΎΡ
ΡΠ°Π½ΡΠ΅ΡΡΡ ΠΏΡΠΎΠ±Π»Π΅ΠΌΠ° Π² ΡΠΎΠΌ, ΡΡΠΎ ΠΌΡ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΠΌ Π΄Π²Π° ΡΠ°Π·Π½ΡΡ
useState
, Ρ
ΠΎΡΡ ΠΌΠΎΠ³Π»ΠΈ Π±Ρ ΡΠΎΠΊΡΠ°ΡΠΈΡΡ Π·Π°ΠΏΠΈΡΡ
export const PostList = () => {
const [postsData, setPostsData] = useState([
{ id: 'asd1', title: 'Javascript', body: 'ΠΡΡΡΠΈΠΉ ΡΠ·ΡΠΊ Π½Π° ΠΠ΅ΠΌΠ»Π΅' },
{ id: 'adsgsa2', title: 'C#', body: 'ΠΡΡΡΠΈΠΉ ΡΠ·ΡΠΊ Π½Π° ΠΠ΅ΠΌΠ»Π΅' },
{ id: 'fsdagha3', title: 'Python', body: 'ΠΡΡΡΠΈΠΉ ΡΠ·ΡΠΊ Π½Π° ΠΠ΅ΠΌΠ»Π΅' },
]);
const [title, setTitle] = useState<string>('');
const [body, setBody] = useState<string>('');
const addNewPost = (event: any): void => {
event.preventDefault();
// ΠΏΠΎΠ»ΡΡΠ°Π΅ΠΌ ΡΠ΅ΠΊΡΡΠΈΠ΅ Π·Π½Π°ΡΠ΅Π½ΠΈΡ ΠΈΠ· useState
const newPost = {
id: `${Date.now()}`,
title,
body,
};
// Π½Π΅ ΠΌΡΡΠΈΡΡΠ΅ΠΌ ΠΌΠ°ΡΡΠΈΠ² - Π²ΡΡΠ°Π²Π»ΡΠ΅ΠΌ ΡΡΠ°ΡΡΠΉ ΠΌΠ°ΡΡΠΈΠ² ΠΈ Π΄ΠΎΠ±Π°Π²Π»ΡΠ΅ΠΌ Π½ΠΎΠ²ΡΠΉ ΡΠ»Π΅ΠΌΠ΅Π½Ρ
setPostsData([...postsData, newPost]);
// ΠΡΠΈΡΠ°Π΅ΠΌ ΠΈΠ½ΠΏΡΡΡ
setTitle('');
setBody('');
};
return (
<div className={styles.wrapper}>
<div className={styles.formBlock}>
<form className={styles.form}>
<Input
value={title}
onChange={e => setTitle(e.target.value)}
className={styles.form__input}
type='text'
placeholder={'ΠΠ°Π·Π²Π°Π½ΠΈΠ΅ ΠΏΠΎΡΡΠ°'}
/>
<Input
value={body}
onChange={e => setBody(e.target.value)}
className={styles.form__input}
type='text'
placeholder={'ΠΠΏΠΈΡΠ°Π½ΠΈΠ΅ ΠΏΠΎΡΡΠ°'}
/>
<Button
className={styles.form__button}
buttonType={'purple'}
onClick={addNewPost}
>
ΠΠΎΠ±Π°Π²ΠΈΡΡ ΠΏΠΎΡΡ
</Button>
</form>
</div>
<div className={styles.list}>
{postsData.map(p => (
<PostItem key={p.id} title={p.title}>
{p.body}
</PostItem>
))}
</div>
</div>
);
};
Π’ΡΡ ΠΏΡΠ΅Π΄ΡΡΠ°Π²Π»Π΅Π½Π° Π±ΠΎΠ»Π΅Π΅ Π»Π°ΠΊΠΎΠ½ΠΈΡΠ½Π°Ρ Π·Π°ΠΏΠΈΡΡ Ρ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠ΅ΠΌ ΠΎΠ΄Π½ΠΎΠ³ΠΎ useState
ΠΈ ΡΠΎΠΊΡΠ°ΡΡΠ½Π½ΠΎΠΉ ΡΡΠ½ΠΊΡΠΈΠ΅ΠΉ addNewPost
import React, { useRef, useState } from 'react';
import styles from './PostList.module.scss';
import { PostItem } from '@/components/PostItem/PostItem';
import { Input } from '@/components/Input/Input';
import { Button } from '@/components';
export const PostList = () => {
const [postsData, setPostsData] = useState([
{ id: 'asd1', title: 'Javascript', body: 'ΠΡΡΡΠΈΠΉ ΡΠ·ΡΠΊ Π½Π° ΠΠ΅ΠΌΠ»Π΅' },
{ id: 'adsgsa2', title: 'C#', body: 'ΠΡΡΡΠΈΠΉ ΡΠ·ΡΠΊ Π½Π° ΠΠ΅ΠΌΠ»Π΅' },
{ id: 'fsdagha3', title: 'Python', body: 'ΠΡΡΡΠΈΠΉ ΡΠ·ΡΠΊ Π½Π° ΠΠ΅ΠΌΠ»Π΅' },
]);
const [post, setPost] = useState<{ title: string; body: string }>({
title: '',
body: '',
});
const addNewPost = (event: any): void => {
event.preventDefault();
// Π΄ΠΎΠ±Π°Π²Π»ΡΠ΅ΠΌ Π½ΠΎΠ²ΠΎΠ΅ Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅
setPostsData([...postsData, { ...post, id: `${Date.now()}` }]);
// ΠΎΡΠΈΡΠ°Π΅ΠΌ ΠΈΠ½ΠΏΡΡΡ
setPost({
title: '',
body: '',
});
};
return (
<div className={styles.wrapper}>
<div className={styles.formBlock}>
<form className={styles.form}>
<Input
value={post.title}
// ΡΡΠ΄Π° Π·Π°ΠΊΠΈΠ΄ΡΠ²Π°Π΅ΠΌ ΡΡΠ°ΡΡΠΉ ΠΏΠΎΡΡ ΠΈ ΠΏΠ΅ΡΠ΅Π·Π°ΡΠΈΡΠ°Π΅ΠΌ Π½ΡΠΆΠ½ΠΎΠ΅ ΠΏΠΎΠ»Π΅
onChange={e => setPost({ ...post, title: e.target.value })}
className={styles.form__input}
type='text'
placeholder={'ΠΠ°Π·Π²Π°Π½ΠΈΠ΅ ΠΏΠΎΡΡΠ°'}
/>
<Input
value={post.body}
// ΡΡΠ΄Π° Π·Π°ΠΊΠΈΠ΄ΡΠ²Π°Π΅ΠΌ ΡΡΠ°ΡΡΠΉ ΠΏΠΎΡΡ ΠΈ ΠΏΠ΅ΡΠ΅Π·Π°ΡΠΈΡΠ°Π΅ΠΌ Π½ΡΠΆΠ½ΠΎΠ΅ ΠΏΠΎΠ»Π΅
onChange={e => setPost({ ...post, body: e.target.value })}
className={styles.form__input}
type='text'
placeholder={'ΠΠΏΠΈΡΠ°Π½ΠΈΠ΅ ΠΏΠΎΡΡΠ°'}
/>
<Button
className={styles.form__button}
buttonType={'purple'}
onClick={addNewPost}
>
ΠΠΎΠ±Π°Π²ΠΈΡΡ ΠΏΠΎΡΡ
</Button>
</form>
</div>
<div className={styles.list}>
{postsData.map(p => (
<PostItem key={p.id} title={p.title}>
{p.body}
</PostItem>
))}
</div>
</div>
);
};
ΠΡΠΎΠ³: Π΄ΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠ΅ Π½ΠΎΠ²ΠΎΠ³ΠΎ ΠΏΠΎΡΡΠ° ΡΠ°Π±ΠΎΡΠ°Π΅Ρ
57:35 β React Devtools. ΠΠ½ΡΡΡΡΠΌΠ΅Π½ΡΡ ΡΠ°Π·ΡΠ°Π±ΠΎΡΡΠΈΠΊΠ° React
React DevTools - Π½Π΅ΠΎΠ±Ρ ΠΎΠ΄ΠΈΠΌΡΠΉ ΠΏΠ»Π°Π³ΠΈΠ½ Π² ΡΠ°Π±ΠΎΡΠ΅, ΠΊΠΎΡΠΎΡΡΠΉ ΠΏΠΎΠ·Π²ΠΎΠ»ΠΈΡ ΠΏΡΠΎΡΠΌΠΎΡΡΠ΅ΡΡ Π΄Π΅ΡΠ΅Π²ΠΎ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠΎΠ² ΡΡΡΠ°Π½ΠΈΡΡ, Π²Π»ΠΈΡΠ½ΠΈΠ΅ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΡ ΡΡΠ΅ΠΉΡΠ° ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠΎΠ²
59:15 β ΠΠ±ΠΌΠ΅Π½ Π΄Π°Π½Π½ΡΠΌΠΈ ΠΌΠ΅ΠΆΠ΄Ρ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ°ΠΌΠΈ. ΠΡ ΡΠΎΠ΄ΠΈΡΠ΅Π»Ρ ΠΊ ΡΠ΅Π±Π΅Π½ΠΊΡ. ΠΡ ΡΠ΅Π±Π΅Π½ΠΊΠ° ΠΊ ΡΠΎΠ΄ΠΈΡΠ΅Π»Ρ.
ΠΡ ΠΌΠΎΠΆΠ΅ΠΌ ΠΏΠ΅ΡΠ΅Π΄Π°Π²Π°ΡΡ ΡΡΠ½ΠΊΡΠΈΠΈ ΡΠ΅ΡΡΡΡΠΌΡ ΡΠ°Π·Π½ΡΠΌΠΈ ΡΠΏΠΎΡΠΎΠ±Π°ΠΌΠΈ:
- Π‘Π°ΠΌΡΠΉ ΠΏΡΠΎΡΡΠΎΠΉ ΡΡΠ°Π½Π΄Π°ΡΡΠ½ΡΠΉ - ΡΡΠΎ ΠΎΡ ΡΠΎΠ΄ΠΈΡΠ΅Π»Ρ ΠΊ ΡΠ΅Π±ΡΠ½ΠΊΡ
- ΠΡ ΡΠ΅Π±ΡΠ½ΠΊΠ° ΠΊ ΡΠΎΠ΄ΠΈΡΠ΅Π»Ρ Π²ΡΠΏΠΎΠ»Π½ΡΠ΅ΡΡΡ ΡΠ΅ΡΠ΅Π· callback-ΡΡΠΊΠ½ΡΠΈΡ
- ΠΠ΅ΠΆΠ΄Ρ Π΄ΠΎΡΠ΅ΡΠ½ΠΈΠΌΠΈ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ°ΠΌΠΈ (ΡΠ΅ΡΠ΅Π· ΡΠΎΠ΄ΠΈΡΠ΅Π»ΡΡΠΊΠΈΠΉ)
- Π Π³Π»ΠΎΠ±Π°Π»ΡΠ½ΠΎ Π² ΡΠ°Π·Π»ΠΈΡΠ½ΡΠ΅ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΡ ΠΏΡΠΎΠ΅ΠΊΡΠ° (Π·Π°ΡΠ°ΡΡΡΡ ΡΠ΅ΡΠ΅Π· ΠΊΠΎΠ½ΡΠ΅ΠΊΡΡ)
ΠΠ΅ΡΠ²ΡΠΌ Π΄Π΅Π»ΠΎΠΌ, Π½ΡΠΆΠ½ΠΎ Π²ΡΠ΄Π΅Π»ΠΈΡΡ ΡΠΎΡΠΌΡ Π΄ΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΡ Π½ΠΎΠ²ΠΎΠ³ΠΎ ΠΏΠΎΡΡΠ° Π² ΠΎΡΠ΄Π΅Π»ΡΠ½ΡΠΉ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ. Π ΡΡΡ Π½Π°ΠΌ ΠΏΠΎΠ½Π°Π΄ΠΎΠ±ΠΈΡΡΡ ΡΠ΅Π°Π»ΠΈΠ·ΠΎΠ²Π°ΡΡ ΠΏΠ΅ΡΠ΅Π΄Π°ΡΡ ΠΏΡΠΎΠΏΡΠΎΠ² ΠΎΡ Π΄ΠΎΡΠ΅ΡΠ½Π΅Π³ΠΎ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠ° ΠΊ ΡΠΎΠ΄ΠΈΡΠ΅Π»ΡΡΠΊΠΎΠΌΡ.
ΠΠ΅ΡΠ΅Π΄Π°ΡΠΌ ΡΠ΅ΡΠ΅Π· create={createPost}
ΡΡΠ½ΠΊΡΠΈΡ ΠΎΡ ΡΠΎΠ΄ΠΈΡΠ΅Π»ΡΡΠΊΠΎΠ³ΠΎ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠ° ΠΊ Π΄ΠΎΡΠ΅ΡΠ½Π΅ΠΉ ΡΠΎΡΠΌΠ΅ Π½Π° Π΄ΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠ΅ ΠΏΠΎΡΡΠ°.
Π ΠΏΠ΅ΡΠ΅Π΄Π°ΡΠΌ ΡΡΠ½ΠΊΡΠΈΡ remove={removePost}
Π΄Π»Ρ ΡΠ΄Π°Π»Π΅Π½ΠΈΡ ΠΏΠΎΡΡΠ°, Π½ΠΎ ΡΠΆΠ΅ Π½Π΅ΠΏΠΎΡΡΠ΅Π΄ΡΡΠ²Π΅Π½Π½ΠΎ Π² Π°ΠΉΡΠ΅ΠΌ ΠΏΠΎΡΡΠ°
PostList.tsx
export const PostList = () => {
const [postsData, setPostsData] = useState<IPost[]>([
{ id: 'asd1', title: 'Javascript', body: 'ΠΡΡΡΠΈΠΉ ΡΠ·ΡΠΊ Π½Π° ΠΠ΅ΠΌΠ»Π΅' },
{ id: 'adsgsa2', title: 'C#', body: 'ΠΡΡΡΠΈΠΉ ΡΠ·ΡΠΊ Π½Π° ΠΠ΅ΠΌΠ»Π΅' },
{ id: 'fsdagha3', title: 'Python', body: 'ΠΡΡΡΠΈΠΉ ΡΠ·ΡΠΊ Π½Π° ΠΠ΅ΠΌΠ»Π΅' },
]);
// ΠΊΠΎΠ»Π»Π±ΡΠΊ ΡΡΠ½ΠΊΡΠΈΡ Π΄Π»Ρ ΡΠΎΠ·Π΄Π°Π½ΠΈΡ ΠΏΠΎΡΡΠ°, ΠΊΠΎΡΠΎΡΡΡ ΠΏΠ΅ΡΠ΅Π΄Π°ΡΠΌ Π² Π΄ΠΎΡΠ΅ΡΠ½ΠΈΠΉ ΡΠ»Π΅ΠΌΠ΅Π½Ρ
const createPost = (newPost: IPost): void => {
setPostsData([...postsData, newPost]);
};
// ΠΊΠΎΠ»Π»Π±ΡΠΊ ΡΡΠ½ΠΊΡΠΈΡ Π΄Π»Ρ ΡΠ΄Π°Π»Π΅Π½ΠΈΡ ΠΏΠΎΡΡΠ°, ΠΊΠΎΡΠΎΡΡΡ ΠΏΠ΅ΡΠ΅Π΄Π°ΡΠΌ Π² Π΄ΠΎΡΠ΅ΡΠ½ΠΈΠΉ ΡΠ»Π΅ΠΌΠ΅Π½Ρ
const removePost = (post: IPost): void => {
// Π² ΡΡΠ΅ΠΉΡ Π²Π΅ΡΠ½ΡΠΌ Π½ΠΎΠ²ΡΠΉ ΠΌΠ°ΡΡΠΈΠ², ΠΊΠΎΡΠΎΡΡΠΉ Π±ΡΠ΄Π΅Ρ ΠΎΡΡΠΈΠ»ΡΡΡΠΎΠ²Π°Π½ ΡΠ΅ΡΠ΅Π· filter
setPostsData(postsData.filter(p => p.id !== post.id));
};
return (
<div className={styles.wrapper}>
<PostForm create={createPost} />
<div className={styles.list}>
{postsData.map(p => (
<PostItem remove={removePost} key={p.id} post={p} />
))}
</div>
</div>
);
};
Π’ΡΡ ΠΏΡΠ΅Π΄ΡΡΠ°Π²Π»Π΅Π½ ΠΈΠ½ΡΠ΅ΡΡΠ΅ΠΉΡ ΠΏΠΎΡΡΠ° Π² ΠΎΡΠ΄Π΅Π»ΡΠ½ΠΎΠΌ ΡΠ°ΠΉΠ»Π΅
PostList.interface.ts
export interface IPost {
id: string;
title: string;
body: string;
}
Π‘Π΅ΠΉΡΠ°Ρ Π½ΡΠΆΠ½ΠΎ ΡΠ°Π·Π±ΠΈΡΡ Π»ΠΎΠ³ΠΈΠΊΡ ΡΠ°ΠΊ, ΡΡΠΎΠ±Ρ ΠΌΠΎΠΆΠ½ΠΎ Π±ΡΠ»ΠΎ ΠΏΠ΅ΡΠ΅Π΄Π°ΡΡ Π²Π½ΡΡΡΡ Π΄ΠΎΡΠ΅ΡΠ½Π΅Π³ΠΎ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ° ΡΡΠ½ΠΊΡΠΈΡ ΠΎΡ ΡΠΎΠ΄ΠΈΡΠ΅Π»ΡΡΠΊΠΎΠ³ΠΎ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ°
PostForm.props.ts
import { DetailedHTMLProps, HTMLAttributes, ReactNode } from 'react';
import { IPost } from '@/page-components/PostList/PostList.interface';
export interface PostFormProps
extends DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
create: (newPost: IPost) => void;
}
Π£ΠΆΠ΅ ΡΠ°ΠΌ ΠΎΡΠ΄Π΅Π»ΡΠ½ΡΠΉ ΡΠ»Π΅ΠΌΠ΅Π½Ρ Π±ΡΠ΄Π΅Ρ Π² ΡΠ΅Π±Ρ ΠΏΡΠΈΠ½ΠΈΠΌΠ°ΡΡ ΡΡΠ½ΠΊΡΠΈΡ ΡΠ΄Π°Π»Π΅Π½ΠΈΡ ΠΏΠΎΡΡΠ° ΠΏΠΎ ΠΏΠΎΠ»ΡΡΠ΅Π½Π½ΠΎΠΌΡ ΠΏΠΎΡΡΡ ΠΈ ΡΠ°ΠΌ ΠΏΠΎΡΡ
PostItem.props.ts
import { DetailedHTMLProps, HTMLAttributes, ReactNode } from 'react';
import { IPost } from '@/page-components/PostList/PostList.interface';
export interface PostItemProps
extends DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
post: IPost;
remove: (post: IPost) => void;
}
ΠΠ°Π»Π΅Π΅ ΡΡΡ Π²ΡΠ·Π²Π°Π΅ΠΌ ΡΡΠ½ΠΊΡΠΈΡ ΠΈΠ· ΡΠΎΠ΄ΠΈΡΠ΅Π»ΡΡΠΊΠΎΠ³ΠΎ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ° create()
Π² Π΄ΠΎΡΠ΅ΡΠ½Π΅ΠΌ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠ΅
PostForm.tsx
import React, { useState } from 'react';
import styles from './PostForm.module.scss';
import { Input } from '@/components/Input/Input';
import { Button } from '@/components';
import { PostFormProps } from '@/components/PostForm/PostForm.props';
export const PostForm = ({ create }: PostFormProps) => {
const [post, setPost] = useState<{ title: string; body: string }>({
title: '',
body: '',
});
const addNewPost = (event: any): void => {
event.preventDefault();
const newPost = {
...post,
id: `${Date.now()}`,
};
// Π²ΡΠ·ΡΠ²Π°Π΅ΠΌ ΡΡΠ½ΠΊΡΠΈΡ ΡΠΎΠ΄ΠΈΡΠ΅Π»Ρ, Π² ΠΊΠΎΡΠΎΡΡΡ ΠΏΠ΅ΡΠ΅Π΄Π°ΡΠΌ Π½ΠΎΠ²ΡΠΉ ΠΏΠΎΡΡ
create(newPost);
setPost({
title: '',
body: '',
});
};
return (
<div className={styles.formBlock}>
<form className={styles.form}>
<Input
value={post.title}
onChange={e => setPost({ ...post, title: e.target.value })}
className={styles.form__input}
type='text'
placeholder={'ΠΠ°Π·Π²Π°Π½ΠΈΠ΅ ΠΏΠΎΡΡΠ°'}
/>
<Input
value={post.body}
onChange={e => setPost({ ...post, body: e.target.value })}
className={styles.form__input}
type='text'
placeholder={'ΠΠΏΠΈΡΠ°Π½ΠΈΠ΅ ΠΏΠΎΡΡΠ°'}
/>
<Button className={styles.form__button} buttonType={'purple'} onClick={addNewPost}>
ΠΠΎΠ±Π°Π²ΠΈΡΡ ΠΏΠΎΡΡ
</Button>
</form>
</div>
);
};
ΠΠ°Π»Π΅Π΅ ΠΏΠ΅ΡΠ΅Ρ
ΠΎΠ΄ΠΈΠΌ Π² ΡΠ»Π΅ΠΌΠ΅Π½Ρ ΠΎΡΠ΄Π΅Π»ΡΠ½ΠΎΠ³ΠΎ ΠΏΠΎΡΡΠ°. Π’ΡΡ ΠΊΠ½ΠΎΠΏΠΊΠΎΠΉ Π²ΡΠ·ΡΠ²Π°Π΅ΠΌ ΡΡΠ½ΠΊΡΠΈΡ ΡΠ΄Π°Π»Π΅Π½ΠΈΡ ΠΏΠΎΡΡΠ°, ΠΏΠ΅ΡΠ΅Π΄Π°Π²Π°Ρ Π² Π½Π΅Ρ ΠΏΠΎΠ»Π½ΡΠΉ ΠΏΠΎΡΡ: () => remove(post)
PostItem.tsx
export const PostItem = ({ remove, post, className, ...props }: PostItemProps) => {
return (
<div className={cn(styles.wrapper, className)} {...props}>
<div className={styles.post}>
<div className={styles.post__content}>
<h2>{post.title}</h2>
<Paragraph size={'l'}>{post.body}</Paragraph>
</div>
<Button
onClick={() => remove(post)}
buttonType={'purple'}
className={styles.post__button}
>
Π£Π΄Π°Π»ΠΈΡΡ ΠΏΠΎΡΡ
</Button>
</div>
</div>
);
};
ΠΠΎΠ²ΡΠ΅ ΠΏΠΎΡΡΡ Π²ΡΡ ΡΠ°ΠΊ ΠΆΠ΅ Π΄ΠΎΠ±Π°Π²Π»ΡΡΡΡΡ!
Π£Π΄Π°Π»Π΅Π½ΠΈΠ΅ ΠΏΠΎΡΡΠ° ΡΠ°ΠΊ ΠΆΠ΅ ΡΠ°Π±ΠΎΡΠ°Π΅Ρ:
01:04:20 β ΠΡΡΠΈΡΠΎΠ²ΠΊΠ° ΠΏΠΎ ΡΡΠ»ΠΎΠ²ΠΈΡ
ΠΡΡΠΈΡΠΎΠ²ΠΊΠ° ΠΏΠΎ ΡΡΠ»ΠΎΠ²ΠΈΡ Π²ΡΠΏΠΎΠ»Π½ΡΠ΅ΡΡΡ ΠΊΡΠ°ΠΉΠ½Π΅ ΠΏΡΠΎΡΡΠΎ - ΡΠ΅ΡΠ΅Π· ΡΠ΅ΡΠ½Π°ΡΠ½ΡΠΉ ΠΎΠΏΠ΅ΡΠ°ΡΠΎΡ:
PostList.tsx
return (
<div className={styles.wrapper}>
<PostForm create={createPost} />
<div className={styles.list}>
{postsData.length ? (
postsData.map(p => <PostItem remove={removePost} key={p.id} post={p} />)
) : (
<h2 style={{ textAlign: 'center' }}>ΠΠΎΡΡΡ Π½Π΅ Π΄ΠΎΠ±Π°Π²Π»Π΅Π½Ρ</h2>
)}
</div>
</div>
);
ΠΡΠΈ ΡΠ΄Π°Π»Π΅Π½ΠΈΠΈ Π²ΡΠ΅Ρ ΠΏΠΎΡΡΠΎΠ², Ρ Π½Π°Ρ Π²ΡΠ»Π΅Π·Π΅Ρ Π½Π°Π΄ΠΏΠΈΡΡ:
01:05:30 β Π‘ΠΎΡΡΠΈΡΠΎΠ²ΠΊΠ°. ΠΡΠΏΠ°Π΄Π°ΡΡΠΈΠΉ ΡΠΏΠΈΡΠΎΠΊ
ΠΠ΅ΡΠ²ΡΠΌ Π΄Π΅Π»ΠΎΠΌ, Π½ΡΠΆΠ½ΠΎ ΡΠ΅Π°Π»ΠΈΠ·ΠΎΠ²Π°ΡΡ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ ΡΠΎΡΡΠΈΡΠΎΠ²ΠΊΠΈ. ΠΠ½ Π±ΡΠ΄Π΅Ρ Π² ΡΠ΅Π±Ρ ΠΏΡΠΈΠ½ΠΈΠΌΠ°ΡΡ ΠΌΠ°ΡΡΠΈΠ² ΠΎΠΏΡΠΈΠΉ, Π΄Π΅ΡΠΎΠ»ΡΠ½ΡΡ ΠΎΠΏΡΠΈΡ ΠΈ ΠΎΠΏΡΠ΅Π΄Π΅Π»ΡΠ½Π½ΠΎΠ΅ Π²ΡΠ±ΡΠ°Π½Π½ΠΎΠ΅ Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Π΅ΠΌ
Select.props.ts
import { DetailedHTMLProps, HTMLAttributes } from 'react';
export interface SelectProps
extends DetailedHTMLProps<HTMLAttributes<HTMLSelectElement>, HTMLSelectElement> {
options: { value: string; name: string }[];
defaultValue: string;
value: string;
}
ΠΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ ΡΠ΅Π»Π΅ΠΊΡΠ° Π²ΡΠ²ΠΎΠ΄ΠΈΡ ΠΌΠ°ΡΡΠΈΠ² ΠΏΠ΅ΡΠ΅Π΄Π°Π½Π½ΡΡ
Π² Π½Π΅Π³ΠΎ ΠΎΠΏΡΠΈΠΉ ΠΈ ΡΡΠΈΠ³Π³Π΅ΡΠΈΡ ΠΏΠ΅ΡΠ΅Π΄Π°Π½Π½ΡΡ Π² Π½Π΅Π³ΠΎ ΡΡΠ½ΠΊΡΠΈΡ onChange
ΠΏΡΠΈ Π²ΡΠ±ΠΎΡΠ΅ ΠΎΠΏΡΠ΅Π΄Π΅Π»ΡΠ½Π½ΠΎΠ³ΠΎ ΡΠ΅Π»Π΅ΠΊΡΠ°
Select.tsx
import React from 'react';
import cn from 'classnames';
import styles from './Select.module.scss';
import { SelectProps } from '@/components/Select/Select.props';
export const Select = ({
defaultValue,
options,
className,
value,
onChange,
...props
}: SelectProps): JSX.Element => {
return (
<select
value={value}
onChange={event => onChange(event.target.value)}
className={styles.select}
>
<option disabled value=''>
{defaultValue}
</option>
{options.map(option => (
<option key={option.value} value={option.value}>
{option.name}
</option>
))}
</select>
);
};
Π Π³Π»Π°Π²Π½ΠΎΠΌ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ΅ ΠΌΡ ΡΠΎΠ·Π΄Π°Π»ΠΈ ΡΡΠ½ΠΊΡΠΈΡ sortPosts
, ΠΊΠΎΡΠΎΡΡΡ ΠΈ ΠΏΠ΅ΡΠ΅Π΄Π°ΡΠΌ Π² Π΄ΠΎΡΠ΅ΡΠ½ΠΈΠΉ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ ΡΠ΅Π»Π΅ΠΊΡΠ°. ΠΠ½ΡΡΡΠΈ ΡΡΠ½ΠΊΡΠΈΠΈ ΠΌΡ ΡΡΡΠ°Π½Π°Π²Π»ΠΈΠ²Π°Π΅ΠΌ ΡΠΈΠΏ ΡΠ΅Π»Π΅ΠΊΡΠ΅Π½Π° setSelectedSort
ΠΈ ΠΏΡΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΠΌ ΡΡΠ°Π²Π½Π΅Π½ΠΈΠ΅ ΡΠΏΠΈΡΠΊΠΎΠ² ΡΠ΅ΡΠ΅Π· localeCompare
.
PostList.tsx
export const PostList = () => {
const [postsData, setPostsData] = useState<IPost[]>([
{ id: 'asd1', title: 'Javascript', body: 'ΠΡΡΡΠΈΠΉ ΡΠ·ΡΠΊ Π½Π° ΠΠ΅ΠΌΠ»Π΅' },
{ id: 'adsgsa2', title: 'C#', body: 'Π₯ΡΠΎΡΠΈΠΉ ΡΠ·ΡΠΊ' },
{ id: 'fsdagha3', title: 'Python', body: 'ΠΠΎΡΠ΅ΠΌΡ Π±Ρ ΠΈ Π½Π΅Ρ?' },
]);
// ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ Π΄Π»Ρ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠ° ΡΠΎΡΡΠΈΡΠΎΠ²ΠΊΠΈ select
const [selectedSort, setSelectedSort] = useState<'title' | 'body' | ''>('');
// ΠΊΠΎΠ»Π»Π±ΡΠΊ ΡΡΠ½ΠΊΡΠΈΡ Π΄Π»Ρ ΡΠΎΠ·Π΄Π°Π½ΠΈΡ ΠΏΠΎΡΡΠ°, ΠΊΠΎΡΠΎΡΡΡ ΠΏΠ΅ΡΠ΅Π΄Π°ΡΠΌ Π² Π΄ΠΎΡΠ΅ΡΠ½ΠΈΠΉ ΡΠ»Π΅ΠΌΠ΅Π½Ρ
const createPost = (newPost: IPost): void => {
setPostsData([...postsData, newPost]);
};
// ΠΊΠΎΠ»Π»Π±ΡΠΊ ΡΡΠ½ΠΊΡΠΈΡ Π΄Π»Ρ ΡΠ΄Π°Π»Π΅Π½ΠΈΡ ΠΏΠΎΡΡΠ°, ΠΊΠΎΡΠΎΡΡΡ ΠΏΠ΅ΡΠ΅Π΄Π°ΡΠΌ Π² Π΄ΠΎΡΠ΅ΡΠ½ΠΈΠΉ ΡΠ»Π΅ΠΌΠ΅Π½Ρ
const removePost = (post: IPost): void => {
// Π² ΡΡΠ΅ΠΉΡ Π²Π΅ΡΠ½ΡΠΌ Π½ΠΎΠ²ΡΠΉ ΠΌΠ°ΡΡΠΈΠ², ΠΊΠΎΡΠΎΡΡΠΉ Π±ΡΠ΄Π΅Ρ ΠΎΡΡΠΈΠ»ΡΡΡΠΎΠ²Π°Π½ ΡΠ΅ΡΠ΅Π· filter
setPostsData(postsData.filter(p => p.id !== post.id));
};
// ΡΡΠ½ΠΊΡΠΈΡ Π΄Π»Ρ ΡΠΎΡΡΠΈΡΠΎΠ²ΠΊΠΈ ΠΏΠΎΡΡΠΎΠ²
const sortPosts = (sort: 'title' | 'body'): void => {
setSelectedSort(sort);
// ΡΡΡ ΠΌΡ ΡΠΎΡΡΠΈΡΡΠ΅ΠΌ ΠΌΠ°ΡΡΠΈΠ², Π½Π΅ ΠΌΡΡΠΈΡΡΡ Π΅Π³ΠΎ ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ Π½Π°ΠΏΡΡΠΌΡΡ
setPostsData([...postsData].sort((a, b) => a[sort].localeCompare(b[sort])));
};
return (
<div className={styles.wrapper}>
<PostForm create={createPost} />
<Divider />
<Select
value={selectedSort}
onChange={sortPosts}
defaultValue={'Π‘ΠΎΡΡΠΈΡΠΎΠ²ΠΊΠ°'}
options={[
{ value: 'title', name: 'ΠΠΎ Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΡ' },
{ value: 'body', name: 'ΠΠΎ ΠΎΠΏΠΈΡΠ°Π½ΠΈΡ' },
]}
/>
<div className={styles.list}>
{postsData.length ? (
postsData.map(p => <PostItem remove={removePost} key={p.id} post={p} />)
) : (
<h2 style={{ textAlign: 'center' }}>ΠΠΎΡΡΡ Π½Π΅ Π΄ΠΎΠ±Π°Π²Π»Π΅Π½Ρ</h2>
)}
</div>
</div>
);
};
ΠΠ±Π° Π²ΠΈΠ΄Π° ΡΠΎΡΡΠΈΡΠΎΠ²ΠΊΠΈ:
01:15:10 β useMemo. ΠΠ΅ΠΌΠΎΠΈΠ·Π°ΡΠΈΡ. ΠΠ΅ΡΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅. ΠΠΎΠΈΡΠΊ. Π€ΠΈΠ»ΡΡΡΠ°ΡΠΈΡ.
Π₯ΡΠΊ useMemo
Π²ΠΎΠ·Π²ΡΠ°ΡΠ°Π΅Ρ ΠΌΠ΅ΠΌΠΎΠΈΠ·ΠΈΡΠΎΠ²Π°Π½Π½ΠΎΠ΅ Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅.
ΠΠ΅ΡΠ²ΡΠΌ ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡΠΎΠΌ Ρ ΡΠΊ ΠΏΡΠΈΠ½ΠΈΠΌΠ°Π΅Ρ Π² ΡΠ΅Π±Ρ ΡΡΠ½ΠΊΡΠΈΡ, ΠΊΠΎΡΠΎΡΠ°Ρ Π²ΡΡΡΠΈΡΡΠ²Π°Π΅Ρ ΠΎΠΏΡΠ΅Π΄Π΅Π»ΡΠ½Π½ΠΎΠ΅ Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅. ΠΡΠΎΡΡΠΌ ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡΠΎΠΌ ΠΏΡΠΈΠ½ΠΈΠΌΠ°Π΅Ρ Π² ΡΠ΅Π±Ρ ΠΌΠ°ΡΡΠΈΠ² Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠ΅ΠΉ. ΠΡΠ»ΠΈ ΠΊΠ°ΠΊΠ°Ρ-Π»ΠΈΠ±ΠΎ ΠΈΠ· Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠ΅ΠΉ ΠΈΠ·ΠΌΠ΅Π½ΠΈΠ»Π°ΡΡ, ΡΠΎ Ρ ΡΠΊ Π·Π°Π½ΠΎΠ²ΠΎ ΠΏΠ΅ΡΠ΅ΡΡΠΈΡΡΠ²Π°Π΅Ρ Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅.
ΠΡΠ»ΠΈ ΠΌΠ°ΡΡΠΈΠ² Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠ΅ΠΉ Π½Π΅Β Π±ΡΠ» ΠΏΠ΅ΡΠ΅Π΄Π°Π½, Π½ΠΎΠ²ΠΎΠ΅ Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅ Π±ΡΠ΄Π΅Ρ Π²ΡΡΠΈΡΠ»ΡΡΡΡΡ ΠΏΡΠΈ ΠΊΠ°ΠΆΠ΄ΠΎΠΌ ΡΠ΅Π½Π΄Π΅ΡΠ΅.
ΠΡΠ° ΠΎΠΏΡΠΈΠΌΠΈΠ·Π°ΡΠΈΡ ΠΏΠΎΠΌΠΎΠ³Π°Π΅Ρ ΠΈΠ·Π±Π΅ΠΆΠ°ΡΡ Π΄ΠΎΡΠΎΠ³ΠΎΡΡΠΎΡΡΠΈΡ Π²ΡΡΠΈΡΠ»Π΅Π½ΠΈΠΉ ΠΏΡΠΈ ΠΊΠ°ΠΆΠ΄ΠΎΠΌ ΡΠ΅Π½Π΄Π΅ΡΠ΅.
ΠΡΠΆΠ½ΠΎ ΠΏΠΎΠΌΠ½ΠΈΡΡ, ΡΡΠΎ ΡΡΠ½ΠΊΡΠΈΡ, ΠΏΠ΅ΡΠ΅Π΄Π°Π½Π½Π°ΡΒ useMemo
, Π·Π°ΠΏΡΡΠΊΠ°Π΅ΡΡΡ Π²ΠΎΒ Π²ΡΠ΅ΠΌΡ ΡΠ΅Π½Π΄Π΅ΡΠΈΠ½Π³Π°. ΠΠ΅ Π½ΡΠΆΠ½ΠΎΒ Π΄Π΅Π»Π°ΡΡ ΡΠ°ΠΌ Π½ΠΈΡΠ΅Π³ΠΎ, ΡΡΠΎ ΠΎΠ±ΡΡΠ½ΠΎ Π½Π΅Β Π΄Π΅Π»Π°Π΅ΡΡΡ Π²ΠΎΒ Π²ΡΠ΅ΠΌΡ ΡΠ΅Π½Π΄Π΅ΡΠΈΠ½Π³Π°. ΠΠ°ΠΏΡΠΈΠΌΠ΅Ρ, Π²ΡΠ΅ ΠΏΠΎΠ±ΠΎΡΠ½ΡΠ΅ ΡΡΡΠ΅ΠΊΡΡ Π΄ΠΎΠ»ΠΆΠ΅Π½ Π²ΡΠΏΠΎΠ»Π½ΡΡΡΒ useEffect
, Π°Β Π½Π΅Β useMemo
.
example:
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
ΠΠ°Π»Π΅Π΅ ΡΡΡΡΠΊΡΡΡΠ° ΠΏΡΠΎΠ΅ΠΊΡΠ° Π±ΡΠ»Π° Π½Π΅ΠΌΠ½ΠΎΠ³ΠΎ ΡΠ΅ΠΎΡΠ³Π°Π½ΠΈΠ·ΠΎΠ²Π°Π½Π° ΠΈ ΡΠ΅ΠΏΠ΅ΡΡ Π³Π»Π°Π²Π½ΡΠΌ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠΎΠΌ Π±ΡΠ΄Π΅Ρ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ ΡΡΡΠ°Π½ΠΈΡΡ
Posts.tsx
Π ΡΠ°ΠΌΠΎΠΌ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ΅ Posts.tsx
ΠΌΡ ΡΠΎΠ·Π΄Π°Π΄ΠΈΠΌ ΠΎΠ±ΡΠ΅Π΅ ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ ΠΏΠΎΠ΄ ΠΏΠΎΠΈΡΠΊ, Π³Π΄Π΅ filter
Π±ΡΠ΄Π΅Ρ ΠΎΡΠ²Π΅ΡΠ°ΡΡ Π·Π° ΡΡΡΠΎΠΊΡ Π·Π°ΠΏΡΠΎΡΠ° query
ΠΈ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ ΡΠ΅Π»Π΅ΠΊΡΠ° sort
.
ΠΠ°Π»Π΅Π΅ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΡΡΡ Π΄Π²Π΅ ΡΡΠ½ΠΊΡΠΈΠΈ sortPosts
ΠΈ sortedAndSearchedPosts
, ΠΊΠΎΡΠΎΡΡΠ΅ ΡΠΈΠ»ΡΡΡΡΡΡ ΠΌΠ°ΡΡΠΈΠ² (ΠΏΠ΅ΡΠ²ΡΠΉ ΠΏΠΎ ΡΠ΅Π»Π΅ΠΊΡΡ, Π²ΡΠΎΡΠΎΠΉ ΠΏΠΎ Π·Π°ΠΏΡΠΎΡΡ) ΠΈ ΠΊΠ΅ΡΠΈΡΡΡΡ ΡΡΡ ΡΠΎΡΡΠΈΡΠΎΠ²ΠΊΡ. Π€ΡΠ½ΠΊΡΠΈΡ sortPosts
ΡΠ΄Π°Π»Π΅Π½Π°, ΡΠ°ΠΊ ΠΊΠ°ΠΊ Π΅Ρ Π·Π°ΠΌΠ΅Π½ΡΡΡ Π΄Π²Π΅ Π²ΡΡΠ΅ΠΎΠΏΠΈΡΠ°Π½Π½ΡΠ΅ ΡΡΠ½ΠΊΡΠΈΠΈ.
ΠΠ°Π»Π΅Π΅ ΠΌΡ Π²ΡΠ·Π²Π°Π΅ΠΌ ΡΡΠΈ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ°: ΡΠΎΡΠΌΡ, ΡΠΈΠ»ΡΡΡ ΠΈ Π³Π΅Π½Π΅ΡΠ°ΡΠΈΡ ΡΠΏΠΈΡΠΊΠ° ΠΏΠΎΡΡΠΎΠ².
page-components / Posts.tsx
import React, { useMemo, useRef, useState } from 'react';
import styles from './Posts.module.scss';
import { PostFilter } from '@/components';
import { PostForm } from '@/components';
import { PostList } from '@/components';
import { IPost } from '@/page-components/Posts/Posts.interface';
import { IFilter } from '@/components/PostFilter/PostFilter.props';
export const Posts = () => {
const [posts, setPosts] = useState<IPost[]>([
{ id: 'asd1', title: 'Javascript', body: 'ΠΡΡΡΠΈΠΉ ΡΠ·ΡΠΊ Π½Π° ΠΠ΅ΠΌΠ»Π΅' },
{ id: 'adsgsa2', title: 'C#', body: 'Π₯ΡΠΎΡΠΈΠΉ ΡΠ·ΡΠΊ' },
{ id: 'fsdagha3', title: 'Python', body: 'ΠΠΎΡΠ΅ΠΌΡ Π±Ρ ΠΈ Π½Π΅Ρ?' },
]);
// ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ ΡΠ΅Π»Π΅ΠΊΡΠ° ΠΈ ΡΡΡΠΎΠΊΠΈ ΠΏΠΎΠΈΡΠΊΠ°
const [filter, setFilter] = useState<IFilter>({ query: '', sort: 'title' });
// ΠΏΠΎΠ»ΡΡΠ°Π΅ΠΌ ΠΎΡΡΠΎΡΡΠΈΡΠΎΠ²Π°Π½Π½ΡΠΉ ΠΌΠ°ΡΡΠΈΠ²
const sortedPosts = useMemo<IPost[]>(() => {
return [...posts].sort((a, b) => a[filter.sort].localeCompare(b[filter.sort]));
}, [filter.sort, posts]);
// ΡΠΎΡΡΠΈΡΡΠ΅ΠΌ ΠΌΠ°ΡΡΠΈΠ² ΠΏΠΎ ΡΡΡΠΎΠΊΠ΅ ΠΏΠΎΠΈΡΠΊΠ°
const sortedAndSearchedPosts = useMemo<IPost[]>(() => {
return sortedPosts.filter(post =>
post.title.toLowerCase().includes(filter.query.toLowerCase()),
);
}, [filter.query, sortedPosts]);
const createPost = (newPost: IPost): void => {
setPosts([...posts, newPost]);
};
const removePost = (post: IPost): void => {
setPosts(posts.filter(p => p.id !== post.id));
};
return (
<div className={styles.wrapper}>
<PostForm create={createPost} />
<PostFilter filter={filter} setFilter={setFilter} />
<PostList className={styles.list} posts={sortedAndSearchedPosts} remove={removePost} />
</div>
);
};
ΠΠ°Π»Π΅Π΅ ΠΈΠ΄ΡΡ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ PostList
, ΠΊΠΎΡΠΎΡΡΠΉ ΠΏΡΠΈΠ½ΠΈΠΌΠ°Π΅Ρ Π² ΡΠ΅Π±Ρ ΡΡΠ½ΠΊΡΠΈΡ Π΄Π»Ρ ΡΠ΄Π°Π»Π΅Π½ΠΈΡ ΠΏΠΎΡΡΠ° ΠΈ ΠΌΠ°ΡΡΠΈΠ² ΠΏΠΎΡΡΠΎΠ².
components / PostList / PostList.props.ts
import { DetailedHTMLProps, HTMLAttributes, ReactNode } from 'react';
import { IPost } from '@/page-components/Posts/Posts.interface';
export interface PostListProps
extends DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
remove: (post: IPost) => void;
posts: IPost[];
}
Π’ΡΡ Π²ΡΠ²ΠΎΠ΄ΠΈΡΡΡ ΡΠΏΠΈΡΠΎΠΊ ΠΏΠΎΡΡΠΎΠ², Π² ΠΊΠ°ΠΆΠ΄ΡΠΉ ΠΈΠ· ΠΊΠΎΡΠΎΡΡΡ ΠΏΠ΅ΡΠ΅Π΄Π°ΡΡΡΡ ΡΠ²ΠΎΡ ΡΡΠ½ΠΊΡΠΈΡ Π΄Π»Ρ ΠΈΡ ΡΠ΄Π°Π»Π΅Π½ΠΈΡ
components / PostList / PostList.tsx
export const PostList = ({ remove, posts, className, ...props }: PostListProps) => {
return (
<div className={cn(styles.wrapper, className)} {...props}>
{/* Π²ΡΠ²ΠΎΠ΄ΠΈΠΌ ΠΏΠΎΠ»Π½ΠΎΡΡΡΡ ΠΎΡΡΠΈΠ»ΡΡΡΠΎΠ²Π°Π½Π½ΡΠΉ ΠΊΠΎΠ½Π΅ΡΠ½ΡΠΉ ΠΌΠ°ΡΡΠΈΠ² */}
{posts.length ? (
posts.map(p => <PostItem remove={remove} key={p.id} post={p} />)
) : (
<h2 style={{ textAlign: 'center' }}>ΠΠΎΡΡΡ Π½Π΅ Π΄ΠΎΠ±Π°Π²Π»Π΅Π½Ρ</h2>
)}
</div>
);
};
ΠΠ°Π»Π΅Π΅ ΠΈΠ΄ΡΡ ΡΠΈΠ»ΡΡΡ ΠΏΠΎΡΡΠΎΠ², ΠΊΠΎΡΠΎΡΡΠΉ Π² ΡΠ΅Π±Ρ ΠΏΡΠΈΠ½ΠΈΠΌΠ°Π΅Ρ ΠΏΡΠΎΠΏΡ ΡΠΈΠ»ΡΡΡΠ° ΠΏΠΎ ΠΈΠ½ΡΠ΅ΡΡΠ΅ΠΉΡΡ IFilter
ΠΈ ΡΡΠ½ΠΊΡΠΈΡ setFilter
, ΠΊΠΎΡΠΎΡΠ°Ρ ΡΡΡΠ°Π½Π°Π²Π»ΠΈΠ²Π°Π΅Ρ Π½ΠΎΠ²ΡΠΉ ΡΠΈΠ»ΡΡΡ.
Π‘Π°ΠΌ ΠΈΠ½ΡΠ΅ΡΡΠ΅ΠΉΡ IFilter
ΠΏΡΠ΅Π΄ΡΡΠ°Π²Π»ΡΠ΅Ρ ΠΈΠ· ΡΠ΅Π±Ρ ΠΈΠ½ΡΠ΅ΡΡΠ΅ΠΉΡ ΡΡΠ΅ΠΉΡΠ° ΡΠΈΠ»ΡΡΡΠ° ΠΈΠ· Π³Π»Π°Π²Π½ΠΎΠ³ΠΎ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ°
components / PostFilter / PostFilter.props.ts
import { DetailedHTMLProps, HTMLAttributes, ReactNode } from 'react';
import { IPost } from '@/page-components/Posts/Posts.interface';
export interface IPostFilterProps
extends DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
filter: IFilter;
setFilter: (filter: IFilter) => void;
}
export interface IFilter {
sort: 'title' | 'body';
query: string;
}
ΠΠ°Π½Π½ΡΠΉ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ Π²ΡΠΏΠΎΠ»Π½ΡΠ΅Ρ ΡΠΈΠ»ΡΡΡΠ°ΡΠΈΡ ΠΏΠΎ ΠΈΠ½ΠΏΡΡΡ ΠΈ ΠΏΠΎ ΡΠ΅Π»Π΅ΠΊΡΡ
components / PostFilter / PostFilter.tsx
export const PostFilter = ({ filter, setFilter }: IPostFilterProps) => {
return (
<div className={styles.wrapper}>
<Input
className={styles.search}
placeholder={'ΠΠΎΠΈΡΠΊ...'}
value={filter.query}
onChange={e => setFilter({ ...filter, query: e.target.value })}
/>
<Divider />
<Select
value={filter.sort}
onChange={(selectedSort: 'title' | 'body') =>
setFilter({ ...filter, sort: selectedSort })
}
defaultValue={'Π‘ΠΎΡΡΠΈΡΠΎΠ²ΠΊΠ°'}
options={[
{ value: 'title', name: 'ΠΠΎ Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΡ' },
{ value: 'body', name: 'ΠΠΎ ΠΎΠΏΠΈΡΠ°Π½ΠΈΡ' },
]}
/>
</div>
);
};
Π‘Π΄Π΅Π»Π°Π΅ΠΌ ΠΏΠΎΠΈΡΠΊ ΠΏΠΎ ΡΠΈΡΠΈΠ½Π΅ ΡΠΊΡΠ°Π½Π°
components / PostFilter / PostFilter.module.css
.search {
width: 100%;
}
ΠΡΠΎΠ³: ΡΠ΅ΠΏΠ΅ΡΡ ΡΠ°Π±ΠΎΡΠ°Π΅Ρ ΠΏΠΎΠΈΡΠΊ ΠΏΠΎ ΡΡΡΠΎΠΊΠ΅ Π·Π°ΠΏΡΠΎΡΠ°
01:23:50 β ΠΠΎΠ΄Π°Π»ΡΠ½ΠΎΠ΅ ΠΎΠΊΠ½ΠΎ. ΠΠ΅ΡΠ΅ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΠΌΡΠΉ UI ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ
Π§ΡΠΎΠ±Ρ Π΄ΠΎΠ±Π°Π²ΠΈΡΡ Π΅ΡΡ ΠΎΠ΄ΠΈΠ½ ΡΡΠΈΠ»Ρ ΠΈΠ»ΠΈ Π½Π΅ΡΠΊΠΎΠ»ΡΠΊΠΎ ΡΡΠΈΠ»Π΅ΠΉ, ΠΌΠΎΠΆΠ½ΠΎ Π²ΠΎΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡΡΡ ΡΠ°ΠΊΠΎΠΉ ΠΊΠΎΠ½ΡΡΡΡΠΊΡΠΈΠ΅ΠΉ: [ΠΌΠ°ΡΡΠΈΠ²_ΠΊΠ»Π°ΡΡΠΎΠ²].join(' ')
, Π³Π΄Π΅ ΠΌΡ ΠΎΠ±ΡΠ΅Π΄ΠΈΠ½ΡΠ΅ΠΌ Π²Π΅ΡΡ ΠΌΠ°ΡΡΠΈΠ² ΡΠ΅ΡΠ΅Π· ΡΡΠ½ΠΊΡΠΈΡ join()
ΠΡΠ»ΠΈ Π½Π°ΠΌ Π½ΡΠΆΠ½ΠΎ Π΄ΠΎΠ±Π°Π²ΠΈΡΡ ΠΊΠ»Π°ΡΡ ΠΏΠΎ ΡΡΠ»ΠΎΠ²ΠΈΡ, ΡΠΎ ΠΌΠΎΠΆΠ½ΠΎ ΡΠΎΠ·Π΄Π°ΡΡ ΡΠ°ΠΊΡΡ ΠΏΡΠΎΠ²Π΅ΡΠΊΡ:
ΠΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ ΠΌΠΎΠ΄Π°Π»ΡΠ½ΠΎΠ³ΠΎ ΠΎΠΊΠ½Π° ΠΏΡΠΈΠ½ΠΈΠΌΠ°Π΅Ρ Π² ΡΠ΅Π±Ρ Π΄ΠΎΡΠ΅ΡΠ½ΠΈΠ΅ ΡΠ»Π΅ΠΌΠ΅Π½ΡΡ, ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ Π²ΠΈΠ΄ΠΈΠΌΠΎΡΡΠΈ ΠΈ ΡΡΠ½ΠΊΡΠΈΡ ΡΡΡΠ°Π½ΠΎΠ²ΠΊΠΈ ΡΡΠΎΠΉ Π²ΠΈΠ΄ΠΈΠΌΠΎΡΡΠΈ
components / Modal / Modal.props.ts
import { DetailedHTMLProps, HTMLAttributes, ReactNode } from 'react';
export interface IModalProps
extends DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
children: ReactNode;
visible: boolean;
setVisible: (visible: boolean) => void;
}
Π’ΡΡ ΡΠΆΠ΅ ΠΏΡΠ΅Π΄ΡΡΠ°Π²Π»Π΅Π½ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ Modal
, ΠΊΠΎΡΠΎΡΡΠΉ ΠΈ ΡΠ΅Π°Π»ΠΈΠ·ΡΠ΅Ρ ΠΏΠΎΠ΄ ΡΠΎΠ±ΠΎΠΉ ΠΎΡΠΊΡΡΡΠΈΠ΅ ΠΌΠΎΠ΄Π°Π»ΡΠ½ΠΎΠ³ΠΎ ΠΎΠΊΠ½Π° ΠΏΠΎ ΡΠΎΡΡΠΎΡΠ½ΠΈΡ, ΠΏΠ΅ΡΠ΅Π΄Π°Π½Π½ΠΎΠΌΡ ΠΎΡ ΡΠΎΠ΄ΠΈΡΠ΅Π»Ρ.
Π’Π°ΠΊ ΠΆΠ΅ ΡΡΡ ΠΏΠΎΠΊΠ°Π·Π°Π½ΠΎ, ΠΊΠ°ΠΊ ΠΌΠΎΠΆΠ½ΠΎ Π²ΠΎΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡΡΡ ΡΡΠ½ΠΊΡΠΈΠ΅ΠΉ cn()
ΠΈΠ· Π²Π½Π΅ΡΠ½Π΅Π³ΠΎ ΠΌΠΎΠ΄ΡΠ»Ρ classnames
, ΠΊΠΎΡΠΎΡΠ°Ρ ΡΠΏΡΠΎΡΡΠΈΡ Π²Π·Π°ΠΈΠΌΠΎΠ΄Π΅ΠΉΡΡΠ²ΠΈΠ΅ Ρ Π½Π°Π²Π΅ΡΠΈΠ²Π°Π½ΠΈΠ΅ΠΌ ΠΊΠ»Π°ΡΡΠΎΠ² Π½Π° ΡΠ»Π΅ΠΌΠ΅Π½ΡΡ.
Π’ΡΡ Π½ΡΠΆΠ½ΠΎ ΡΠΊΠ°Π·Π°ΡΡ, ΡΡΠΎ ΡΡΠ½ΠΊΡΠΈΡ e.stopPropagation()
(ΠΊΠΎΡΠΎΡΠ°Ρ Π²Π»ΠΎΠΆΠ΅Π½Π° Π² Π΄ΠΎΡΠ΅ΡΠ½ΠΈΠΉ ΡΠ»Π΅ΠΌΠ΅Π½Ρ ΠΎΠ±ΡΡΡΠΊΠΈ) ΠΏΡΠ΅Π΄ΠΎΡΠ²ΡΠ°ΡΠ°Π΅Ρ Π²ΡΠΏΠΎΠ»Π½Π΅Π½ΠΈΠ΅ ΠΎΡΡΠ°Π»ΡΠ½ΡΡ
ΡΡΠ½ΠΊΡΠΈΠΉ, ΡΡΠ°Π±Π°ΡΡΠ²Π°Π½ΠΈΠ΅ ΠΊΠΎΡΠΎΡΡΡ
ΠΏΠΎΠ²Π΅ΡΠ΅Π½ΠΎ Π½Π° Π΄Π°Π½Π½ΡΠΉ ΡΠ»Π΅ΠΌΠ΅Π½Ρ ΠΈΠ»ΠΈ Π΅Π³ΠΎ ΡΠΎΠ΄ΠΈΡΠ΅Π»Π΅ΠΉ. ΠΠΎΠ½ΠΊΡΠ΅ΡΠ½ΠΎ Π² Π΄Π°Π½Π½ΠΎΠΌ ΡΠ»ΡΡΠ°Π΅, ΠΎΠ½ Π½Π΅ Π΄Π°ΡΡ Π·Π°ΠΊΡΡΡΡΡΡ ΠΌΠΎΠ΄Π°Π»ΡΠ½ΠΎΠΌΡ ΠΎΠΊΠ½Ρ ΠΏΡΠΈ ΠΊΠ»ΠΈΠΊΠ΅ Π½Π° Π½Π΅Π³ΠΎ (ΡΡΠ½ΠΊΡΠΈΡ Π·Π°ΠΊΡΡΡΠΈΡ ΡΠ°Π±ΠΎΡΠ°Π΅Ρ Π½Π΅ ΡΠΎΠ»ΡΠΊΠΎ Π½Π° ΡΠΎΠ΄ΠΈΡΠ΅Π»Π΅ ΠΈΠ·Π½Π°ΡΠ°Π»ΡΠ½ΠΎ, Π½ΠΎ ΠΈ Π½Π° ΡΠ΅Π±ΡΠ½ΠΊΠ΅)
components / Modal / Modal.tsx
import React from 'react';
import styles from './Modal.module.scss';
import cn from 'classnames';
import { IModalProps } from '@/components/Modal/Modal.props';
export const Modal = ({ children, className, visible, setVisible, ...props }: IModalProps): JSX.Element => {
return (
<div
className={cn(styles.modal, className, {
[styles.active]: visible,
[styles.disabled]: !visible,
})}
onClick={() => setVisible(false)}
>
<div
className={styles.modal__content}
onClick={e => e.stopPropagation()}
>
{children}
</div>
</div>
);
};
- Π ΡΠΎΠ΄ΠΈΡΠ΅Π»ΡΡΠΊΠΈΠΉ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ Π½ΡΠΆΠ½ΠΎ Π΄ΠΎΠ±Π°Π²ΠΈΡΡ ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅, ΠΊΠΎΡΠΎΡΠΎΠ΅ Π±ΡΠ΄Π΅Ρ ΠΊΠΎΠ½ΡΡΠΎΠ»ΠΈΡΠΎΠ²Π°ΡΡ Π²ΠΈΠ΄ΠΈΠΌΠΎΡΡΡ ΠΌΠΎΠ΄Π°Π»ΡΠ½ΠΎΠ³ΠΎ ΠΎΠΊΠ½Π°.
- ΠΠ°Π»Π΅Π΅ Π² ΡΡΠ½ΠΊΡΠΈΡ
createPost()
Π΄ΠΎΠ±Π°Π²ΠΈΠΌ Π·Π°ΠΊΡΡΡΠΈΠ΅ ΠΌΠΎΠ΄Π°Π»ΡΠ½ΠΎΠ³ΠΎ ΠΎΠΊΠ½Π° ΠΏΡΠΈ Π΄ΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠΈ Π½ΠΎΠ²ΠΎΠ³ΠΎ ΠΏΠΎΡΡΠ° - Π ΡΠ΅Π½Π΄Π΅ΡΠ΅ ΠΏΠ΅ΡΠ΅Π΄Π°ΡΠΌ Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡΠ΅Π»ΡΠ½ΡΡ ΠΊΠ½ΠΎΠΏΠΊΡ, ΠΊΠΎΡΠΎΡΠ°Ρ Π±ΡΠ΄Π΅Ρ ΠΏΡΠΎΡΠ²Π»ΡΡΡ ΠΌΠΎΠ΄Π°Π»ΡΠ½ΠΎΠ΅ ΠΎΠΊΠ½ΠΎ
- ΠΠ½ΡΡΡΡ ΠΌΠΎΠ΄Π°Π»ΠΊΠΈ ΠΏΠ΅ΡΠ΅Π»ΠΎΠΆΠΈΠΌ ΡΠΎΡΠΌΡ Π΄Π»Ρ ΡΠΎΠ·Π΄Π°Π½ΠΈΡ Π½ΠΎΠ²ΠΎΠ³ΠΎ ΠΏΠΎΡΡΠ°
page-components / Posts.tsx
export const Posts = () => {
const [posts, setPosts] = useState<IPost[]>([
{ id: 'asd1', title: 'Javascript', body: 'ΠΡΡΡΠΈΠΉ ΡΠ·ΡΠΊ Π½Π° ΠΠ΅ΠΌΠ»Π΅' },
{ id: 'adsgsa2', title: 'C#', body: 'Π₯ΡΠΎΡΠΈΠΉ ΡΠ·ΡΠΊ' },
{ id: 'fsdagha3', title: 'Python', body: 'ΠΠΎΡΠ΅ΠΌΡ Π±Ρ ΠΈ Π½Π΅Ρ?' },
]);
const [filter, setFilter] = useState<IFilter>({ query: '', sort: 'title' });
// ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ ΠΌΠΎΠ΄Π°Π»ΡΠ½ΠΎΠ³ΠΎ ΠΎΠΊΠ½Π°
const [modal, setModal] = useState(false);
const sortedPosts = useMemo<IPost[]>(() => {
return [...posts].sort((a, b) => a[filter.sort].localeCompare(b[filter.sort]));
}, [filter.sort, posts]);
const sortedAndSearchedPosts = useMemo<IPost[]>(() => {
return sortedPosts.filter(post =>
post.title.toLowerCase().includes(filter.query.toLowerCase()),
);
}, [filter.query, sortedPosts]);
const createPost = (newPost: IPost): void => {
setPosts([...posts, newPost]);
// ΠΏΠΎΡΠ»Π΅ ΡΠΎΠ·Π΄Π°Π½ΠΈΡ ΠΌΠΎΠ΄Π°Π»ΠΊΠΈ, ΠΎΠ½ΠΎ Π·Π°ΠΊΡΠΎΠ΅ΡΡΡ
setModal(false);
};
const removePost = (post: IPost): void => {
setPosts(posts.filter(p => p.id !== post.id));
};
return (
<div className={styles.wrapper}>
<Button
className={styles.button}
buttonType={'purple'}
onClick={() => setModal(true)}
>
Π‘ΠΎΠ·Π΄Π°ΡΡ ΠΏΠΎΡΡ
</Button>
<Modal visible={modal} setVisible={setModal}>
<PostForm create={createPost} />
</Modal>
<PostFilter filter={filter} setFilter={setFilter} />
<PostList className={styles.list} posts={sortedAndSearchedPosts} remove={removePost} />
</div>
);
};
ΠΡΠΎΠ³:
01:30:23 β ΠΠ½ΠΈΠΌΠ°ΡΠΈΠΈ. React transition group
ΠΠ΄Π½ΠΈΠΌ ΠΈΠ· ΡΠΏΠΎΡΠΎΠ±ΠΎΠ² Π΄ΠΎΠ±Π°Π²ΠΈΡΡ Π°Π½ΠΈΠΌΠ°ΡΠΈΡ Π² ΡΠ΅Π°ΠΊΡ ΡΠ²Π»ΡΠ΅ΡΡΡ react-transition-group
, ΠΊΠΎΡΠΎΡΡΠΉ ΠΌΡ ΠΌΠΎΠΆΠ΅ΠΌ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ ΠΊΠ°ΠΊ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΡ ΡΠ΅Π°ΠΊΡΠ°
npm install react-transition-group --save &&
npm install --save @types/react-transition-group
Π’ΡΡ Π½ΡΠΆΠ½ΠΎ ΠΎΠ±Π΅ΡΠ½ΡΡΡ Π³ΡΡΠΏΠΏΡ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠΎΠ² Π² TransitionGroup
Π° ΠΊΠ°ΠΆΠ΄ΡΠΉ ΠΎΡΠ΄Π΅Π»ΡΠ½ΡΠΉ ΡΠ»Π΅ΠΌΠ΅Π½Ρ Π² CSSTransition
(Π² ΡΡΠΎΡ ΠΆΠ΅ ΡΠ»Π΅ΠΌΠ΅Π½Ρ Π½ΡΠΆΠ½ΠΎ ΠΏΠ΅ΡΠ΅Π΄Π°ΡΡ ΡΠ²ΠΎΠΉΡΡΠ²ΠΎ ΠΊΠ»ΡΡΠ°, ΡΠ°ΠΊ ΠΊΠ°ΠΊ ΠΎΠ½ ΡΠ°ΡΠΏΠΎΠ»Π°Π³Π°Π΅ΡΡΡ Π²ΡΡΠ΅, ΡΠ΅ΠΌ PostItem
)
components / PostList / PostList.tsx
import { TransitionGroup, CSSTransition } from 'react-transition-group';
export const PostList = ({ remove, posts, className, ...props }: PostListProps) => {
return (
<div className={cn(styles.wrapper, className)} {...props}>
{posts.length ? (
<TransitionGroup className='post-list'>
{posts.map(p => (
<CSSTransition key={p.id} timeout={500} classNames='post'>
<PostItem remove={remove} post={p} />
</CSSTransition>
))}
</TransitionGroup>
) : (
<h2 style={{ textAlign: 'center' }}>ΠΠΎΡΡΡ Π½Π΅ Π΄ΠΎΠ±Π°Π²Π»Π΅Π½Ρ</h2>
)}
</div>
);
};
Π‘ΡΠΈΠ»ΠΈ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ°:
CSSTransition
ΠΌΡ Π½Π°ΠΈΠΌΠ΅Π½ΠΎΠ²Π°Π»ΠΈ ΠΊΠ°ΠΊ post
ΠΈ ΠΏΠΎΡΡΠΎΠΌΡ Π² ΠΎΠΏΡΠ΅Π΄Π΅Π»ΡΠ½Π½ΡΡ
ΡΠΎΡΡΠΎΡΠ½ΠΈΡΡ
Π½ΡΠΆΠ½ΠΎ Π½Π°ΠΈΠΌΠ΅Π½ΠΎΠ²Π°ΡΡ Π½Π°ΡΠ°Π»ΡΠ½ΡΠΉ ΡΠ»Π΅ΠΌΠ΅Π½Ρ ΠΊΠ°ΠΊ post
.post-enter {
opacity: 0;
}
.post-enter-active {
opacity: 1;
transition: all 0.2s ease-in;
}
.post-exit {
opacity: 1;
}
.post-exit-active {
opacity: 0;
transform: rotateY(-350deg);
transition: all 0.2s ease-in;
}
01:33:40 β ΠΠ΅ΠΊΠΎΠΌΠΏΠΎΠ·ΠΈΡΠΈΡ. ΠΠ°ΡΡΠΎΠΌΠ½ΡΠ΅ Ρ ΡΠΊΠΈ
ΠΠΎΠ³Π΄Π° Π½Π°Ρ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ Π½Π°ΡΠΈΠ½Π°Π΅Ρ Ρ ΡΠ°Π½ΠΈΡΡ Π±ΠΎΠ»ΡΡΠΎΠ΅ ΠΊΠΎΠ»ΠΈΡΠ΅ΡΡΠ²ΠΎ Π»ΠΎΠ³ΠΈΠΊΠΈ, Π½ΡΠΆΠ½ΠΎ Π΅Π³ΠΎ Π΄Π΅ΠΊΠΎΠΌΠΏΠΎΠ·ΠΈΡΠΎΠ²Π°ΡΡ ΠΈ Π²ΡΠ΄Π΅Π»ΡΡΡ Π΅Π³ΠΎ ΡΡΠ½ΠΊΡΠΈΠΈ Π² Ρ Π΅Π»ΠΏΠ΅ΡΡ ΠΈΠ»ΠΈ Π΄Π°ΠΆΠ΅ ΠΎΡΠ΄Π΅Π»ΡΠ½ΡΠ΅ Ρ ΡΠΊΠΈ.
Π’ΡΡ Π±ΡΠ΄ΡΡ Ρ ΡΠ°Π½ΠΈΡΡΡΡ ΠΎΡΠ΄Π΅Π»ΡΠ½ΡΠ΅ Ρ ΡΠΊΠΈ, ΠΊΠΎΡΠΎΡΡΠ΅ ΠΌΡ Π²ΡΡΠ΅ΠΏΠΈΠ»ΠΈ ΠΈΠ· ΠΎΡΠ½ΠΎΠ²Π½ΠΎΠ³ΠΎ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ° ΡΠΎΠ·Π΄Π°Π½ΠΈΡ ΠΏΠΎΡΡΠΎΠ².
hooks / usePosts.ts
import { useMemo } from 'react';
import { IPost } from '@/page-components/Posts/Posts.interface';
const useSortedPosts = (posts: IPost[], sort: 'title' | 'body') => {
return useMemo<IPost[]>(() => {
return [...posts].sort((a, b) => a[sort].localeCompare(b[sort]));
}, [sort, posts]);
};
export const usePosts = (posts: IPost[], sort: 'title' | 'body', query: string) => {
const sortedPosts = useSortedPosts(posts, sort);
return useMemo<IPost[]>(() => {
return sortedPosts.filter(post => post.title.toLowerCase().includes(query.toLowerCase()));
}, [query, sortedPosts]);
};
Π ΡΠ°ΠΌΠΎΠΌ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ΅ ΠΌΡ ΠΏΡΠΎΡΡΠΎ Π²ΡΠ·Π²Π°Π΅ΠΌ ΡΠΎΡΡΠΈΡΠΎΠ²ΠΊΡ ΠΏΠΎΡΡΠΎΠ²
page-components / Posts.tsx
export const Posts = () => {
const [posts, setPosts] = useState<IPost[]>([
{ id: 'asd1', title: 'Javascript', body: 'ΠΡΡΡΠΈΠΉ ΡΠ·ΡΠΊ Π½Π° ΠΠ΅ΠΌΠ»Π΅' },
{ id: 'adsgsa2', title: 'C#', body: 'Π₯ΡΠΎΡΠΈΠΉ ΡΠ·ΡΠΊ' },
{ id: 'fsdagha3', title: 'Python', body: 'ΠΠΎΡΠ΅ΠΌΡ Π±Ρ ΠΈ Π½Π΅Ρ?' },
]);
const [filter, setFilter] = useState<IFilter>({ query: '', sort: 'title' });
const [modal, setModal] = useState(false);
// ΡΠΎΡΡΠΈΡΠΎΠ²ΠΊΠ° ΠΌΠ°ΡΡΠΈΠ²Π° ΠΏΠΎΡΡΠΎΠ²
const sortedAndSearchedPosts = usePosts(posts, filter.sort, filter.query);
const createPost = (newPost: IPost): void => {
setPosts([...posts, newPost]);
setModal(false);
};
const removePost = (post: IPost): void => {
setPosts(posts.filter(p => p.id !== post.id));
};
return (
<div className={styles.wrapper}>
<Button className={styles.button} buttonType={'purple'} onClick={() => setModal(true)}>
Π‘ΠΎΠ·Π΄Π°ΡΡ ΠΏΠΎΡΡ
</Button>
<Modal visible={modal} setVisible={setModal}>
<PostForm create={createPost} />
</Modal>
<PostFilter filter={filter} setFilter={setFilter} />
<PostList className={styles.list} posts={sortedAndSearchedPosts} remove={removePost} />
</div>
);
};
01:36:20 β Π Π°Π±ΠΎΡΠ° Ρ ΡΠ΅ΡΠ²Π΅ΡΠΎΠΌ. Axios
ΠΠ΅ΡΠ²ΡΠΌ Π΄Π΅Π»ΠΎΠΌ, ΡΡΡΠ°Π½ΠΎΠ²ΠΈΠΌ ΠΌΠΎΠ΄ΡΠ»Ρ ΠΏΠΎ ΡΠ°Π±ΠΎΡΠ΅ Ρ ΡΠ΅ΡΠ²Π΅ΡΠΎΠΌ axios
, ΠΊΠΎΡΠΎΡΡΠΉ ΠΏΠΎΠ·Π²ΠΎΠ»ΠΈΡ ΠΏΡΠΎΡΡΠΎ ΠΎΡΠΏΡΠ°Π²Π»ΡΡΡ Π·Π°ΠΏΡΠΎΡΡ Π½Π° ΠΏΠΎΠ»ΡΡΠ΅Π½ΠΈΠ΅ Π΄Π°Π½Π½ΡΡ
Π½Π° ΡΠ΅ΡΠ²Π΅Ρ
npm iΒ axios
01:38:40 β ΠΠΈΠ·Π½Π΅Π½Π½ΡΠΉ ΡΠΈΠΊΠ» ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ°. useEffect
ΠΠΈΠ·Π½Π΅Π½Π½ΡΠΉ ΡΠΈΠΊΠ» ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ° React Π΄Π΅Π»ΠΈΡΡΡ Π½Π° 4 ΡΠ°ΡΡΠΈ:
- ΠΠ½ΠΈΡΠΈΠ°Π»ΠΈΠ·Π°ΡΠΈΡ (ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ ΠΏΠΎΠ»ΡΡΠ°Π΅Ρ ΠΏΡΠΎΠΏΡΡ ΠΈ ΡΠΎΡΡΠΎΡΠ½ΠΈΡ)
- ΠΠΎΠ½ΡΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ (Π²Π΅ΡΠ°Π΅ΠΌ ΡΠ»ΡΡΠ°ΡΠ΅Π»ΠΈ ΡΠΎΠ±ΡΡΠΈΡ, Π³Π΅Π½Π΅ΡΠΈΠΌ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ)
- ΠΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ (ΠΏΡΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΠΌ ΠΊΠ°ΠΊΠΈΠ΅-Π»ΠΈΠ±ΠΎ Π΄Π΅ΠΉΡΡΠ²ΠΈΡ Π½Π°Π΄ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠΎΠΌ)
- Π Π°Π·ΠΌΠΎΠ½ΡΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ (ΡΠ΄Π°Π»ΡΠ΅ΠΌ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ / ΠΎΡΠΏΠΈΡΡΠ²Π°Π΅ΠΌΡΡ ΠΎΡ ΡΠ»ΡΡΠ°ΡΠ΅Π»Π΅ΠΉ ΡΠΎΠ±ΡΡΠΈΡ, ΠΎΡΠΈΡΠ°Π΅ΠΌ Π³Π»ΠΎΠ±Π°Π»ΡΠ½ΠΎΠ΅ Ρ ΡΠ°Π½ΠΈΠ»ΠΈΡΠ΅)
Π ΡΡΡ ΠΌΡ ΠΏΠΎΠ΄Ρ
ΠΎΠ΄ΠΈΠΌ ΠΊ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΡ Ρ
ΡΠΊΠ° useEffect
, ΠΊΠΎΡΠΎΡΡΠΉ ΠΏΠΎΠ·Π²ΠΎΠ»ΠΈΡ Π²ΡΠΏΠΎΠ»Π½ΡΡΡ Π΄Π΅ΠΉΡΡΠ²ΠΈΡ ΠΏΠΎΠ΄ ΠΎΠΏΡΠ΅Π΄Π΅Π»ΡΠ½Π½ΡΠ΅ ΡΡΠ°Π΄ΠΈΠΈ ΠΌΠΎΠ½ΡΠΈΡΠΎΠ²ΠΊΠΈ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ°
Π Π³Π»Π°Π²Π½ΠΎΠΌ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ΅ ΠΏΠΎΠ»ΡΡΠΈΠΌ ΠΌΠ°ΡΡΠΈΠ² ΠΏΠΎΡΡΠΎΠ² Ρ ΡΠ΅ΡΠ²Π΅ΡΠ° ΡΠ΅ΡΠ΅Π· fetchPosts
ΠΈ ΡΠΎΡ
ΡΠ°Π½ΠΈΠΌ Π΅Π³ΠΎ Π² ΡΡΠ΅ΠΉΡ Π½Π°ΡΠΈΡ
ΠΏΠΎΡΡΠΎΠ². ΠΡΠ·Π²Π°Π½Π° ΡΡΠ° ΡΡΠ½ΠΊΡΠΈΡ Π±ΡΠ΄Π΅Ρ ΡΠ΅ΡΠ΅Π· useEffect
, ΠΊΠΎΡΠΎΡΡΠΉ Π² ΠΏΡΠ΅Π΄ΡΡΠ°Π²Π»Π΅Π½Π½ΠΎΠΌ ΡΠ΅ΡΠ°ΠΏΠ΅ Π±ΡΠ΄Π΅Ρ Π²ΡΠΏΠΎΠ»Π½ΡΡΡ Π΄Π΅ΠΉΡΡΠ²ΠΈΠ΅ ΡΠΎΠ²Π½ΠΎ ΠΎΠ΄ΠΈΠ½ ΡΠ°Π· - ΠΏΡΠΈ Π·Π°Π³ΡΡΠ·ΠΊΠ΅ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ°
page-components / Posts.tsx
export const Posts = () => {
const [posts, setPosts] = useState('');
const fetchPosts = async () => {
const posts = await axios.get('https://jsonplaceholder.typicode.com/posts');
setPosts(posts.data);
};
useEffect(() => {
fetchPosts();
}, []);
const [filter, setFilter] = useState<IFilter>({ query: '', sort: 'title' });
const [modal, setModal] = useState(false);
const sortedAndSearchedPosts = usePosts(posts, filter.sort, filter.query);
const createPost = (newPost: IPost): void => {
setPosts([...posts, newPost]);
setModal(false);
};
const removePost = (post: IPost): void => {
setPosts(posts.filter(p => p.id !== post.id));
};
return (
<div className={styles.wrapper}>
<Button className={styles.button} buttonType={'purple'} onClick={() => setModal(true)}>
Π‘ΠΎΠ·Π΄Π°ΡΡ ΠΏΠΎΡΡ
</Button>
<Modal visible={modal} setVisible={setModal}>
<PostForm create={createPost} />
</Modal>
<PostFilter filter={filter} setFilter={setFilter} />
<PostList className={styles.list} posts={sortedAndSearchedPosts} remove={removePost} />
</div>
);
};
ΠΡΠΎΠ³: ΠΌΡ ΠΏΠΎΠ»ΡΡΠΈΠ»ΠΈ ΠΏΠΎΡΡΡ Ρ ΡΠ΅ΡΠ²Π΅ΡΠ° ΠΈ ΡΡΠ°Π·Ρ ΠΈΡ
ΠΎΡΡΠ΅Π½Π΄Π΅ΡΠΈΠ»ΠΈ, ΡΠ°ΠΊ ΠΊΠ°ΠΊ ΡΠ΅ΡΠ΅Π· useEffect
Π²ΡΠ·Π²Π°Π»ΠΈ ΡΡΠ½ΠΊΡΠΈΡ ΠΏΠΎΠ»ΡΡΠ΅Π½ΠΈΡ ΠΏΠΎΡΡΠΎΠ²
01:43:08 β API. PostService
Π§ΡΠΎΠ±Ρ ΡΠΏΡΠΎΡΡΠΈΡΡ ΡΠ²ΠΎΡ ΡΠ°Π±ΠΎΡΡ Ρ ΡΠ΅ΡΠ²Π΅ΡΠΎΠΌ ΡΠ΅ΡΠ΅Π· Π²Π½Π΅ΡΠ΅Π½ΠΈΠ΅ Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡΠ΅Π»ΡΠ½ΠΎΠΉ Π°Π±ΡΡΡΠ°ΠΊΡΠΈΠΈ, ΠΌΠΎΠΆΠ½ΠΎ ΡΠ΄Π΅Π»Π°ΡΡ ΠΎΡΠ΄Π΅Π»ΡΠ½ΠΎΠ΅ API Π½Π° ΡΡΠΎΠ½ΡΠ΅, ΠΊΠΎΡΠΎΡΠΎΠ΅ Π±ΡΠ΄Π΅Ρ ΠΏΠΎΠ»ΡΡΠ°ΡΡ Π½Π°ΡΠΈ ΠΏΠΎΡΡΡ Ρ ΡΠ΅ΡΠ²Π΅ΡΠ°.
Π‘ΠΎΠ·Π΄Π°Π΄ΠΈΠΌ ΠΊΠ»Π°ΡΡ ΡΠΎ ΡΡΠ°ΡΠΈΡΠ½ΠΎΠΉ ΡΡΠ½ΠΊΡΠΈΠ΅ΠΉ ΠΈ Π±ΡΠ΄Π΅ΠΌ ΡΠΆΠ΅ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ ΡΡΡ ΠΊΠΎΠ½ΡΡΡΡΠΊΡΠΈΡ Π΄Π»Ρ ΡΠ°Π±ΠΎΡΡ Ρ ΡΠ΅ΡΠ²Π΅ΡΠΎΠΌ
ΠΠ°ΡΠ°Π½Π΅Π΅ ΡΡΠΎΠΈΡ ΡΠΊΠ°Π·Π°ΡΡ, ΡΡΠΎ ΠΎΠ±ΡΠ°Π±Π°ΡΡΠ²Π°ΡΡ ΠΎΡΠΈΠ±ΠΊΡ ΡΡΡ - ΠΏΠ»ΠΎΡ ΠΎΠΉ ΠΏΠΎΠ΄Ρ ΠΎΠ΄, ΠΏΠΎΡΡΠΎΠΌΡ ΠΏΠ΅ΡΠ΅Π»ΠΎΠ²ΠΈΠΌ ΠΎΡΠΈΠ±ΠΊΡ Π² Π΄ΡΡΠ³ΠΎΠΌ ΠΌΠ΅ΡΡΠ΅ ΠΊΠΎΠ΄Π° β
API / post.service.ts
import axios from 'axios';
export default class PostService {
static async getAll() {
const posts = await axios.get('https://jsonplaceholder.typicode.com/posts');
return posts.data;
}
}
Π ΡΠ΅ΠΏΠ΅ΡΡ ΠΌΡ ΠΌΠΎΠΆΠ΅ΠΌ ΠΏΠΎΠ»ΡΡΠΈΡΡ ΠΏΠΎΡΡΡ Π±ΠΎΠ»Π΅Π΅ Π»Π°ΠΊΠΎΠ½ΠΈΡΠ½ΡΠΌ ΠΈ ΠΏΠΎΠ½ΡΡΠ½ΡΠΌ ΡΠΏΠΎΡΠΎΠ±ΠΎΠΌ
page-components / Posts.tsx
const fetchPosts = async () => {
const posts = await PostService.getAll();
setPosts(posts);
};
01:44:45 β ΠΠ½Π΄ΠΈΠΊΠ°ΡΠΈΡ Π·Π°Π³ΡΡΠ·ΠΊΠΈ Π΄Π°Π½Π½ΡΡ Ρ ΡΠ΅ΡΠ²Π΅ΡΠ°
ΠΡ ΡΠΎΠ·Π΄Π°Π»ΠΈ ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ isPostLoading
, ΠΊΠΎΡΠΎΡΠΎΠ΅ Π±ΡΠ΄Π΅Ρ ΠΎΡΠ²Π΅ΡΠ°ΡΡ Π·Π° ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ Π·Π°Π³ΡΡΠ·ΠΊΠΈ ΠΏΠΎΡΡΠΎΠ². Π ΡΡΠ½ΠΊΡΠΈΠΈ fetchPosts
Π΄ΠΎΠ±Π°Π²ΠΈΠΌ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠ΅ ΡΡΠΎΠ³ΠΎ ΡΠΎΡΡΠΎΡΠ½ΠΈΡ (ΡΠ°ΠΊ ΠΊΠ°ΠΊ ΡΠ΅ΡΠ΅Π· Π½Π΅Π³ΠΎ ΠΈΠ΄ΡΡ ΠΏΠΎΠ»ΡΡΠ΅Π½ΠΈΠ΅ Π΄Π°Π½Π½ΡΡ
Ρ ΡΠ΅ΡΠ²Π΅ΡΠ°). Π Π΄Π°Π»Π΅Π΅ Π² render
ΡΠΊΠ°ΠΆΠ΅ΠΌ, ΡΡΠΎ ΠΎΡΠΎΠ±ΡΠ°ΠΆΠ°ΡΡ ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΠ΅ ΠΎ Π·Π°Π³ΡΡΠ·ΠΊΠ΅ Π½ΡΠΆΠ½ΠΎ ΠΏΠΎΠΊΠ° Π΅Π³ΠΎ ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ Π½Π΅ ΠΏΠ΅ΡΠ΅ΠΉΠ΄ΡΡ Π² false
page-components / Posts.tsx
export const Posts = () => {
const [posts, setPosts] = useState('');
// ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ Π΄Π»Ρ Π·Π°Π³ΡΡΠ·ΠΊΠΈ ΠΏΠΎΡΡΠΎΠ²
const [isPostLoading, setIsPostLoading] = useState(false);
const fetchPosts = async () => {
// ΡΠ΅ΠΉΡΠ°Ρ ΠΏΠΎΡΡΡ ΡΠΎΠ»ΡΠΊΠΎ Π½Π°ΡΠ½ΡΡ Π·Π°Π³ΡΡΠ·ΠΊΡ, ΠΏΠΎΡΡΠΎΠΌΡ Π½ΡΠΆΠ½ΠΎ ΠΏΠΎΠΊΠ°Π·Π°ΡΡ Π·Π°Π³ΡΡΠ·ΠΊΡ
setIsPostLoading(true);
const posts = await PostService.getAll();
setPosts(posts);
// Π·Π΄Π΅ΡΡ ΡΠΆΠ΅ ΠΏΠΎΡΡΡ Π·Π°Π³ΡΡΠΆΠ΅Π½Ρ
setIsPostLoading(false);
};
useEffect(() => {
fetchPosts();
}, []);
/// CODE ...
return (
<div className={styles.wrapper}>
<Button className={styles.button} buttonType={'purple'} onClick={() => setModal(true)}>
Π‘ΠΎΠ·Π΄Π°ΡΡ ΠΏΠΎΡΡ
</Button>
<Modal visible={modal} setVisible={setModal}>
<PostForm create={createPost} />
</Modal>
<PostFilter filter={filter} setFilter={setFilter} />
{isPostLoading ? (
<h1>ΠΠ΄ΡΡ Π·Π°Π³ΡΡΠ·ΠΊΠ°...</h1>
) : (
<PostList
className={styles.list}
posts={sortedAndSearchedPosts}
remove={removePost}
/>
)}
</div>
);
};
ΠΡΠΎΠ³:
01:46:20 β ΠΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ Loader. ΠΠ½ΠΈΠΌΠ°ΡΠΈΠΈ
ΠΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ Π»ΠΎΠ°Π΄Π΅ΡΠ° Π±ΡΠ΄Π΅Ρ ΠΏΡΠ΅Π΄ΡΡΠ°Π²Π»ΡΡΡ ΠΈΠ· ΡΠ΅Π±Ρ ΠΎΠ±ΡΡΠ½ΡΠΉ <div>
components / Loader / Loader.tsx
export const Loader = ({ children, className }: ILoaderProps) => {
return <div className={cn(styles.loader, className)}>{children}</div>;
};
Π Π°Π½ΠΈΠΌΠ°ΡΠΈΡ Π±ΡΠ΄Π΅Ρ Π±Π΅ΡΠΊΠΎΠ½Π΅ΡΠ½ΠΎΠΉ ΠΈ ΡΠ΅Π°Π»ΠΈΠ·ΠΎΠ²Π°Π½Π½ΠΎΠΉ ΡΠ΅ΡΠ΅Π· ΠΎΠ±ΡΡΠ½ΡΠΉ CSS
components / Loader / Loader.module.SCSS
.loader {
display: flex;
align-items: center;
justify-items: center;
text-align: center;
width: 100px;
height: 100px;
border-radius: 50%;
border: 3px dashed var(--primary);
animation: rotate 1s linear infinite;
transition: all 0.2s;
}
@keyframes rotate {
from {
transform: rotate(0deg) scale(1);
}
to { transform: rotate(360deg) scale(1.2);
}
}
Π’Π΅ΠΏΠ΅ΡΡ ΠΎΡΡΠ°Π½Π΅ΡΡΡ ΡΠΎΠ»ΡΠΊΠΎ Π²ΡΡΠ°Π²ΠΈΡΡ ΠΊΡΡΡΠΈΠ»ΠΊΡ Π½Π° ΡΡΡΠ°Π½ΠΈΡΡ
page-components / Posts.tsx
{isPostLoading ? (
<div className={styles.loadPosition}>
<Loader>ΠΠ΄ΡΡ Π·Π°Π³ΡΡΠ·ΠΊΠ°...</Loader>
</div>
) : (
<PostList
className={styles.list}
posts={sortedAndSearchedPosts}
remove={removePost}
/>
)}
ΠΡΠΎΠ³: ΠΏΠΎΠ»ΡΡΠ΅Π½Π° ΠΊΡΡΡΠΈΠ»ΠΊΠ°, ΠΊΠΎΡΠΎΡΠ°Ρ ΠΎΠΏΠΎΠ²Π΅ΡΠ°Π΅Ρ ΠΎ Π·Π°Π³ΡΡΠ·ΠΊΠ΅
01:49:25 β ΠΠ°ΡΡΠΎΠΌΠ½ΡΠΉ Ρ ΡΠΊ useFetching(). ΠΠ±ΡΠ°Π±ΠΎΡΠΊΠ° ΠΎΡΠΈΠ±ΠΎΠΊ
ΠΠ°Π»Π΅Π΅ Π½Π°ΠΌ Π½ΡΠΆΠ½ΠΎ ΡΠ΄Π΅Π»Π°ΡΡ Ρ ΡΠΊ, ΠΊΠΎΡΠΎΡΡΠΉ Π±ΡΠ΄Π΅Ρ ΠΏΠΎΠ»ΡΡΠ°ΡΡ ΡΡΠ½ΠΊΡΠΈΡ ΠΈ Π²ΡΠΏΠΎΠ»Π½ΡΡΡ Π΅Ρ, Π° ΡΠ°ΠΊ ΠΆΠ΅ Π±ΡΠ΄Π΅Ρ ΠΊΠΎΠ½ΡΡΠΎΠ»ΠΈΡΠΎΠ²Π°ΡΡ ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ ΡΠΏΠΈΠ½Π½Π΅ΡΠ° Π·Π°Π³ΡΡΠ·ΠΊΠΈ ΠΈ ΠΏΠ΅ΡΠ΅Ρ Π²Π°ΡΡΠ²Π°ΡΡ ΠΎΡΠΈΠ±ΠΊΡ, Π΅ΡΠ»ΠΈ ΡΠ°ΠΊΠΎΠ²Π°Ρ ΠΏΡΠΈΠ΄ΡΡ Π½Π° ΡΡΡΠ°Π½ΠΈΡΡ
hooks / useFetching.ts
import { useState } from 'react';
export const useFetching = (callback: Function): [Function, boolean, string] => {
const [isLoading, setIsLoading] = useState<boolean>(false);
const [error, setError] = useState<string>('');
const fetching = async (): Promise<void> => {
try {
setIsLoading(true);
await callback();
} catch (e: unknown) {
setError(e.message as string);
} finally {
setIsLoading(false);
}
};
return [fetching, isLoading, error];
};
Π ΡΠ΅ΠΏΠ΅ΡΡ Π² ΡΠΎΠ΄ΠΈΡΠ΅Π»ΡΡΠΊΠΎΠΌ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ΅ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΠΌ Ρ
ΡΠΊ useFetching
, ΠΊΠΎΡΠΎΡΡΠΉ ΠΏΠΎΠ·Π²ΠΎΠ»ΠΈΡ Π½Π°ΠΌ ΡΠ±ΡΠ°ΡΡ ΠΎΡΡΠ»Π΅ΠΆΠΈΠ²Π°Π½ΠΈΠ΅ ΡΠΎΡΡΠΎΡΠ½ΠΈΡ Π·Π°Π³ΡΡΠ·ΠΊΠΈ Π²Π½ΡΡΡΠΈ ΡΠΎΠ΄ΠΈΡΠ΅Π»ΡΡΠΊΠΎΠ³ΠΎ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ°
Π’Π°ΠΊ ΠΆΠ΅ ΡΠ΄Π΅Π»Π°Π΅ΠΌ Π²ΡΠ²ΠΎΠ΄ ΠΎΡΠΈΠ±ΠΊΠΈ, Π΅ΡΠ»ΠΈ ΡΠ°ΠΊΠΎΠ²Π°Ρ Π±ΡΠ΄Π΅Ρ ΠΈΠΌΠ΅ΡΡΡΡ ΡΠ΅ΡΠ΅Π· postsError
page-components / Posts.tsx
export const Posts = () => {
const [posts, setPosts] = useState('');
// ΠΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΠΌ Ρ
ΡΠΊ Π΄Π»Ρ
const [fetchPosts, isPostLoading, postsError] = useFetching(async () => {
const posts = await PostService.getAll();
setPosts(posts);
});
useEffect(() => {
fetchPosts();
}, []);
const [filter, setFilter] = useState<IFilter>({ query: '', sort: 'title' });
const [modal, setModal] = useState(false);
const sortedAndSearchedPosts = usePosts(posts, filter.sort, filter.query);
const createPost = (newPost: IPost): void => {
setPosts([...posts, newPost]);
setModal(false);
};
const removePost = (post: IPost): void => {
setPosts(posts.filter(p => p.id !== post.id));
};
return (
<div className={styles.wrapper}>
<Button className={styles.button} buttonType={'purple'} onClick={() => setModal(true)}>
Π‘ΠΎΠ·Π΄Π°ΡΡ ΠΏΠΎΡΡ
</Button>
<Modal visible={modal} setVisible={setModal}>
<PostForm create={createPost} />
</Modal>
<PostFilter filter={filter} setFilter={setFilter} />
{postsError && <h1>ΠΡΠΎΠΈΠ·ΠΎΡΠ»Π° ΠΎΡΠΈΠ±ΠΊΠ° {postsError}</h1>}
{isPostLoading ? (
<div className={styles.loadPosition}>
<Loader>ΠΠ΄ΡΡ Π·Π°Π³ΡΡΠ·ΠΊΠ°...</Loader>
</div>
) : (
<PostList
className={styles.list}
posts={sortedAndSearchedPosts}
remove={removePost}
/> )}
</div>
);
};
ΠΡΠ»ΠΈ ΠΌΡ Π½Π΅ ΡΠΌΠΎΠΆΠ΅ΠΌ ΠΏΠΎΠ»ΡΡΠΈΡΡ ΠΏΠΎΡΡΡ Ρ ΡΠ΅ΡΠ²Π΅ΡΠ°, ΡΠΎ ΡΠ²ΠΈΠ΄ΠΈΠΌ Π²ΠΎΡ ΡΠ°ΠΊΡΡ ΠΎΡΠΈΠ±ΠΊΡ:
01:54:15β ΠΠΎΡΡΡΠ°Π½ΠΈΡΠ½ΡΠΉ Π²ΡΠ²ΠΎΠ΄. ΠΠ°Π³ΠΈΠ½Π°ΡΠΈΡ (pagination)
ΠΡΠ²ΠΎΠ΄ΠΈΡΡ ΡΡΠ°Π·Ρ Π½Π° ΠΎΠ΄Π½ΠΎΠΉ ΡΡΡΠ°Π½ΠΈΡΠ΅ 100 ΠΏΠΎΡΡΠΎΠ² - ΡΡΠΎ Π½Π΅ ΡΠ°ΠΌΠ°Ρ Π»ΡΡΡΠ°Ρ ΠΈΠ΄Π΅Ρ. ΠΡΠ»ΠΈ Π΄Π°Π½Π½ΡΠ΅ ΠΏΠΎΡΡΡ Π±ΡΠ΄ΡΡ ΠΈΠΌΠ΅ΡΡ Π΅ΡΡ ΠΈ ΡΠΎΡΠΎΠ³ΡΠ°ΡΠΈΠΈ, ΡΠΎ Π·Π°Π³ΡΡΠ·ΠΊΠ° ΡΡΡΠ°Π½ΠΈΡΡ Π±ΡΠ΄Π΅Ρ Π΄ΠΎΠ»Π³ΠΎΠΉ ΠΈ ΡΡΡΡΠΎΠΉΡΡΠ²ΠΎ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Ρ ΡΠ°ΠΊ ΠΆΠ΅ Π±ΡΠ΄Π΅Ρ ΡΠΈΠ»ΡΠ½ΠΎ Π½Π°Π³ΡΡΠΆΠ΅Π½ΠΎ.
Π‘Π΅ΠΉΡΠ°Ρ ΡΡΠΎΠΈΡ Π²ΠΎΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡΡΡ ΠΏΠ°Π³ΠΈΠ½Π°ΡΠΈΠ΅ΠΉ - ΠΏΠΎΡΡΡΠ°Π½ΠΈΡΠ½ΠΎΠΉ Π·Π°Π³ΡΡΠ·ΠΊΠΎΠΉ ΡΡΡΠ°Π½ΠΈΡ.
ΠΠΎΠΌΠ΅Π½ jsonplaceholder
ΠΏΠΎΠ·Π²ΠΎΠ»ΡΠ΅Ρ ΠΏΠΎ ΠΎΠΏΡΠ΅Π΄Π΅Π»ΡΠ½Π½ΡΠΌ ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡΠ°ΠΌ Π²ΡΠ²ΠΎΠ΄ΠΈΡΡ Π»ΠΈΠΌΠΈΡΠΈΡΠΎΠ²Π°Π½Π½ΠΎΠ΅ ΠΊΠΎΠ»ΠΈΡΠ΅ΡΡΠ²ΠΎ ΠΏΠΎΡΡΠΎΠ² ΠΈ ΠΌΠ΅Π½ΡΡΡ ΡΡΡΠ°Π½ΠΈΡΡ. Π Ρ
Π΅Π΄Π΅ΡΠ΅ (x-total-count
) ΡΠ°ΠΊ ΠΆΠ΅ ΡΠΊΠ°Π·ΡΠ²Π°Π΅ΡΡΡ ΠΌΠ°ΠΊΡΠΈΠΌΠ°Π»ΡΠ½ΠΎΠ΅ ΠΊΠΎΠ»ΠΈΡΠ΅ΡΡΠ²ΠΎ ΠΏΠΎΡΡΠΎΠ², ΠΊΠΎΡΠΎΡΠΎΠ΅ ΠΌΠΎΠΆΠ΅Ρ Π²ΡΠ΄Π°ΡΡ Π·Π°ΠΏΡΠΎΡ
API / post.service.ts
import axios from 'axios';
export default class PostService {
static async getAll(limit: number = 10, page: number = 1) {
return await axios.get('https://jsonplaceholder.typicode.com/posts', {
params: {
_limit: limit,
_page: page,
},
});
}
}
utilities / pages.utilities.ts
// ΡΡΡ ΠΌΡ ΠΏΠΎΠ»ΡΡΠ°Π΅ΠΌ ΠΊΠΎΠ»ΠΈΡΠ΅ΡΡΠ²ΠΎ ΡΡΡΠ°Π½ΠΈΡ Π² Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠΈ ΠΎΡ ΠΎΠ±ΡΠ΅Π³ΠΎ ΠΊΠΎΠ»ΠΈΡΠ΅ΡΡΠ²Π° ΠΏΠΎΡΡΠΎΠ² ΠΈ ΠΈΡ
ΠΌΠ°ΠΊΡΠΈΠΌΠ°Π»ΡΠ½ΠΎΠ³ΠΎ ΠΊΠΎΠ»ΠΈΡΠ΅ΡΡΠ²Π° Π½Π° ΡΡΡΠ°Π½ΠΈΡΠ΅
export const getPageCount = (totalCount: number, limit: number): number => {
// Π²Π΅ΡΠ½ΡΠΌ ΡΠΈΡΠ»ΠΎ ΡΡΡΠ°Π½ΠΈΡ, ΠΎΠΊΡΡΠ³Π»ΡΠ½Π½ΠΎΠ΅ Π² Π±ΠΎΠ»ΡΡΡΡ ΡΡΠΎΡΠΎΠ½Ρ
return Math.ceil(totalCount / limit);
};
// ΡΡΡ ΠΌΡ ΡΠΎΠ·Π΄Π°Π΄ΠΈΠΌ ΠΌΠ°ΡΡΠΈΠ² ΡΡΡΠ°Π½ΠΈΡ, ΠΊΠΎΡΠΎΡΡΠ΅ Π±ΡΠ΄ΡΡ Π²ΡΠ²ΠΎΠ΄ΠΈΡΡ ΠΏΠΎΡΡΡ
export const getPagesArray = (totalPages: number) => {
let result: number[] = [];
for (let i = 0; i < totalPages; i++) {
result.push(i + 1);
}
return result;
};
Π£ΠΆΠ΅ Π² ΠΎΡΠ½ΠΎΠ²Π½ΠΎΠΌ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ΅ ΡΡΡΠ°Π½ΠΈΡΡ ΠΌΡ ΡΠΎΠ·Π΄Π°ΡΠΌ ΡΡΠΈ Π½ΠΎΠ²ΡΡ ΡΠΎΡΡΠΎΡΠ½ΠΈΡ, ΠΊΠΎΡΠΎΡΡΠ΅ Π±ΡΠ΄ΡΡ ΠΎΡΠ²Π΅ΡΠ°ΡΡ Π·Π° ΠΎΠ±ΡΠ΅Π΅ ΠΊΠΎΠ»ΠΈΡΠ΅ΡΡΠ²ΠΎ ΡΡΡΠ°Π½ΠΈΡ, Π»ΠΈΠΌΠΈΡ ΠΏΠΎΡΡΠΎΠ² Π½Π° ΡΡΡΠ°Π½ΠΈΡΠ΅ ΠΈ Π·Π° ΡΠ°ΠΌΡ ΡΡΡΠ°Π½ΠΈΡΡ.
Π Ρ
ΡΠΊΠ΅ useFetching
ΠΌΡ ΡΠ°ΠΊ ΠΆΠ΅ ΠΏΠΎΠ»ΡΡΠ°Π΅ΠΌ Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΎΠΊ ΠΎΡ ΠΎΡΠ²Π΅ΡΠ° ΠΈ Π²ΡΠ·ΡΠ²Π°Π΅ΠΌ ΡΡΠ½ΠΊΡΠΈΡ getPageCount
, ΠΊΠΎΡΠΎΡΠ°Ρ Π΄Π΅Π»ΠΈΡ Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΎΠΊ Π½Π° Π»ΠΈΠΌΠΈΡ ΠΈ ΠΏΠΎΠ»ΡΡΠ°Π΅Ρ ΠΊΠΎΠ»ΠΈΡΠ΅ΡΡΠ²ΠΎ ΡΡΡΠ°Π½ΠΈΡ, Π° ΡΠΆΠ΅ Π΄Π°Π»ΡΡΠ΅ setTotalPages
ΡΡΡΠ°Π½Π°Π²Π»ΠΈΠ²Π°Π΅Ρ ΡΡΠΎ ΠΊΠΎΠ»ΠΈΡΠ΅ΡΡΠ²ΠΎ Π² ΠΊΠ°ΡΠ΅ΡΡΠ²Π΅ ΠΊΠΎΠ»ΠΈΡΠ΅ΡΡΠ²Π° ΡΡΡΠ°Π½ΠΈΡ.
ΠΠΎΡΠΎΠΌ Π² pagesArray
ΠΌΡ ΠΏΠΎΠ»ΡΡΠ°Π΅ΠΌ ΠΌΠ°ΡΡΠΈΠ² ΡΡΡΠ°Π½ΠΈΡ (1, 2, 3β¦).
Π ΡΠΆΠ΅ Π² ΡΡΠ½ΠΊΡΠΈΠΈ changePage
ΠΌΡ ΠΎΡΡΡΠ΅ΡΡΠ²Π»ΡΠ΅ΠΌ ΡΠ΅ΡΡΠΈΠ½Π³ Π½ΠΎΠ²ΡΡ
ΠΏΠΎΡΡΠΎΠ² Π½Π° ΡΡΡΠ°Π½ΠΈΡΠ΅. ΠΡΡ ΡΡΠ½ΠΊΡΠΈΡ Π²ΡΠ·ΡΠ²Π°Π΅Ρ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ Button
.
ΠΠ½ΠΎΠΏΠΊΠΈ ΡΠ΅Π½Π΄Π΅ΡΡΡΡΡ Π² Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠΈ ΠΎΡ ΠΊΠΎΠ»ΠΈΡΠ΅ΡΡΠ²Π° ΡΠ»Π΅ΠΌΠ΅Π½ΡΠΎΠ² Π² ΠΌΠ°ΡΡΠΈΠ²Π΅ pagesArray
.
page-components / Posts.tsx
export const Posts = () => {
const [posts, setPosts] = useState('');
const [filter, setFilter] = useState<IFilter>({ query: '', sort: 'title' });
const [modal, setModal] = useState(false);
// ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅, ΠΊΠΎΡΠΎΡΠΎΠ΅ Ρ
ΡΠ°Π½ΠΈΡ ΠΎΠ±ΡΠ΅Π΅ ΠΊΠΎΠ»ΡΠΈΠ΅ΡΡΠ²ΠΎ ΠΏΠΎΡΡΠΎΠ²
const [totalPages, setTotalPages] = useState<number>(0);
// ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ Π»ΠΈΠΌΠΈΡΠ° ΠΏΠΎΡΡΠΎΠ²
const [limit, setLimit] = useState<number>(10);
// ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ ΡΡΡΠ°Π½ΠΈΡΡ ΠΏΠΎΡΡΠΎΠ²
const [page, setPage] = useState<number>(1);
const [fetchPosts, isPostLoading, postsError] = useFetching(async () => {
const response = await PostService.getAll(limit, page);
setPosts(response.data);
// ΠΎΠ±ΡΠ΅Π΅ ΠΊΠΎΠ»ΠΈΡΠ΅ΡΡΠ²ΠΎ ΠΏΠΎΡΡΠΎΠ² ΠΏΠΎΠ»ΡΡΠ°Π΅ΠΌ ΠΈΠ· Ρ
Π΅Π΄Π΅ΡΠ° Π·Π°ΠΏΡΠΎΡΠ°
const totalCount = response.headers['x-total-count'];
// ΠΏΠΎΠ»ΡΡΠ°Π΅ΠΌ ΠΎΠ±ΡΠ΅Π΅ ΠΊΠΎΠ»ΠΈΡΠ΅ΡΡΠ²ΠΎ ΡΡΡΠ°Π½ΠΈΡ
setTotalPages(getPageCount(totalCount, limit));
});
// ΠΏΠΎΠ»ΡΡΠ°Π΅ΠΌ ΠΌΠ°ΡΡΠΈΠ² Π½ΠΎΠΌΠ΅ΡΠΎΠ² ΡΡΡΠ°Π½ΠΈΡ
let pagesArray: number[] = getPagesArray(totalPages);
// ΡΡΡ ΠΌΡ Π±ΡΠ΄Π΅ΠΌ ΡΡΡΠ°Π½Π°Π²Π»ΠΈΠ²Π°ΡΡ Π² ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ Π²ΡΠ±ΡΠ°Π½Π½ΡΡ ΡΡΡΠ°Π½ΠΈΡΡ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Ρ
const changePage = (page: number) => {
setPage(page);
fetchPosts();
};
useEffect(() => {
fetchPosts();
}, []);
/// CODE ...
return (
<div className={styles.wrapper}>
<Button className={styles.button} buttonType={'purple'} onClick={() => setModal(true)}>
Π‘ΠΎΠ·Π΄Π°ΡΡ ΠΏΠΎΡΡ
</Button>
<Modal visible={modal} setVisible={setModal}>
<PostForm create={createPost} />
</Modal>
<PostFilter filter={filter} setFilter={setFilter} />
{postsError && <h1>ΠΡΠΎΠΈΠ·ΠΎΡΠ»Π° ΠΎΡΠΈΠ±ΠΊΠ° {postsError}</h1>}
{isPostLoading ? (
<div className={styles.loadPosition}>
<Loader>ΠΠ΄ΡΡ Π·Π°Π³ΡΡΠ·ΠΊΠ°...</Loader>
</div>
) : (
<PostList
className={styles.list}
posts={sortedAndSearchedPosts}
remove={removePost}
/>
)}
{/* ΡΡΡ ΡΠΆΠ΅ ΠΌΡ Π²ΡΠ²ΠΎΠ΄ΠΈΠΌ ΠΊΠ½ΠΎΠΏΠΊΠΈ ΡΠΎ ΡΡΡΠ°Π½ΠΈΡΠ°ΠΌΠΈ */}
<div className={styles.buttonBlock}>
{pagesArray.map(p => (
<Button
onClick={() => changePage(p)}
key={p}
className={cn(styles.buttonPage, {
[styles.buttonPage__current]: page === p,
})}
buttonType={'gray'}
>
{p}
</Button>
))}
</div>
</div>
);
};
ΠΡ ΡΠ΅Π°Π»ΠΈΠ·ΠΎΠ²Π°Π»ΠΈ ΠΏΠΎΠ΄Π³ΡΡΠ·ΠΊΡ Π½ΠΎΠ²ΡΡ ΠΏΠΎΡΡΠΎΠ² Π½Π° ΡΡΡΠ°Π½ΠΈΡΠ΅, Π½ΠΎ ΡΡΡ ΠΌΡ ΡΡΠΎΠ»ΠΊΠ½ΡΠ»ΠΈΡΡ Ρ ΠΏΡΠΎΠ±Π»Π΅ΠΌΠΎΠΉ, ΡΡΠΎ ΠΏΡΠΈ ΠΏΠ΅ΡΠ΅Ρ ΠΎΠ΄Π΅ Π½Π° ΡΠ°Π·Π½ΡΠ΅ ΡΡΡΠ°Π½ΠΈΡΡ, Ρ Π½Π°Ρ Π·Π°Π³ΡΡΠΆΠ°Π΅ΡΡΡ ΠΏΡΠΎΡΠ»Π°Ρ Π²ΡΠ±ΡΠ°Π½Π½Π°Ρ ΡΡΡΠ°Π½ΠΈΡΠ°
02:06:20 β ΠΠ±ΡΡΡΠ½Π΅Π½ΠΈΠ΅ ΠΌΠ΅Ρ Π°Π½ΠΈΠ·ΠΌΠ° ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΡ ΡΠΎΡΡΠΎΡΠ½ΠΈΡ
Π’ΡΡ Π½ΡΠΆΠ½ΠΎ ΡΠΊΠ°Π·Π°ΡΡ, ΡΡΠΎ Ρ
ΡΠΊΠΈ - ΡΡΠΎ Π°ΡΠΈΠ½Ρ
ΡΠΎΠ½Π½ΡΠΉ ΠΏΡΠΎΡΠ΅ΡΡ, ΠΊΠΎΡΠΎΡΡΠΉ ΠΏΡΠΎΠΈΡΡ
ΠΎΠ΄ΠΈΡ Π²Π½ΡΡΡΠΈ ΡΠ΅Π°ΠΊΡΠ° Π½Π΅ ΡΡΠ°Π·Ρ. Π€ΡΠ½ΠΊΡΠΈΠΈ ΠΏΠΎ ΡΠΈΠΏΡ ΡΠ΅ΡΡΠ΅ΡΠΎΠ² useState
ΠΊΠΎΠΏΡΡΡΡ ΠΈ Π²ΡΠΏΠΎΠ»Π½ΡΡΡΡΡ ΡΠ°Π·ΠΎΠΌ, ΠΎΡΡΠ΅Π³ΠΎ ΠΈ Π½Π°ΡΠ΅ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠ΅ ΡΡΡΠ°Π½ΠΈΡ Π²ΡΡΠ΅ ΠΈ Π½Π΅ ΡΠ°Π±ΠΎΡΠ°Π΅Ρ ΡΡΠ°Π·Ρ. ΠΡΠΎ ΡΠ΄Π΅Π»Π°Π½ΠΎ Π΄Π»Ρ ΡΠΎΠ³ΠΎ, ΡΡΠΎΠ±Ρ ΠΈΠ·Π±Π΅ΠΆΠ°ΡΡ ΠΏΠΎΠ²ΡΠΎΡΠ½ΡΡ
ΠΌΠ°Π½ΠΈΠΏΡΠ»ΡΡΠΈΠΉ Ρ DOM-Π΄Π΅ΡΠ΅Π²ΠΎΠΌ
Π‘Π°ΠΌΡΠΉ ΠΏΡΠΎΡΡΠΎΠΉ ΡΠΏΠΎΡΠΎΠ± ΡΠ΅ΡΠΈΡΡ Π΄Π°Π½Π½ΡΡ ΠΏΡΠΎΠ±Π»Π΅ΠΌΡ - ΡΡΠΎ Π·Π°ΠΊΠΈΠ½ΡΡΡ ΠΈΠ·ΠΌΠ΅Π½ΡΡΡΠ΅Π΅ΡΡ Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅ Π² useEffect
ΠΈ ΠΈΠ·ΠΌΠ΅Π½ΡΡΡ ΡΡΡΠ°Π½ΠΈΡΡ ΡΠΎΠ»ΡΠΊΠΎ ΡΠ΅ΡΠ΅Π· Π½Π΅Π³ΠΎ.
Π ΡΠ΅ΠΏΠ΅ΡΡ ΠΌΠΎΠΆΠ½ΠΎ ΠΈΠ· ΡΡΠ½ΠΊΡΠΈΠΈ changePage
ΡΠ±ΡΠ°ΡΡ ΡΡΠ½ΠΊΡΠΈΡ ΡΠ΅ΡΡΠΈΠ½Π³Π° ΠΏΠΎΡΡΠΎΠ², ΡΠ°ΠΊ ΠΊΠ°ΠΊ ΡΡΠΎ Π΄Π΅Π»Π°Π΅Ρ useEffect
.
page-components / Posts.tsx
const changePage = (page: number) => {
setPage(page);
};
useEffect(() => {
fetchPosts();
}, [page]);
Π ΡΠ°ΠΊ ΠΆΠ΅ ΠΌΠΎΠΆΠ½ΠΎ Π΄Π΅ΠΊΠΎΠΌΠΏΠΎΠ·ΠΈΡΠΎΠ²Π°ΡΡ ΠΏΠΎΡΡΡΠ°Π½ΠΈΡΠ½ΡΠΉ Π²ΡΠ²ΠΎΠ΄ Π² ΠΎΡΠ΄Π΅Π»ΡΠ½ΡΠΉ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ.
ΠΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ ΠΏΠ°Π³ΠΈΠ½Π°ΡΠΈΠΈ Π±ΡΠ΄Π΅Ρ Π² ΡΠ΅Π±Ρ ΠΏΡΠΈΠ½ΠΈΠΌΠ°ΡΡ ΠΎΠ±ΡΠ΅Π΅ ΠΊΠΎΠ»ΠΈΡΠ΅ΡΡΠ²ΠΎ ΡΡΡΠ°Π½ΠΈΡ, ΡΡΡΠ°Π½ΠΈΡΡ, Π½Π° ΠΊΠΎΡΠΎΡΠΎΠΉ ΠΌΡ ΡΠ΅ΠΉΡΠ°Ρ Π½Π°Ρ ΠΎΠ΄ΠΈΠΌΡΡ ΠΈ ΡΡΠ½ΠΊΡΠΈΡ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΡ ΡΡΡΠ°Π½ΠΈΡΡ
components / Pagination / Pagination.props.ts
import { DetailedHTMLProps, HTMLAttributes, ReactNode } from 'react';
export interface IPaginationProps
extends DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
totalPages: number;
page: number;
changePage: (page: number) => void;
}
Π ΡΠ°ΠΌ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ ΡΠ°ΠΊ ΠΆΠ΅ ΠΏΠ΅ΡΠ΅Π½Π΅ΡΡΠΌ Π²ΡΠ·ΠΎΠ² ΡΡΠ½ΠΊΡΠΈΠΈ ΠΏΠΎΠ»ΡΡΠ΅Π½ΠΈΡ ΠΌΠ°ΡΡΠΈΠ²Π° ΡΡΡΠ°Π½ΠΈΡ
components / Pagination / Pagination.tsx
export const Pagination = ({ totalPages, page, changePage }: IPaginationProps) => {
let pagesArray: number[] = getPagesArray(totalPages);
return (
<div className={styles.buttonBlock}>
{pagesArray.map(p => (
<Button
onClick={() => changePage(p)}
key={p}
className={cn(styles.buttonPage, {
[styles.buttonPage__current]: page === p,
})}
buttonType={'gray'}
>
{p}
</Button>
))}
</div>
);
};
Π ΠΎΠ΄ΠΈΡΠ΅Π»ΡΡΠΊΠΈΠΉ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ Π½Π° Π΄Π°Π½Π½ΡΠΉ ΠΌΠΎΠΌΠ΅Π½Ρ Π²ΡΠ³Π»ΡΠ΄ΠΈΡ ΠΏΠΎΠ΄ΠΎΠ±Π½ΡΠΌ ΠΎΠ±ΡΠ°Π·ΠΎΠΌ:
page-components / Posts.tsx
export const Posts = () => {
const [posts, setPosts] = useState('');
const [filter, setFilter] = useState<IFilter>({ query: '', sort: 'title' });
const [modal, setModal] = useState(false);
// ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅, ΠΊΠΎΡΠΎΡΠΎΠ΅ Ρ
ΡΠ°Π½ΠΈΡ ΠΎΠ±ΡΠ΅Π΅ ΠΊΠΎΠ»ΡΠΈΠ΅ΡΡΠ²ΠΎ ΠΏΠΎΡΡΠΎΠ²
const [totalPages, setTotalPages] = useState<number>(0);
// ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ Π»ΠΈΠΌΠΈΡΠ° ΠΏΠΎΡΡΠΎΠ²
const [limit, setLimit] = useState<number>(10);
// ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ ΡΡΡΠ°Π½ΠΈΡΡ ΠΏΠΎΡΡΠΎΠ²
const [page, setPage] = useState<number>(1);
const [fetchPosts, isPostLoading, postsError] = useFetching(async () => {
const response = await PostService.getAll(limit, page);
setPosts(response.data);
// ΠΎΠ±ΡΠ΅Π΅ ΠΊΠΎΠ»ΠΈΡΠ΅ΡΡΠ²ΠΎ ΠΏΠΎΡΡΠΎΠ² ΠΏΠΎΠ»ΡΡΠ°Π΅ΠΌ ΠΈΠ· Ρ
Π΅Π΄Π΅ΡΠ° Π·Π°ΠΏΡΠΎΡΠ°
const totalCount = response.headers['x-total-count'];
// ΠΏΠΎΠ»ΡΡΠ°Π΅ΠΌ ΠΎΠ±ΡΠ΅Π΅ ΠΊΠΎΠ»ΠΈΡΠ΅ΡΡΠ²ΠΎ ΡΡΡΠ°Π½ΠΈΡ
setTotalPages(getPageCount(totalCount, limit));
});
// ΡΡΡ ΠΌΡ Π±ΡΠ΄Π΅ΠΌ ΡΡΡΠ°Π½Π°Π²Π»ΠΈΠ²Π°ΡΡ Π² ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ Π²ΡΠ±ΡΠ°Π½Π½ΡΡ ΡΡΡΠ°Π½ΠΈΡΡ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Ρ
const changePage = (page: number) => {
setPage(page);
};
useEffect(() => {
fetchPosts();
}, [page]);
const sortedAndSearchedPosts = usePosts(posts, filter.sort, filter.query);
const createPost = (newPost: IPost): void => {
setPosts([...posts, newPost]);
setModal(false);
};
const removePost = (post: IPost): void => {
setPosts(posts.filter(p => p.id !== post.id));
};
return (
<div className={styles.wrapper}>
<Button className={styles.button} buttonType={'purple'} onClick={() => setModal(true)}>
Π‘ΠΎΠ·Π΄Π°ΡΡ ΠΏΠΎΡΡ
</Button>
<Modal visible={modal} setVisible={setModal}>
<PostForm create={createPost} />
</Modal>
<PostFilter filter={filter} setFilter={setFilter} />
{postsError && <h1>ΠΡΠΎΠΈΠ·ΠΎΡΠ»Π° ΠΎΡΠΈΠ±ΠΊΠ° {postsError}</h1>}
{isPostLoading ? (
<div className={styles.loadPosition}>
<Loader>ΠΠ΄ΡΡ Π·Π°Π³ΡΡΠ·ΠΊΠ°...</Loader>
</div>
) : (
<PostList
className={styles.list}
posts={sortedAndSearchedPosts}
remove={removePost}
/> )}
<Pagination totalPages={totalPages} page={page} changePage={changePage} />
</div>
);
};
02:12:00 β React router. ΠΠΎΡΡΡΠ°Π½ΠΈΡΠ½Π°Ρ Π½Π°Π²ΠΈΠ³Π°ΡΠΈΡ. BrowserRouter, Route, Switch, Redirect
Π£ΡΡΠ°Π½Π°Π²Π»ΠΈΠ²Π°Π΅ΠΌ ΡΠΎΡΡΠ΅Ρ Π΄Π»Ρ ΡΠ΅Π°ΠΊΡΠ°
npm iΒ react-router-dom
Π’Π΅ΠΏΠ΅ΡΡ ΠΌΡ Π½Π΅ΠΌΠ½ΠΎΠ³ΠΎ ΠΏΠ΅ΡΠ΅ΡΡΡΡΠΊΡΡΡΠΈΡΡΠ΅ΠΌ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅:
- Π‘ΠΎΠ·Π΄Π°Π΄ΠΈΠΌ ΠΏΠ°ΠΏΠΊΡ
pages
, ΠΊΠΎΡΠΎΡΠ°Ρ Π±ΡΠ΄Π΅Ρ Ρ ΡΠ°Π½ΠΈΡΡ Π½Π΅ ΠΎΡΠ΄Π΅Π»ΡΠ½ΡΠ΅ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΡ, Π° ΡΠ΅Π»ΡΠ΅ ΡΡΡΠ°Π½ΠΈΡΡ, ΠΊΠΎΡΠΎΡΡΠ΅ Π±ΡΠ΄ΡΡ ΡΠ°ΡΠΏΠΎΠ»Π°Π³Π°ΡΡΡΡ Π² ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΈ - ΠΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ
App
, ΠΊΠΎΡΠΎΡΡΠΉ ΡΠ²Π»ΡΠ΅ΡΡΡ ΡΡΠ°ΡΡΠΎΠ²ΠΎΠΉ ΡΠΎΡΠΊΠΎΠΉ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ, Π±ΡΠ΄Π΅Ρ Ρ ΡΠ°Π½ΠΈΡΡ Π² ΡΠ΅Π±Π΅BrowserRouter
, ΠΊΠΎΡΠΎΡΡΠΉ Π±ΡΠ΄Π΅Ρ ΠΎΡΡΠ»Π΅ΠΆΠΈΠ²Π°ΡΡ Π²ΡΠ΅ ΡΠΎΡΡΡ Π² ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΈ - Π§ΡΠΎΠ±Ρ ΠΎΠΏΡΠ΅Π΄Π΅Π»ΠΈΡΡ ΡΡΡΠ°Π½ΠΈΡΡ Π² ΠΊΠ°ΡΠ΅ΡΡΠ²Π΅ ΠΎΡΠ΄Π΅Π»ΡΠ½ΠΎΠ³ΠΎ ΡΠΎΡΡΠ°, Π½ΡΠΆΠ½ΠΎ ΠΏΠΎΠΌΠ΅ΡΡΠΈΡΡ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ ΡΡΡΠ°Π½ΠΈΡΡ Π² ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ ΠΎΡΠ΄Π΅Π»ΡΠ½ΠΎΠ³ΠΎ ΡΠΎΡΡΠ°
Route
. Π‘Π°ΠΌRoute
Π²Π½ΡΡΡΡ ΡΠ΅Π±Ρ ΠΏΡΠΈΠ½ΠΈΠΌΠ°Π΅Ρ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ Π΄Π»Ρ ΡΠΎΡΡΠΈΠ½Π³Π° ΠΈ ΠΏΡΡΡ, ΠΏΠΎ ΠΊΠΎΡΠΎΡΠΎΠΌΡ ΡΡΡΠ°Π½ΠΈΡΠ° Π΄ΠΎΠ»ΠΆΠ½Π° ΠΎΡΡΠΈΡΠΎΠ²ΡΠ²Π°ΡΡΡΡ
Π’Π΅ΠΏΠ΅ΡΡ, ΡΡΠΎΠ±Ρ ΡΠ΅Π°Π»ΠΈΠ·ΠΎΠ²Π°ΡΡ ΠΏΠΎΠ»Π½ΠΎΡΠ΅Π½Π½ΡΠΉ ΡΠΎΡΡΠΈΠ½Π³ ΠΈ ΠΏΠ΅ΡΠ΅Ρ
ΠΎΠ΄ ΠΌΠ΅ΠΆΠ΄Ρ ΡΡΡΠ°Π½ΠΈΡΠ°ΠΌΠΈ, Π½ΡΠΆΠ½ΠΎ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ Link
, ΠΊΠΎΡΠΎΡΡΠΉ ΠΏΡΠΈΠ½ΠΈΠΌΠ°Π΅Ρ ΠΏΡΡΡ to
, ΠΊΠΎΡΠΎΡΡΠΉ, Π² ΡΠ²ΠΎΡ ΠΎΡΠ΅ΡΠ΅Π΄Ρ, ΠΎΠΏΡΠ΅Π΄Π΅Π»ΡΠ΅Ρ, ΠΊΡΠ΄Π° Π½ΡΠΆΠ½ΠΎ ΠΏΠ΅ΡΠ΅ΠΉΡΠΈ Π½Π° ΡΡΡΠ°Π½ΠΈΡΠ΅.
Π£ΠΆΠ΅ ΠΏΡΠ΅Π΄ΡΡΠ°Π²Π»Π΅Π½Π½Π°Ρ ΡΠ΅Π°Π»ΠΈΠ·Π°ΡΠΈΡ ΠΏΠΎΠ·Π²ΠΎΠ»ΠΈΡ Π½Π°ΠΌ ΠΏΠ΅ΡΠ΅Ρ ΠΎΠ΄ΠΈΡΡ ΡΠΎ ΡΡΡΠ°Π½ΠΈΡΡ Π½Π° ΡΡΡΠ°Π½ΠΈΡΡ Π±Π΅Π· ΠΏΠ΅ΡΠ΅Π·Π°Π³ΡΡΠ·ΠΊΠΈ ΡΡΡΠ°Π½ΠΈΡΡ
ΠΠ°Π»Π΅Π΅, ΡΡΠΎΠ±Ρ ΠΌΡ ΠΌΠΎΠ³Π»ΠΈ ΠΏΠ΅ΡΠ΅ΠΌΠ΅ΡΠ°ΡΡΡΡ ΠΏΠΎ ΡΠΎΡΡΠ°ΠΌ ΠΈ ΠΎΠ±ΡΠ°Π±Π°ΡΡΠ²Π°ΡΡ Π½Π΅ΡΡΡΠ΅ΡΡΠ²ΡΡΡΠΈΠ΅ ΡΡΡΠ°Π½ΠΈΡΡ, Π½ΡΠΆΠ½ΠΎ Π΄ΠΎΠ±Π°Π²ΠΈΡΡ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ Switch
, Π² ΠΊΠΎΡΠΎΡΠΎΠΌ ΠΏΠ΅ΡΠ΅Π΄Π°ΡΡ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ Redirect
, ΠΊΠΎΡΠΎΡΡΠΉ ΡΠΆΠ΅ Π±ΡΠ΄Π΅Ρ ΠΏΠ΅ΡΠ΅Π½Π°ΠΏΡΠ°Π²Π»ΡΡΡ Π½Π°Ρ Π½Π° Π΄ΡΡΠ³ΡΡ ΡΡΡΠ°Π½ΠΈΡΡ, Π΅ΡΠ»ΠΈ ΡΠ°, Π½Π° ΠΊΠΎΡΠΎΡΡΡ ΠΌΡ ΠΏΠ΅ΡΠ΅Ρ
ΠΎΠ΄ΠΈΠΌ, Π½Π΅ Π±ΡΠ΄Π΅Ρ ΡΡΡΠ΅ΡΡΠ²ΠΎΠ²Π°ΡΡ
Π ΡΠ°ΠΊ Π²ΡΠ³Π»ΡΠ΄ΠΈΡ ΡΡΡΠ°Π½ΠΈΡΠ° ΠΎΡΠΈΠ±ΠΊΠΈ ΠΏΡΠΈ ΠΏΠ΅ΡΠ΅Ρ ΠΎΠ΄Π΅ Π½Π΅ Π½Π΅ΡΡΡΠ΅ΡΡΠ²ΡΡΡΡΡ ΡΡΡΠ°Π½ΠΈΡΡ
pages > Error.jsx
import React from 'react';
const Error = () => {
return (
<div>
<h1 style={{color: 'red'}}>
ΠΡ ΠΏΠ΅ΡΠ΅ΡΠ»ΠΈ Π½Π° Π½Π΅ΡΡΡΠ΅ΡΡΠ²ΡΡΡΡΡ ΡΡΡΠ°Π½ΠΈΡΡ!
</h1>
</div>
);
};
export default Error;
Π’Π°ΠΊ ΠΆΠ΅ ΠΌΠΎΠΆΠ½ΠΎ ΠΏΠ΅ΡΠ΅Π½Π΅ΡΡΠΈ Π²ΡΡ Π»ΠΎΠ³ΠΈΠΊΡ ΡΠΎΡΡΠΈΠ½Π³Π° Π² ΠΎΡΠ΄Π΅Π»ΡΠ½ΡΠΉ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ
02:22:00 β ΠΠΈΠ½Π°ΠΌΠΈΡΠ΅ΡΠΊΠ°Ρ Π½Π°Π²ΠΈΠ³Π°ΡΠΈΡ. useHistory, useParams. ΠΠ°Π³ΡΡΠ·ΠΊΠ° ΠΊΠΎΠΌΠΌΠ΅Π½ΡΠ°ΡΠΈΠ΅Π² ΠΊ ΠΏΠΎΡΡΡ
Π Π΄Π°Π»Π΅Π΅ Π½Π°ΠΌ Π½ΡΠΆΠ½ΠΎ ΡΠ΅Π°Π»ΠΈΠ·ΠΎΠ²Π°ΡΡ ΡΠΎΡΡΡ ΠΏΠΎΠ΄ ΠΊΠ°ΠΆΠ΄ΡΠΉ ΠΎΡΠ΄Π΅Π»ΡΠ½ΡΠΉ ΠΏΠΎΡΡ, ΡΡΠΎΠ±Ρ ΠΌΠΎΠΆΠ½ΠΎ Π±ΡΠ»ΠΎ ΠΏΡΠΎΡΠΌΠΎΡΡΠ΅ΡΡ ΠΏΠΎ Π½Π΅ΠΌΡ Π΄Π΅ΡΠ°Π»ΡΠ½ΡΡ ΠΈΠ½ΡΠΎΡΠΌΠ°ΡΠΈΡ
Π’ΡΡ Π½ΡΠΆΠ½ΠΎ ΡΠΆΠ΅ Π²ΠΎΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡΡΡ Ρ
ΡΠΊΠΎΠΌ useHistory
, ΠΊΠΎΡΠΎΡΡΠΉ ΠΏΡΠ΅Π΄ΠΎΡΡΠ°Π²Π»ΡΠ΅Ρ ΡΠ΅Π°ΠΊΡ-ΡΠΎΡΡΠ΅Ρ-Π΄ΠΎΠΌ. ΠΡΠΎΡ Ρ
ΡΠΊ ΠΏΠΎΠ·Π²ΠΎΠ»ΡΠ΅Ρ Π½Π°ΠΌ ΡΠ΅Π°Π»ΠΈΠ·ΠΎΠ²Π°ΡΡ ΠΏΠ΅ΡΠ΅Ρ
ΠΎΠ΄ ΠΏΠΎ ΡΡΡΠ°Π½ΠΈΡΠ°ΠΌ Π±Π΅Π· ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ° Link
. ΠΠΎΠ½ΠΊΡΠ΅ΡΠ½ΠΎ ΠΌΡ ΠΌΠΎΠΆΠ΅ΠΌ Π²ΠΎΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡΡΡ ΠΌΠ΅ΡΠΎΠ΄ΠΎΠΌ push
Π΄Π»Ρ Π³Π΅Π½Π΅ΡΠ°ΡΠΈΠΈ ΡΠΎΡΡΠΎΠ² ΠΏΠΎ Π½Π°ΡΠΈΠΌ ΠΏΠΎΡΡΠ°ΠΌ. ΠΠ΅ΡΠ΅Ρ
ΠΎΠ΄ Π±ΡΠ΄Π΅Ρ ΠΎΡΡΡΠ΅ΡΡΠ²Π»ΡΡΡΡΡ Π±Π΅Π· ΠΏΠΎΠΌΠΎΡΠΈ ΡΡΡΠ»ΠΎΠΊ - ΠΌΡ Π½Π°ΠΆΠ°Π»ΠΈ Π½Π° ΠΊΠ½ΠΎΠΏΠΊΡ ΠΈ ΠΏΠ΅ΡΠ΅ΡΠ»ΠΈ Π½Π° Π½ΡΠΆΠ½ΡΡ ΡΡΡΠ°Π½ΠΈΡΡ
components / PostItem.jsx
const PostItem = (props) => {
const router = useHistory()
return (
<div className="post">
<div className="post__content">
<strong>{props.post.id}. {props.post.title}</strong>
<div>
{props.post.body}
</div>
</div>
<div className="post__btns">
<MyButton onClick={() => router.push(`/posts/${props.post.id}`)}>
ΠΡΠΊΡΡΡΡ
</MyButton>
<MyButton onClick={() => props.remove(props.post)}>
Π£Π΄Π°Π»ΠΈΡΡ
</MyButton>
</div>
</div>
);
};
Π ΡΠ΅ΠΉΡΠ°Ρ Π½Π°Ρ ΡΠΎΡΡ Π·Π°Π½ΠΎΡΠΈΡΡΡ Π² ΠΏΠΎΠΈΡΠΊΠΎΠ²ΡΡ ΡΡΡΠΎΠΊΡ, Π½ΠΎ Π½ΠΈΡΠ΅Π³ΠΎ Π½Π΅ ΠΏΡΠΎΠΈΡΡ ΠΎΠ΄ΠΈΡ, ΡΠ°ΠΊ ΠΊΠ°ΠΊ ΡΠΎΡΡ ΠΏΠΎΡΡΠ° Π½Π΅ Π±ΡΠ» ΡΠΎΠ·Π΄Π°Π½
Π‘ΠΎΠ·Π΄Π°Π΄ΠΈΠΌ Π²ΡΠ΅ΠΌΠ΅Π½Π½ΡΡ ΡΡΡΠ°Π½ΠΈΡΡ ΠΎΡΠ΄Π΅Π»ΡΠ½ΠΎΠ³ΠΎ ΠΏΠΎΡΡΠ°
Π Π΄Π°Π»Π΅Π΅ Π² ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ΅ Ρ ΡΠΎΡΡΠ°ΠΌΠΈ Π½ΡΠΆΠ½ΠΎ ΡΠ΅Π°Π»ΠΈΠ·ΠΎΠ²Π°ΡΡ ΠΏΠ΅ΡΠ΅Ρ
ΠΎΠ΄ Π½Π° Π΄ΠΈΠ½Π°ΠΌΠΈΡΠ΅ΡΠΊΡΡ ΡΡΡΠ°Π½ΠΈΡΡ. Π§ΡΠΎΠ±Ρ ΡΠΊΠ°Π·Π°ΡΡ, ΡΡΠΎ ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡ ΡΡΡΠ°Π½ΠΈΡΡ Π΄ΠΈΠ½Π°ΠΌΠΈΡΠ΅ΡΠΊΠΈΠΉ, Π½ΡΠΆΠ½ΠΎ Π² ΡΡΡΠ»ΠΊΠ΅ ΡΠΊΠ°Π·Π°ΡΡ :id
Π΄Π²ΠΎΠ΅ΡΠΎΡΠΈΠ΅.
Π’Π°ΠΊ ΠΆΠ΅ Ρ Π½Π°Ρ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΡΡΡ Π΄Π²Π° ΡΠΎΡΡΠ°, ΠΊΠΎΡΠΎΡΡΠ΅ Π½Π°ΡΠΈΠ½Π°ΡΡΡΡ Π½Π° /posts
. Π§ΡΠΎΠ±Ρ ΠΈΠ·Π±Π΅ΠΆΠ°ΡΡ ΠΊΠΎΠ½ΡΠ»ΠΈΠΊΡΠΎΠ², Π½ΡΠΆΠ½ΠΎ ΠΏΠ΅ΡΠ΅Π΄Π°ΡΡ Π°ΡΡΠΈΠ±ΡΡ exact
ΠΠ°Π»Π΅Π΅ Π½Π°ΠΌ Π½ΡΠΆΠ½ΠΎ ΠΏΠΎΠ΄Π³ΡΡΠΆΠ°ΡΡ ΠΈΠ½ΡΠΎΡΠΌΠ°ΡΠΈΡ ΠΏΠΎ ΠΏΠΎΡΡΡ (Π΅Π³ΠΎ ΠΈΠΌΡ, ΡΠ΅ΠΊΡΡ ΠΈ ΡΠ°ΠΊ Π΄Π°Π»Π΅Π΅)
ΠΠ΅ΡΠ²ΡΠΌ Π΄Π΅Π»ΠΎΠΌ, Π½ΡΠΆΠ½ΠΎ Π΄ΠΎΠ±Π°Π²ΠΈΡΡ Π² API ΠΌΠ΅ΡΠΎΠ΄, ΠΊΠΎΡΠΎΡΡΠΉ Π±ΡΠ΄Π΅Ρ ΠΏΠΎΠ»ΡΡΠ°ΡΡ ΠΎΠ΄ΠΈΠ½ ΠΎΠΏΡΠ΅Π΄Π΅Π»ΡΠ½Π½ΡΠΉ ΠΏΠΎΡΡ Ρ ΡΠ΅ΡΠ²Π΅ΡΠ° getById
ΠΈ Π΅Π³ΠΎ ΠΊΠΎΠΌΠΌΠ΅Π½ΡΠ°ΡΠΈΠΈ getCommentsByPostId
, ΠΊΠΎΡΠΎΡΡΠ΅ Π±ΡΠ΄ΡΡ ΠΏΠΎΠ»ΡΡΠ°ΡΡ id
ΠΏΠΎΡΡΠ°
src > API > PostService.js
import axios from "axios";
export default class PostService {
static async getAll(limit = 10, page = 1) {
const response = await axios.get('https://jsonplaceholder.typicode.com/posts', {
params: {
_limit: limit,
_page: page
}
})
return response;
}
static async getById(id) {
const response = await axios.get('https://jsonplaceholder.typicode.com/posts/' + id)
return response;
}
static async getCommentsByPostId(id) {
const response = await axios.get(`https://jsonplaceholder.typicode.com/posts/${id}/comments`)
return response;
}
}
Π‘Π΅ΠΉΡΠ°Ρ Π²ΡΠ΅ΠΌΠ΅Π½Π½ΡΡ ΡΡΡΠ°Π½ΠΈΡΡ ΠΏΠΎΡΡΠ° ΠΏΠ΅ΡΠ΅Π΄Π΅Π»Π°Π΅ΠΌ ΠΏΠΎΠ΄ ΠΏΠΎΠ»Π½ΠΎΡΠ΅Π½Π½ΡΡ ΡΡΡΠ°Π½ΠΈΡΡ, ΠΊΠΎΡΠΎΡΠ°Ρ Π±ΡΠ΄Π΅Ρ ΠΏΠΎΠ»ΡΡΠ°ΡΡ ΠΈΠ½ΡΠΎΡΠΌΠ°ΡΠΈΡ ΠΏΠΎ ΠΏΠΎΡΡΡ Ρ ΡΠ΅ΡΠ²Π΅ΡΠ°
ΠΠ°Π»Π΅Π΅ ΠΈΠ΄ΡΡ Ρ
ΡΠΊ useParams
, ΠΊΠΎΡΠΎΡΡΠΉ ΡΠΎΠΆΠ΅ ΠΈΠ΄ΡΡ ΠΈΠ· ΡΠ΅Π°ΠΊΡ-ΡΠΎΡΡΠ΅Ρ-Π΄ΠΎΠΌ ΠΈ ΠΏΠΎΠ·Π²ΠΎΠ»ΡΠ΅Ρ ΠΏΠΎΠ»ΡΡΠ°ΡΡ ΠΏΡΠΎΠΏΡΡ ΠΎΠΏΡΠ΅Π΄Π΅Π»ΡΠ½Π½ΠΎΠ³ΠΎ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠ° ΠΏΠΎ ΡΡΡΠ»ΠΊΠ΅ (Π½Π°ΠΏΡΠΈΠΌΠ΅Ρ, ΠΏΡΠΈ ΠΏΠ΅ΡΠ΅Ρ
ΠΎΠ΄Π΅ Π½Π° ΡΡΡΠ°Π½ΠΈΡΡ ΠΏΠΎΡΡΠ° Π΄Π°Π½Π½ΡΠΉ Ρ
ΡΠΊ Π²ΠΎΠ·Π²ΡΠ°ΡΠ°Π΅Ρ id ΠΏΠΎΡΡΠ°, ΡΠ°ΠΊ ΠΊΠ°ΠΊ Π² ΡΡΡΠ»ΠΊΠ΅ ΡΡΠΎΡ ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡ Π΄ΠΈΠ½Π°ΠΌΠΈΡΠ΅ΡΠΊΠΈΠΉ)
ΠΠΈΠΆΠ΅ ΠΏΡΠ΅Π΄ΡΡΠ°Π²Π»Π΅Π½Π° ΡΠ΅Π°Π»ΠΈΠ·Π°ΡΠΈΡ ΠΎΡΠ΄Π΅Π»ΡΠ½ΠΎΠΉ ΡΡΡΠ°Π½ΠΈΡΡ Ρ ΠΏΠΎΡΡΠΎΠΌ ΠΈ Π΅Π³ΠΎ ΠΊΠΎΠΌΠΌΠ΅Π½ΡΠ°ΡΠΈΡΠΌΠΈ
import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import { useFetching } from '../hooks/useFetching';
import PostService from '../API/PostService';
import Loader from '../components/UI/Loader/Loader';
const PostIdPage = () => {
// Ρ
ΡΠΊ ΠΏΠΎΠ»ΡΡΠ΅Π½ΠΈΡ ΠΏΡΠΎΠΏΡΠΎΠ²
const params = useParams();
// ΡΡΠΎ ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ Π±ΡΠ΄Π΅Ρ Ρ
ΡΠ°Π½ΠΈΡΡ ΡΠΎ, ΡΡΠΎ Π½Π°ΠΌ Π²Π΅ΡΠ½ΡΡ ΡΠ΅ΡΠ²Π΅Ρ
const [post, setPost] = useState({});
// ΡΡΡ ΡΠΆΠ΅ Π±ΡΠ΄ΡΡ ΡΠ°ΡΠΏΠΎΠ»Π°Π³Π°ΡΡΡΡ ΠΊΠΎΠΌΠΌΠ΅Π½ΡΠ°ΡΠΈΠΈ ΠΏΠΎΡΡΠ°
const [comments, setComments] = useState([]);
// ΡΡΠΎΡ Ρ
ΡΠΊ Π²Π΅ΡΠ½ΡΡ Π½Π°ΠΌ ΠΎΠ΄ΠΈΠ½ ΠΏΠΎΡΡ ΠΏΠΎ ΠΏΠ΅ΡΠ΅Π΄Π°Π½Π½ΠΎΠΌΡ id
const [fetchPostById, isLoading, error] = useFetching(async (id) => {
// ΠΏΠΎΠ»ΡΡΠ°Π΅ΠΌ ΠΏΠΎΡΡ Ρ ΡΠ΅ΡΠ²Π΅ΡΠ°
const response = await PostService.getById(id);
// ΡΡΡΠ°Π½Π°Π²Π»ΠΈΠ²Π°Π΅ΠΌ Π΄Π°Π½Π½ΡΠ΅ ΠΏΠΎΡΡΠ° Π² ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ ΠΏΠΎΡΡΠ°
setPost(response.data);
});
// ΡΡΡ ΠΌΡ ΡΠ΅ΡΡΠΈΠΌ ΠΊΠΎΠΌΠΌΠ΅Π½ΡΠ°ΡΠΈΠΈ ΠΏΠΎΡΡΠ°
const [fetchComments, isComLoading, comError] = useFetching(async (id) => {
// ΠΏΠΎΠ»ΡΡΠ°Π΅ΠΌ ΠΏΠΎΡΡ Ρ ΡΠ΅ΡΠ²Π΅ΡΠ°
const response = await PostService.getCommentsByPostId(id);
// ΡΡΡΠ°Π½Π°Π²Π»ΠΈΠ²Π°Π΅ΠΌ ΠΊΠΎΠΌΠΌΠ΅Π½ΡΠ°ΡΠΈΠΈ Π² ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅
setComments(response.data);
});
// ΡΡΡΠ΅ΠΊΡ Π±ΡΠ΄Π΅Ρ ΡΠ΅ΡΡΠΈΡΡ Π½ΡΠΆΠ½ΡΠ΅ Π½Π°ΠΌ Π΄Π°Π½Π½ΡΠ΅ ΠΏΡΠΈ ΠΏΠ΅ΡΠ²ΠΎΠΉ Π·Π°Π³ΡΡΠ·ΠΊΠ΅ ΡΡΡΠ°Π½ΠΈΡΡ
useEffect(() => {
// ΡΡΡ ΠΎΠ½ Π±ΡΠ΄Π΅Ρ ΠΏΠΎΠ»ΡΡΠ°ΡΡ Π΄Π°Π½Π½ΡΠ΅ ΠΏΠΎ ΠΏΠΎΡΡΡ
fetchPostById(params.id);
// ΡΡΡ ΠΌΡ Π±ΡΠ΄Π΅ΠΌ ΠΏΠΎΠ»ΡΡΠΈΡΡ Π΄Π°Π½Π½ΡΠ΅ ΠΏΠΎ ΠΊΠΎΠΌΠΌΠ΅Π½ΡΠ°ΡΠΈΡΠΌ ΠΊ ΠΏΠΎΡΡΡ
fetchComments(params.id);
}, []);
return (
<div>
<h1>ΠΡ ΠΎΡΠΊΡΡΠ»ΠΈ ΡΡΡΠ°Π½ΠΈΡΡ ΠΏΠΎΡΡΠ° c ID = {params.id}</h1>
{/* ΡΠ°ΠΊ ΠΆΠ΅ Π½ΡΠΆΠ½ΠΎ ΠΎΠ±ΡΠ°Π±ΠΎΡΠ°ΡΡ Π·Π°Π³ΡΡΠ·ΠΊΡ ΠΏΠΎΡΡΠ° */}
{isLoading ? (
<Loader />
) : (
<div>
{post.id}. {post.title}
</div>
)}
<h1>ΠΠΎΠΌΠΌΠ΅Π½ΡΠ°ΡΠΈΠΈ</h1>
{/* ΠΈ Π·Π°Π³ΡΡΠ·ΠΊΡ ΠΊΠΎΠΌΠΌΠ΅Π½ΡΠ°ΡΠΈΠ΅Π² ΠΊ ΠΏΠΎΡΡΡ */}
{isComLoading ? (
<Loader />
) : (
<div>
{comments.map((comm) => (
<div key={comm.id} style={{ marginTop: 15 }}>
<h5>{comm.email}</h5>
<div>{comm.body}</div>
</div>
))}
</div>
)}
</div>
);
};
export default PostIdPage;
02:33:10 β Π£Π»ΡΡΡΠ°Π΅ΠΌ Π½Π°Π²ΠΈΠ³Π°ΡΠΈΡ. ΠΡΠΈΠ²Π°ΡΠ½ΡΠ΅ ΠΈ ΠΏΡΠ±Π»ΠΈΡΠ½ΡΠ΅ ΠΌΠ°ΡΡΡΡΡΡ
ΠΠ΅ΡΠ²ΡΠΌ Π΄Π΅Π»ΠΎΠΌ, ΡΡΠΎΠ±Ρ ΡΠΎΠΊΡΠ°ΡΠΈΡΡ ΠΊΠΎΠ»ΠΈΡΠ΅ΡΡΠ²ΠΎ ΠΎΠΏΠΈΡΠ°Π½Π½ΡΡ
ΡΠΎΡΡΠΎΠ² Π² ΡΠ΅ΡΡΡΠ½Π΅ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ° ΡΠΎΡΡΠΈΠ½Π³Π°, ΠΌΠΎΠΆΠ½ΠΎ Π²ΡΠ½Π΅ΡΡΠΈ ΠΎΠΏΡΠ΅Π΄Π΅Π»ΡΠ½Π½ΡΠ΅ Π΄Π°Π½Π½ΡΠ΅, ΠΊΠΎΡΠΎΡΡΠ΅ ΠΏΡΠΈΠ½ΠΈΠΌΠ°Π΅Ρ Route
Π² ΠΎΡΠ΄Π΅Π»ΡΠ½ΡΠΉ ΠΌΠ°ΡΡΠΈΠ² ΠΎΠ±ΡΠ΅ΠΊΡΠΎΠ²
ΠΠ°Π»Π΅Π΅ Π² ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ΅ Ρ ΡΠΎΡΡΠΈΠ½Π³ΠΎΠΌ ΠΏΡΠΎΡΡΠΎ Π²ΡΠ²Π΅ΡΡΠΈ Π²ΡΠ΅ ΡΠΎΡΡΡ ΡΠ΅ΡΠ΅Π· ΠΌΠ°ΠΏΡ
Π‘Π΅ΠΉΡΠ°Ρ Π½ΡΠΆΠ½ΠΎ ΠΎΠΏΠΈΡΠ°ΡΡ Π²ΡΠ΅ ΠΏΡΠΈΠ²Π°ΡΠ½ΡΠ΅ ΠΈ ΠΏΡΠ±Π»ΠΈΡΠ½ΡΠ΅ ΠΌΠ°ΡΡΡΡΡΡ Π½Π° ΡΡΡΠ°Π½ΠΈΡΠ΅
src > router > index.js
import About from "../pages/About";
import Posts from "../pages/Posts";
import PostIdPage from "../pages/PostIdPage";
import Login from "../pages/Login";
export const privateRoutes = [
{path: '/about', component: About, exact: true},
{path: '/posts', component: Posts, exact: true},
{path: '/posts/:id', component: PostIdPage, exact: true},
]
export const publicRoutes = [
{path: '/login', component: Login, exact: true},
]
Π ΡΡΠΎΠ±Ρ Π²ΡΠ²ΠΎΠ΄ΠΈΡΡ ΡΠΎΠ»ΡΠΊΠΎ ΠΏΡΠ±Π»ΠΈΡΠ½ΡΠ΅ ΠΈΠ»ΠΈ ΡΠΎΠ»ΡΠΊΠΎ ΠΏΡΠΈΠ²Π°ΡΠ½ΡΠ΅ ΠΌΠ°ΡΡΡΡΡΡ, ΠΌΡ ΠΌΠΎΠΆΠ΅ΠΌ ΠΏΠΎ ΡΠ΅ΡΠ½Π°ΡΠ½ΠΎΠΌΡ ΠΎΠΏΠ΅ΡΠ°ΡΠΎΡΡ Π²ΡΠ²ΠΎΠ΄ΠΈΡΡ ΡΠ°Π·Π½ΡΠ΅ ΠΊΠΎΠ½ΡΡΡΡΠΊΡΠΈΠΈ Ρ ΡΠ°Π·Π½ΡΠΌΠΈ ΡΠΎΡΡΠ°ΠΌΠΈ
ΠΡΠ»ΠΈ isAuth = false
, ΡΠΎ Π±ΡΠ΄Π΅Ρ ΡΠ΅Π½Π΄Π΅ΡΠΈΡΡΡΡ ΡΠΎΠ»ΡΠΊΠΎ ΠΎΠ΄ΠΈΠ½ ΠΌΠ°ΡΡΡΡΡ - Login
02:38:00 β useContext. ΠΠ»ΠΎΠ±Π°Π»ΡΠ½ΡΠ΅ Π΄Π°Π½Π½ΡΠ΅. ΠΠ²ΡΠΎΡΠΈΠ·Π°ΡΠΈΡ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Ρ
ΠΠ°Π»Π΅Π΅ Ρ Π½Π°Ρ ΠΈΠ΄ΡΡ Ρ
ΡΠΊ useContext
, ΠΊΠΎΡΠΎΡΡΠΉ ΠΏΠΎΠ·Π²ΠΎΠ»ΡΠ΅Ρ ΠΈΠ· Π»ΡΠ±ΠΎΠΉ ΡΠΎΡΠΊΠΈ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ ΠΏΠΎΠ»ΡΡΠΈΡΡ ΠΎΠΏΡΠ΅Π΄Π΅Π»ΡΠ½Π½ΡΠ΅ Π΄Π°Π½Π½ΡΠ΅.
ΠΠΎΡΡΠ΅Π±Π½ΠΎΡΡΡ Π² Π½ΡΠΌ ΠΏΠΎΡΠ²ΠΈΠ»Π°ΡΡ, ΠΊΠΎΠ³Π΄Π° ΠΏΠΎΡΠ²ΠΈΠ»ΠΈΡΡ ΡΠ»ΠΎΠΆΠ½ΠΎΡΡΠΈ Ρ ΠΏΠ΅ΡΠ΅Π΄Π°ΡΠ΅ΠΉ ΠΏΡΠΎΠΏΡΠΎΠ² ΠΌΠ΅ΠΆΠ΄Ρ Π±ΠΎΠ»ΡΡΠΎΠΉ Π²Π»ΠΎΠΆΠ΅Π½Π½ΠΎΡΡΡΡ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠΎΠ² Π² ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΈ
Π§ΡΠΎΠ±Ρ Π½Π°ΡΠ°ΡΡ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ ΠΊΠΎΠ½ΡΠ΅ΠΊΡΡ, Π½Π°ΠΌ Π½ΡΠΆΠ½ΠΎ Π΄Π»Ρ Π½Π°ΡΠ°Π»Π° ΡΠΎΠ·Π΄Π°ΡΡ ΡΠ°ΠΌ ΠΊΠΎΠ½ΡΠ΅ΠΊΡΡ, ΠΊ ΠΊΠΎΡΠΎΡΠΎΠΌΡ ΠΌΡ Π±ΡΠ΅ΠΌ ΠΎΠ±ΡΠ°ΡΠ°ΡΡΡΡ:
src > context > index.js
import {createContext} from 'react'
export const AuthContext = createContext(null);
ΠΠ°Π»Π΅Π΅ Π² Π³Π»Π°Π²Π½ΠΎΠΌ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ΅ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ Π½ΡΠΆΠ½ΠΎ ΠΎΠ±Π΅ΡΠ½ΡΡΡ Π²ΡΡ ΠΏΠΎΡΠ»Π΅Π΄ΡΡΡΠ΅Π΅ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ Π² ΠΊΠΎΠ½ΡΠ΅ΠΊΡΡ AuthContext
, ΠΊΠΎΡΠΎΡΡΠΉ ΠΈ Π±ΡΠ΄Π΅Ρ ΡΠ°ΡΠΏΡΠΎΡΡΡΠ°Π½ΡΡΡ ΠΎΠΏΡΠ΅Π΄Π΅Π»ΡΠ½Π½ΡΠ΅ Π΄Π°Π½Π½ΡΠ΅ Π½Π° Π²ΡΠ΅ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΡ. Π‘Π°ΠΌΠΈΠΌ ΡΠ°ΡΠΏΡΠΎΡΡΡΠ°Π½Π΅Π½ΠΈΠ΅ΠΌ Π·Π°Π½ΠΈΠΌΠ°Π΅ΡΡΡ Provider
ΡΡΠΎΠ³ΠΎ ΠΊΠΎΠ½ΡΠ΅ΠΊΡΡΠ°. Π Π°ΡΡΠΈΠ±ΡΡ value
ΠΌΡ ΠΏΠ΅ΡΠ΅Π΄Π°ΡΠΌ Π·Π½Π°ΡΠ΅Π½ΠΈΡ, ΠΊΠΎΡΠΎΡΡΠ΅ Ρ
ΠΎΡΠΈΠΌ ΡΠ°ΡΠΏΡΠΎΡΡΡΠ°Π½ΡΡΡ.
Π ΡΠ΅ΠΏΠ΅ΡΡ ΠΏΡΠΈ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠΈ ΡΡΡΠ°Π½ΠΈΡΡ Ρ ΠΎΡΠ΄Π΅Π»ΡΠ½ΡΠΌ ΠΏΠΎΡΡΠΎΠΌ ΠΌΡ ΠΌΠΎΠΆΠ΅ΠΌ Π²ΡΡΡΠ΅ΡΠΈΡΡ ΠΏΡΠΎΠ±Π»Π΅ΠΌΡ, ΡΡΠΎ ΠΌΡ Π²ΡΠ±ΡΠ°ΡΡΠ²Π°Π΅ΠΌΡΡ Π½Π° ΡΡΡΠ°Π½ΠΈΡΡ Ρ ΠΏΠΎΡΡΠ°ΠΌΠΈ. ΠΡΠΎ ΠΏΡΠΎΠΈΡΡ ΠΎΠ΄ΠΈΡ ΠΏΠΎ ΡΠ»Π΅Π΄ΡΡΡΠ΅ΠΉ ΠΏΡΠΈΡΠΈΠ½Π΅:
- ΠΈΠ·Π½Π°ΡΠ°Π»ΡΠ½ΠΎ Π°ΡΡΠ΅Π½ΡΠΈΡΠΈΠΊΠ°ΡΠΈΡ ΡΡΠΎΠΈΡ ΠΊΠ°ΠΊ
false
- Ρ Π½Π°Ρ Π³ΡΡΠ·ΠΈΡΡΡ ΡΡΡΠ°Π½ΠΈΡΠ° Π»ΠΎΠ³ΠΈΠ½Π°
- ΠΏΠΎΡΠΎΠΌ ΠΌΡ ΠΏΠΎΠ»ΡΡΠ°Π΅ΠΌ Π΄Π°Π½Π½ΡΠ΅ Ρ Π»ΠΎΠΊΠ°Π»ΡΠ½ΠΎΠ³ΠΎ Ρ ΡΠ°Π½ΠΈΠ»ΠΈΡΠ°, ΡΡΠΎ ΠΌΡ Π°Π²ΡΠΎΡΠΈΠ·ΠΎΠ²Π°Π½Ρ
- Ρ Π½Π°Ρ Π·Π°Π³ΡΡΠΆΠ°Π΅ΡΡΡ ΠΎΠ΄ΠΈΠ½ ΠΈΠ· Π΄ΠΎΡΡΡΠΏΠ½ΡΡ ΡΠΎΡΡΠΎΠ² - ΠΏΠΎΡΡΡ
Π§ΡΠΎΠ±Ρ ΠΏΠΎΠΏΡΠ°Π²ΠΈΡΡ ΠΏΡΠΎΠ±Π»Π΅ΠΌΡ, Π½ΡΠΆΠ½ΠΎ ΡΠΎΠ·Π΄Π°ΡΡ ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ isLoading
, ΠΊΠΎΡΠΎΡΠΎΠ΅ Π½Π΅ Π΄Π°ΡΡ Π½Π°ΠΌ Π²ΡΠ±ΡΠ°ΡΡΠ²Π°ΡΡΡΡ ΠΎΠ±ΡΠ°ΡΠ½ΠΎ, Π° ΠΏΡΠΎΡΡΠΎ Π±ΡΠ΄Π΅Ρ ΠΎΡΠΎΠ±ΡΠ°ΠΆΠ°ΡΡ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ Π·Π°Π³ΡΡΠ·ΠΊΠΈ
src > App.jsx
function App() {
// ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ Π·Π°ΡΠ΅Π³ΠΈΡΡΡΠΈΡΠΎΠ²Π°Π½Π½ΠΎΡΡΠΈ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Ρ
const [isAuth, setIsAuth] = useState(false);
// ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ ΠΏΠΎΠ»Π½ΠΎΠΉ Π·Π°Π³ΡΡΠ·ΠΊΠΈ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ
const [isLoading, setLoading] = useState(true);
// ΡΡΡΠ΅ΠΊΡ Π±ΡΠ΄Π΅Ρ ΠΏΠΎΠ»ΡΡΠ°ΡΡ ΠΎΠ΄ΠΈΠ½ ΡΠ°Π· ΠΏΡΠΈ Π·Π°Π³ΡΡΠ·ΠΊΠ΅ ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Ρ - Π΅ΡΡΡ Π²Ρ
ΠΎΠ΄ ΠΈΠ»ΠΈ Π½Π΅Ρ Π΅Π³ΠΎ
useEffect(() => {
if (localStorage.getItem('auth')) {
setIsAuth(true)
}
// ΡΠ°ΠΊ ΠΆΠ΅ Π·Π°Π³ΡΡΠ·ΠΊΠ° ΡΠ΅ΠΉΡΠ°Ρ ΠΎΡΠΊΠ»ΡΡΠ°Π΅ΡΡΡ
setLoading(false);
}, [])
return (
// Π΄Π°Π»Π΅Π΅ ΡΠΎΠ·Π΄Π°ΡΠΌ ΡΡΡ ΠΏΡΠΎΠ²Π°ΠΉΠ΄Π΅Ρ ΠΎΡ ΠΊΠΎΠ½ΡΠ΅ΠΊΡΡΠ° ΠΈ ΠΎΠ±ΠΎΡΠ°ΡΠΈΠ²Π°Π΅ΠΌ Π² Π½Π΅Π³ΠΎ Π²ΡΡ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅
// Π² Π°ΡΡΠΈΠ±ΡΡ value ΡΠΊΠ°Π·ΡΠ²Π°Π΅ΠΌ, ΠΊΠ°ΠΊΠΈΠ΅ Π΄Π°Π½Π½ΡΠ΅ Π±ΡΠ΄Π΅Ρ Ρ
ΡΠ°Π½ΠΈΡΡ Π² ΡΠ΅Π±Π΅ ΠΊΠΎΠ½ΡΠ΅ΠΊΡΡ
<AuthContext.Provider value={{
isAuth,
setIsAuth,
isLoading
}}>
<BrowserRouter>
<Navbar/>
<AppRouter/>
</BrowserRouter>
</AuthContext.Provider>
)
}
export default App;
Π Π΄Π°Π»Π΅Π΅ Π² ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ΅ ΡΠΎΡΡΠΈΠ½Π³Π° ΠΌΡ ΠΌΠΎΠΆΠ΅ΠΌ ΡΠΏΠΎΠΊΠΎΠΉΠ½ΠΎ ΠΏΠΎΠ»ΡΡΠΈΡΡ ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ Π²Ρ ΠΎΠ΄Π° ΠΈ Π·Π°Π³ΡΡΠ·ΠΊΠΈ. ΠΡΠ»ΠΈ ΠΌΡ Π½Π°Ρ ΠΎΠ΄ΠΈΠΌΡΡ Π² Π·Π°Π³ΡΡΠ·ΠΊΠ΅, ΡΠΎ ΠΌΠΎΠΆΠ΅ΠΌ ΠΏΡΠΎΡΡΠΎ Π²Π΅ΡΠ½ΡΡΡ Π»ΠΎΠ°Π΄Π΅Ρ, ΠΊΠΎΡΠΎΡΡΠΉ ΠΏΠΎΠΊΠ°ΠΆΠ΅Ρ, ΡΡΠΎ Π΄Π°Π½Π½ΡΠ΅ Π·Π°Π³ΡΡΠΆΠ°ΡΡΡΡ
ΠΠ°Π»Π΅Π΅ ΠΏΠΎ ΡΠΎΡΡΠΎΡΠ½ΠΈΡ Π°ΡΡΠ΅Π½ΡΠΈΡΠΈΠΊΠ°ΡΠΈΠΈ Π±ΡΠ΄ΡΡ ΠΏΠΎΠ΄Π³ΡΡΠΆΠ°ΡΡΡΡ ΠΎΠΏΡΠ΅Π΄Π΅Π»ΡΠ½Π½ΡΠ΅ ΡΠΎΡΡΡ
src > components > AppRouter.jsx
const AppRouter = () => {
// ΠΏΠΎΠ»ΡΡΠ°Π΅ΠΌ ΠΈΠ· Π³Π»ΠΎΠ±Π°Π»ΡΠ½ΠΎΠ³ΠΎ ΠΊΠΎΠ½ΡΠ΅ΠΊΡΡΠ° ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ Π²Ρ
ΠΎΠ΄Π° ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Ρ ΠΈ ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ Π·Π°Π³ΡΡΠ·ΠΊΠΈ
const { isAuth, isLoading } = useContext(AuthContext);
// ΠΈΠ΄ΡΡ Π·Π°Π³ΡΡΠ·ΠΊΠ°, Π΅ΡΠ»ΠΈ ΡΡΡΠ°Π½ΠΈΡΠ° Π΄ΠΎ ΡΠΈΡ
ΠΏΠΎΡ Π·Π°Π³ΡΡΠΆΠ°Π΅ΡΡΡ
if (isLoading) {
return <Loader />;
}
return isAuth ? (
<Switch>
{privateRoutes.map((route) => (
<Route component={route.component} path={route.path} exact={route.exact} key={route.path} />
))}
<Redirect to='/posts' />
</Switch>
) : (
<Switch>
{publicRoutes.map((route) => (
<Route component={route.component} path={route.path} exact={route.exact} key={route.path} />
))}
<Redirect to='/login' />
</Switch>
);
};
export default AppRouter;
Π ΡΠ°ΠΊ Π²ΡΠ³Π»ΡΠ΄ΠΈΡ ΠΏΠΎΠ΄Π³ΡΡΠ·ΠΊΠ° Π΄Π°Π½Π½ΡΡ ΠΏΠΎ ΠΏΠΎΡΡΠ°ΠΌ
ΠΠ° ΡΡΡΠ°Π½ΠΈΡΠ΅ Π»ΠΎΠ³ΠΈΠ½Π°, ΠΌΡ Π±ΡΠ΄Π΅ΠΌ ΡΡΡΠ°Π½Π°Π²Π»ΠΈΠ²Π°ΡΡ Π°ΠΉΡΠ΅ΠΌ auth
Π² Π»ΠΎΠΊΠ°Π»ΡΠ½ΠΎΠ΅ Ρ
ΡΠ°Π½ΠΈΠ»ΠΈΡΠ΅, ΠΊΠΎΡΠΎΡΡΠΉ ΠΈ Π±ΡΠ΄Π΅Ρ ΠΎΡΠ²Π΅ΡΠ°ΡΡ Π·Π° Π°ΡΡΠ΅Π½ΡΠΈΡΠΈΠΊΠ°ΡΠΈΡ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Ρ Π² ΡΠΈΡΡΠ΅ΠΌΠ΅
src > page > Login.jsx
import React, {useContext} from 'react';
import MyInput from "../components/UI/input/MyInput";
import MyButton from "../components/UI/button/MyButton";
import {AuthContext} from "../context";
const Login = () => {
// ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ Π²Ρ
ΠΎΠ΄Π° Π² ΡΠΈΡΡΠ΅ΠΌΡ ΠΌΡ ΠΏΠΎΠ»ΡΡΠ°Π΅ΠΌ ΠΈΠ· Π³Π»ΠΎΠ±Π°Π»ΡΠ½ΠΎΠ³ΠΎ ΠΊΠΎΠ½ΡΠ΅ΠΊΡΡΠ°
const {isAuth, setIsAuth} = useContext(AuthContext);
// ΡΡΠ° ΡΡΠ½ΠΊΡΠΈΡ Π±ΡΠ΄Π΅Ρ ΠΎΠΏΡΠ΅Π΄Π΅Π»ΡΡΡ, ΡΡΠΎ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Ρ Π·Π°Π»ΠΎΠ³ΠΈΠ½Π΅Π½
const login = event => {
// ΠΏΡΠ΅Π΄ΠΎΡΠ²ΡΠ°ΡΠ°Π΅ΠΌ ΠΏΠ΅ΡΠ΅Π·Π°Π³ΡΡΠ·ΠΊΡ ΠΎΡ ΡΠΎΡΠΌΡ
event.preventDefault();
// ΡΡΡΠ°Π½Π°Π²Π»ΠΈΠ²Π°Π΅ΠΌ ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅
setIsAuth(true);
// Π² Π»ΠΎΠΊΠ°Π»ΡΠ½Π΅ Ρ
ΡΠ°Π½ΠΈΠ»ΠΈΡΠ΅ ΠΏΠΎΠΌΠ΅ΡΠ°Π΅ΠΌ true
localStorage.setItem('auth', 'true')
}
return (
<div>
<h1>Π‘ΡΡΠ°Π½ΠΈΡΠ° Π΄Π»Ρ Π»ΠΎΠ³ΠΈΠ½Π°</h1>
{/* ΡΠΎΡΠΌΠ° Π»ΠΎΠ³ΠΈΠ½Π° */}
<form onSubmit={login}>
<MyInput type="text" placeholder="ΠΠ²Π΅Π΄ΠΈΡΠ΅ Π»ΠΎΠ³ΠΈΠ½"/>
<MyInput type="password" placeholder="ΠΠ²Π΅Π΄ΠΈΡΠ΅ ΠΏΠ°ΡΠΎΠ»Ρ"/>
<MyButton>ΠΠΎΠΉΡΠΈ</MyButton>
</form>
</div>
);
};
export default Login;
ΠΠ°Π»Π΅Π΅ Π² Π½Π°Π²ΠΈΠ³Π°ΡΠΈΠΎΠ½Π½ΠΎΠΌ Π±Π°ΡΠ΅ ΠΌΠΎΠΆΠ½ΠΎ ΡΠ΄Π΅Π»Π°ΡΡ ΡΡΠ½ΠΊΡΠΈΡ Π²ΡΡ ΠΎΠ΄Π° ΠΈΠ· ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ, ΠΊΠΎΡΠΎΡΠ°Ρ ΡΠ΄Π°Π»ΠΈΡΡ Π°ΠΉΡΠ΅ΠΌ Π°ΡΡΠ΅Π½ΡΠΈΡΠΈΠΊΠ°ΡΠΈΠΈ ΠΈΠ· Π»ΠΎΠΊΠ°Π»ΡΠ½ΠΎΠ³ΠΎ Ρ ΡΠ°Π½ΠΈΠ»ΠΈΡΠ°
src > components > Navbar.jsx
import React, {useContext} from 'react';
import {Link} from "react-router-dom";
import MyButton from "../button/MyButton";
import {AuthContext} from "../../../context";
const Navbar = () => {
const {isAuth, setIsAuth} = useContext(AuthContext);
// ΠΏΡΠΈ Π²ΡΡ
ΠΎΠ΄Π΅ Ρ ΡΠ°ΠΉΡΠ° Π°ΡΡΠ΅Π½ΡΠΈΡΠΈΠΊΠ°ΡΠΈΡ ΡΠΎΠΆΠ΅ ΡΠ»Π΅ΡΠ°Π΅Ρ
const logout = () => {
setIsAuth(false);
localStorage.removeItem('auth')
}
return (
<div className="navbar">
<MyButton onClick={logout}>
ΠΡΠΉΡΠΈ
</MyButton>
<div className="navbar__links">
<Link to="/about">Π ΡΠ°ΠΉΡΠ΅</Link>
<Link to="/posts">ΠΠΎΡΡΡ</Link>
</div>
</div>
);
};
export default Navbar;
ΠΡΠΎΠ³: ΠΌΡ ΠΈΠΌΠ΅Π΅ΠΌ ΡΡΡΠ°Π½ΠΈΡΡ Π²Ρ ΠΎΠ΄Π°, Ρ ΠΊΠΎΡΠΎΡΠΎΠΉ ΠΌΡ Π½Π΅ ΠΌΠΎΠΆΠ΅ΠΌ ΠΏΡΠΎΠΉΡΠΈ Π½ΠΈ Π½Π° ΠΊΠ°ΠΊΠΈΠ΅ ΠΏΡΠΈΠ²Π°ΡΠ½ΡΠ΅ ΡΠΎΡΡΡ / Π»ΠΎΠ³ΠΈΠ½ ΡΠΎΠ·Π΄Π°ΡΡ ΠΎΡΠΌΠ΅ΡΠΊΡ Π² Π»ΠΎΠΊΠ°Π»ΡΠ½ΠΎΠΌ Ρ ΡΠ°Π½ΠΈΠ»ΠΈΡΠ΅ ΠΎ Π²Ρ ΠΎΠ΄Π΅ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Ρ
ΠΈ ΡΠ°ΠΊ ΠΆΠ΅ ΠΌΡ ΠΌΠΎΠΆΠ΅ΠΌ Π²ΡΠΉΡΠΈ ΠΈΠ· ΡΠΈΡΡΠ΅ΠΌΡ
02:47:10 β ΠΠ΅ΡΠΊΠΎΠ½Π΅ΡΠ½Π°Ρ Π»Π΅Π½ΡΠ°. ΠΠΈΠ½Π°ΠΌΠΈΡΠ΅ΡΠΊΠ°Ρ ΠΏΠ°Π³ΠΈΠ½Π°ΡΠΈΡ. useObserver
Π‘Π΅ΠΉΡΠ°Ρ Π½ΡΠΆΠ½ΠΎ Π½Π°ΠΏΠΈΡΠ°ΡΡ Ρ ΡΠΊ, ΠΊΠΎΡΠΎΡΡΠΉ ΠΏΠΎΠ·Π²ΠΎΠ»ΠΈΡ Π½Π°ΠΌ Π½Π°Π±Π»ΡΠ΄Π°ΡΡ, Π΄ΠΎΡΠ»ΠΈ Π»ΠΈ ΠΌΡ Π΄ΠΎ ΠΎΠΏΡΠ΅Π΄Π΅Π»ΡΠ½Π½ΠΎΠ³ΠΎ Π±Π»ΠΎΠΊΠ° Π½Π° ΡΡΡΠ°Π½ΠΈΡΠ΅, ΡΡΠΎΠ±Ρ ΡΠΏΠΎΠΊΠΎΠΉΠ½ΠΎ Π·Π°Π³ΡΡΠ·ΠΈΡΡ ΡΠ»Π΅Π΄ΡΡΡΡΡ ΠΏΠΎΡΡΠΈΡ ΠΊΠΎΠ½ΡΠ΅Π½ΡΠ°.
Π‘Π²ΠΎΠΉΡΡΠ²ΠΎ isIntersecting
Ρ observer
ΠΎΡΠ²Π΅ΡΠ°Π΅Ρ Π·Π° ΡΠΎ, Π² Π·ΠΎΠ½Π΅ Π²ΠΈΠ΄ΠΈΠΌΠΎΡΡΠΈ Π»ΠΈ Π½Π°Ρ ΡΠ»Π΅ΠΌΠ΅Π½Ρ
hooks / useObserver.ts
import { useEffect, useRef } from 'react';
export const useObserver = (ref, canLoad: boolean, isLoading: boolean, callback: Function) => {
// ΡΡΡ Π±ΡΠ΄Π΅Ρ Ρ
ΡΠ°Π½ΠΈΡΡΡΡ ΡΠ°ΠΌ ΠΎΠ±Π·ΡΡΠ²Π΅Ρ
const observer = useRef();
// Π²ΡΠΎΡΠΎΠΉ ΡΡΡΠ΅ΠΊΡ Π±ΡΠ΄Π΅Ρ ΠΎΡΠ²Π΅ΡΠ°ΡΡ Π·Π° ΠΎΡΡΠ»Π΅ΠΆΠΈΠ²Π°Π½ΠΈΠ΅ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠ°, ΠΊΠΎΡΠΎΡΡΠΉ Π±ΡΠ΄Π΅Ρ ΡΡΠΈΠ³Π³Π΅ΡΠΈΡΡ Π·Π°Π³ΡΡΠ·ΠΊΡ ΠΏΠΎΡΡΠΎΠ²
useEffect(() => {
// Π΅ΡΠ»ΠΈ ΠΌΡ Π·Π°Π³ΡΡΠΆΠ°Π΅ΠΌΡΡ, ΡΠΎ Π½ΠΎΠ²ΡΠΉ ΠΎΠ±Π·ΡΡΠ²Π΅Ρ ΡΠΎΠ·Π΄Π°Π²Π°ΡΡ ΡΠ΅ΠΉΡΠ°Ρ Π½Π΅ Π½ΡΠΆΠ½ΠΎ
if (isLoading) return;
// Π΅ΡΠ»ΠΈ ΠΎΠ±Π·ΡΡΠ²Π΅Ρ Π·Π° ΡΠ΅ΠΌ-ΡΠΎ ΡΠΆΠ΅ ΡΠ»Π΅Π΄ΠΈΡ, ΡΠΎ Π½ΡΠΆΠ½ΠΎ ΡΠ±ΡΠ°ΡΡ Ρ Π½Π΅Π³ΠΎ Π²ΡΠ΅ ΡΠ»Π΅ΠΆΠΊΠΈ Π½Π° Π΄Π°Π½Π½ΡΠΉ ΠΌΠΎΠΌΠ΅Π½Ρ Π²ΡΠ΅ΠΌΠ΅Π½ΠΈ
if (observer.current) observer.current?.disconnect();
const callbackObserver = (entries, observer) => {
// Π΅ΡΠ»ΠΈ ΠΎΠ±ΡΠ΅ΠΊΡ Π² Π·ΠΎΠ½Π΅ Π²ΠΈΠ΄ΠΈΠΌΠΎΡΡΠΈ ΠΈ Π΅ΡΠ»ΠΈ Π½ΠΎΠΌΠ΅Ρ ΡΠ΅ΠΊΡΡΠ΅ΠΉ ΡΡΡΠ°Π½ΠΈΡΡ ΠΌΠ΅Π½ΡΡΠ΅ ΠΎΠ±ΡΠ΅Π³ΠΎ ΠΊΠΎΠ»ΠΈΡΠ΅ΡΡΠ²Π° ΡΡΡΠ°Π½ΠΈΡ
if (entries[0].isIntersecting && canLoad) {
// ΡΠΎ ΠΈΠ·ΠΌΠ΅Π½ΡΠ΅ΠΌ Π½ΠΎΠΌΠ΅Ρ ΡΡΡΠ°Π½ΠΈΡΡ
callback();
}
};
// ΠΈΠ½ΠΈΡΠΈΠ°Π»ΠΈΠ·ΠΈΡΡΠ΅ΠΌ Π½ΠΎΠ²ΡΠΉ ΠΎΠ±Π·ΡΡΠ²Π΅Ρ
observer.current = new IntersectionObserver(callbackObserver);
// Π²ΡΠ±ΠΈΡΠ°Π΅ΠΌ ΠΎΡΡΠ»Π΅ΠΆΠΈΠ²Π°Π΅ΠΌΡΠΉ ΡΠ»Π΅ΠΌΠ΅Π½Ρ
observer.current.observe(ref.current);
// ΡΡΠ°Π±Π°ΡΡΠ²Π°ΡΡ ΡΡΡΠ΅ΠΊΡ Π΄ΠΎΠ»ΠΆΠ΅Π½ ΡΠΎΠ»ΡΠΊΠΎ ΡΠΎΠ³Π΄Π°, ΠΊΠΎΠ³Π΄Π° ΠΈΠ·ΠΌΠ΅Π½ΠΈΠ»ΠΎΡΡ ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ Π·Π°Π³ΡΡΠ·ΠΊΠΈ ΡΡΡΠ°Π½ΠΈΡΡ
}, [isLoading]);
};
Π return
Π΄ΠΎΠ±Π°Π²Π»ΡΠ΅ΠΌ Π΄ΠΈΠ²-ΠΏΡΡΡΡΡΠΊΡ, ΠΊΠΎΡΠΎΡΡΠΉ Π±ΡΠ΄Π΅Ρ ΠΏΡΠΎΡΡΠΎ ΠΈΠΌΠ΅ΡΡ Π² ΡΠ΅Π±Π΅ ΡΠ΅ΡΠ΅ΡΠ΅Π½Ρ lastElement
, Π·Π° ΠΊΠΎΡΠΎΡΡΠΌ ΠΈ Π±ΡΠ΄Π΅Ρ ΡΠ»Π΅Π΄ΠΈΡΡ ΠΎΠ±Π·ΡΡΠ²Π΅Ρ. ΠΠ°Π»Π΅Π΅ Π½Π°ΠΌ ΠΏΡΠΎΡΡΠΎ Π½ΡΠΆΠ½ΠΎ Π²ΡΠ·Π²Π°ΡΡ ΡΠ°ΠΌΠΎΠΏΠΈΡΠ½ΡΠΉ Ρ
ΡΠΊ useObserver
ΠΈ ΠΏΠ΅ΡΠ΅Π΄Π°ΡΡ Π² Π½Π΅Π³ΠΎ Π½ΡΠΆΠ½ΡΠ΅ ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡΡ
page-components / Posts.tsx
export const Posts = () => {
const [posts, setPosts] = useState('');
const [filter, setFilter] = useState<IFilter>({ query: '', sort: 'title' });
const [modal, setModal] = useState(false);
const [totalPages, setTotalPages] = useState<number>(0);
const [limit, setLimit] = useState<number>(10);
const [page, setPage] = useState<number>(1);
// ΡΡΡ ΠΌΡ Π±ΡΠ΄Π΅ΠΌ Ρ
ΡΠ°Π½ΠΈΡΡ ΡΡΡΠ»ΠΊΡ Π½Π° ΠΏΠΎΡΠ»Π΅Π΄Π½ΠΈΠΉ ΡΠ»Π΅ΠΌΠ΅Π½Ρ ΡΡΡΠ°Π½ΠΈΡΡ, ΡΡΠΎΠ±Ρ ΠΏΡΠΈ Π΄ΠΎΡΡΠΈΠΆΠ΅Π½ΠΈΠΈ Π΅Π³ΠΎ, Ρ Π½Π°Ρ ΠΏΠΎΠ΄Π³ΡΡΠΆΠ°Π»ΠΈΡΡ Π½ΠΎΠ²ΡΠ΅ ΠΏΠΎΡΡΡ
const lastElement = useRef<HTMLDivElement>();
const [fetchPosts, isPostLoading, postsError] = useFetching(async () => {
const response = await PostService.getAll(limit, page);
// ΠΏΠΎΠ΄Π³ΡΡΠΆΠ°Π΅Ρ Π½Π΅ ΠΏΡΠΎΡΡΠΎ Π½ΠΎΠ²ΡΠ΅ ΠΏΠΎΡΡΡ, Π° Π΄ΠΎΠ±Π°Π²Π»ΡΠ΅Ρ ΠΏΠΎΠ΄Π³ΡΡΠΆΠ΅Π½Π½ΡΠ΅ Π² ΠΎΠ±ΡΠΈΠΉ ΠΌΠ°ΡΡΠΈΠ² ΠΏΠΎΡΡΠΎΠ²
setPosts([...posts, ...response.data]);
const totalCount = response.headers['x-total-count'];
setTotalPages(getPageCount(totalCount, limit));
});
const changePage = (page: number) => {
setPage(page);
};
// ΡΡΡ Π²ΠΎΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΠΌΡΡ ΠΊΠ°ΡΡΠΎΠΌΠ½ΡΠΌ Ρ
ΡΠΊΠΎΠΌ Π΄Π»Ρ ΠΎΡΡΠ»Π΅ΠΆΠΈΠ²Π°Π½ΠΈΡ ΠΊΠΎΠ½ΡΠ° ΡΡΡΠ°Π½ΠΈΡΡ
useObserver(lastElement, page < totalPages, isPostLoading, () => {
setPage(page + 1);
});
useEffect(() => {
fetchPosts();
}, [page, limit]);
const sortedAndSearchedPosts = usePosts(posts, filter.sort, filter.query);
const createPost = (newPost: IPost): void => {
setPosts([...posts, newPost]);
setModal(false);
};
const removePost = (post: IPost): void => {
setPosts(posts.filter(p => p.id !== post.id));
};
return (
<div className={styles.wrapper}>
<Button className={styles.button} buttonType={'purple'} onClick={() => setModal(true)}>
Π‘ΠΎΠ·Π΄Π°ΡΡ ΠΏΠΎΡΡ
</Button>
<Modal visible={modal} setVisible={setModal}>
<PostForm create={createPost} />
</Modal>
<PostFilter filter={filter} setFilter={setFilter} />
<Select
defaultValue={'ΠΠΎΠ»ΠΈΡΠ΅ΡΡΠ²ΠΎ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠΎΠ² Π½Π° ΡΡΡΠ°Π½ΠΈΡΠ΅'}
options={[
{ value: 5, name: '5' },
{ value: 10, name: '10' },
{ value: 25, name: '25' },
{ value: -1, name: 'ΠΡΠ΅' },
]}
value={limit}
onChange={value => setLimit(value)}
/>
{postsError && <h1>ΠΡΠΎΠΈΠ·ΠΎΡΠ»Π° ΠΎΡΠΈΠ±ΠΊΠ° {postsError}</h1>}
<PostList className={styles.list} posts={sortedAndSearchedPosts} remove={removePost} />
{/* ΡΡΠΎ Π½Π°Π±Π»ΡΠ΄Π°Π΅ΠΌΡΠΉ div */}
<div ref={lastElement} style={{ height: 1 }} />
{isPostLoading && (
<div className={styles.loadPosition}>
<Loader>ΠΠ΄ΡΡ Π·Π°Π³ΡΡΠ·ΠΊΠ°...</Loader>
</div>
)}
<Pagination totalPages={totalPages} page={page} changePage={changePage} />
</div>
);
};
ΠΡΠΈ Π΄ΠΎΡΡΠΈΠΆΠ΅Π½ΠΈΠΈ Π½Π΅Π²ΠΈΠ΄ΠΈΠΌΠΎΠ³ΠΎ div
, Ρ Π½Π°Ρ ΡΡΠ°Π±Π°ΡΡΠ²Π°Π΅Ρ ΡΡΠ½ΠΊΡΠΈΡ Π·Π°ΠΊΠΈΠ½ΡΡΠ°Ρ Π² observer
Π’Π΅ΠΏΠ΅ΡΡ, ΠΏΡΠΈ Π΄ΠΎΡΡΠΈΠΆΠ΅Π½ΠΈΠΈ Π½ΠΈΠ·Π° ΡΡΡΠ°Π½ΠΈΡΡ, Ρ Π½Π°Ρ Π°Π²ΡΠΎΠΌΠ°ΡΠΈΡΠ΅ΡΠΊΠΈ ΠΏΠΎΠ΄Π³ΡΡΠΆΠ°ΡΡΡΡ Π½ΠΎΠ²ΡΠ΅ ΠΏΠΎΡΡΡ ΠΈ ΠΏΠ΅ΡΠ΅Π»ΠΈΡΡΡΠ²Π°ΡΡΡΡ ΡΡΡΠ°Π½ΠΈΡΡ