575.09K
Category: internetinternet

Пользовательские хуки

1.

PHASE 3
WEEK 1
DAY 2

2.

ПЛАН
1. Пользовательские хуки
2. memo
3. useContext
4. useMemo
5. useCallback
6. useReducer
7. CSS modules & SCSS

3.

Пользовательски
е
хуки

4.

Пользовательские хуки
// PokemonList.js
useEffect(() => {
import React from 'react';
if (!pokemons.length) {
import Pokemon from './Pokemon';
return false;
import usePokemons from './hooks/use-pokemons';
}
setLoading(true);
function PokemonList({ pokemons }) {
setError(false);
const [pokemonsData, loading, error] = usePokemons(pokemons);
const abortController = new AbortController();
if (error) {
const { signal } = abortController;
return 'Ошибка загрузки покемонов.';
(async () => {
}
let jsonResults;
if (loading) {
try {
return 'Загрузка покемонов...';
jsonResults = await fetchAll(
}
pokemons.map((name) => `https://pokeapi.co/api/v2/pokemon/${name}/`),
return pokemonsData.map(({ name, weight, img }) => (
{ signal },
<Pokemon key={name} name={name} weight={weight} img={img} />
);
));
} catch (err) {
}
setPokemonsData([]);
setLoading(false);
export default PokemonList;
return setError(err);
}
// hooks/use-pokemons.js
setPokemonsData(jsonResults.map((data) => ({
import { useState, useEffect } from 'react';
name: data.name,
import fetchAll from '../lib/fetch-all';
weight: data.weight,
img: data.sprites.front_default,
function usePokemons(pokemons) {
})));
const [pokemonsData, setPokemonsData] = useState([]);
return setLoading(false);
const [loading, setLoading] = useState(false);
})();
const [error, setError] = useState(false);
return () => abortController.abort();
}, [pokemons]);
return [pokemonsData, loading, error];
}
export default usePokemons;

5.

memo

6.

memo
Запрещает повторный
рендеринг, если пропсы
// Pokemon.js
import React, { useState, memo } from 'react';
function Pokemon({ name, weight, img }) {
const [
currentWeight,
остались прежние.
setCurrentWeight,
] = useState(weight);
function addWeight() {
setCurrentWeight((x) => x + 50);
Значительно повышает
}
function removeWeight() {
setCurrentWeight((x) => x - 50);
производительность при
}
return (
динамическом изменении
<>
списка компонентов.
</>
...
);
}
export default memo(Pokemon);

7.

useContext

8.

useContext
Как прокинуть данные в
компоненты с глубокой
вложенностью?
// contexts/pokemons-context.js
import { createContext } from 'react';
export default createContext({
additionalWeight: 0,
addAdditionalWeight: () => {},
removeAdditionalWeight: () => {},
});
Как изменить данные в
родительском компоненте?
Как повлиять на соседний
компонент?

9.

useContext (Provider)
// App.js
return (
import React, { useState } from 'react';
<div className="App">
import PokemonsContext from './contexts/pokemons-
<PokemonsContext.Provider
context';
value={{
import PokemonList from './PokemonList';
additionalWeight,
import PokemonsAdditionalWeight from
addAdditionalWeight,
'./PokemonsAdditionalWeight';
removeAdditionalWeight,
}}
function App() {
>
const [
<PokemonsAdditionalWeight />
additionalWeight,
setAdditionalWeight,
<PokemonList pokemons={['slowpoke',
'pikachu', 'psyduck']} />
] = useState(0);
</PokemonsContext.Provider>
</div>
const addAdditionalWeight = (weight) => {
setAdditionalWeight((x) => x + weight);
);
}
};
export default App;
const removeAdditionalWeight = (weight) => {
setAdditionalWeight((x) => x - weight);
};

10.

useContext (Consumers)
// PokemonsAdditionalWeight.js
// Pokemon.js
import React, { useContext } from 'react';
import React, { useState, memo, useContext } from 'react';
import PokemonsContext from './contexts/pokemons-context';
import PokemonsContext from './contexts/pokemons-context';
function PokemonsAdditionalWeight() {
const weightStep = 50;
const { additionalWeight } = useContext(PokemonsContext);
return (
function Pokemon({ name, weight, img }) {
<h2>
Дополнительный вес покемонов:
const { addAdditionalWeight, removeAdditionalWeight } =
useContext(PokemonsContext);
{additionalWeight}
</h2>
const [currentWeight, setCurrentWeight] =
useState(weight);
);
function addWeight() {
}
setCurrentWeight((x) => x + weightStep);
addAdditionalWeight(weightStep);
export default PokemonsAdditionalWeight;
}
function removeWeight() {
setCurrentWeight((x) => x - weightStep);
removeAdditionalWeight(weightStep);
}
return (
<>...</>
);
}
export default memo(Pokemon);

11.

useContext (нюанс)
Когда значение контекста меняется, все потребители
рендерятся заново, независимо от memo.

12.

useMemo

13.

useMemo (вычисляем один раз)
// App.js
return (
import React, { useState, useMemo } from 'react';
<div className="App">
import PokemonsContext from './contexts/pokemons-context';
<PokemonsContext.Provider
import PokemonList from './PokemonList';
value={{
import PokemonsAdditionalWeight from
additionalWeight,
'./PokemonsAdditionalWeight';
addAdditionalWeight,
removeAdditionalWeight,
function App() {
}}
const [
>
additionalWeight,
<PokemonsAdditionalWeight />
setAdditionalWeight,
<PokemonList pokemons={pokemons} />
] = useState(0);
</PokemonsContext.Provider>
</div>
const addAdditionalWeight = (weight) => {
setAdditionalWeight((x) => x + weight);
);
}
};
export default App;
const removeAdditionalWeight = (weight) => {
setAdditionalWeight((x) => x - weight);
};
const pokemons = useMemo(
() => ['slowpoke', 'pikachu', 'psyduck'],
[],
);

14.

useMemo (борьба с useContext)
// Pokemon.js
return useMemo(
import React, {
() => (
useState, memo, useContext, useMemo,
<>
} from 'react';
<h2>{name}</h2>
import PokemonsContext from './contexts/pokemons-context';
<strong>
Вес:
const weightStep = 50;
{' '}
{currentWeight}
function Pokemon({ name, weight, img }) {
{' '}
const { addAdditionalWeight, removeAdditionalWeight } =
гектограмм
useContext(PokemonsContext);
</strong>
const [currentWeight, setCurrentWeight] = useState(weight);
<button onClick={addWeight} type="button">
function addWeight() {
Потолстеть
setCurrentWeight((x) => x + weightStep);
</button>
addAdditionalWeight(weightStep);
<button onClick={removeWeight} type="button">
}
Похудеть
function removeWeight() {
</button>
setCurrentWeight((x) => x - weightStep);
<img
removeAdditionalWeight(weightStep);
width={96 * (currentWeight / weight)}
}
src={img}
alt={name}
style={{ display: 'block' }}
/>
</>
),
[name, weight, img, currentWeight, addWeight, removeWeight],
);
}
export default memo(Pokemon);

15.

useCallback

16.

useCallback (борьба с useContext)
// App.js
// Pokemon.js
import React, { useState, useMemo, useCallback } from 'react';
import React, {
useState, memo, useContext, useMemo, useCallback,
import PokemonsContext from './contexts/pokemons-context';
import PokemonList from './PokemonList';
} from 'react';
import PokemonsAdditionalWeight from './PokemonsAdditionalWeight';
import PokemonsContext from './contexts/pokemons-context';
function App() {
const weightStep = 50;
const [
additionalWeight,
setAdditionalWeight,
] = useState(0);
function Pokemon({ name, weight, img }) {
const { addAdditionalWeight, removeAdditionalWeight } =
useContext(PokemonsContext);
const [currentWeight, setCurrentWeight] = useState(weight);
const addWeight = useCallback(() => {
const addAdditionalWeight = useCallback((weight) => {
setCurrentWeight((x) => x + weightStep);
setAdditionalWeight((x) => x + weight);
addAdditionalWeight(weightStep);
}, []);
}, [addAdditionalWeight]);
const removeWeight = useCallback(() => {
const removeAdditionalWeight = useCallback((weight) => {
setCurrentWeight((x) => x - weightStep);
setAdditionalWeight((x) => x - weight);
removeAdditionalWeight(weightStep);
}, []);
}, [removeAdditionalWeight]);
return useMemo(
const pokemons = useMemo(
() => (
<>
() => ['slowpoke', 'pikachu', 'psyduck'],
...
[],
</>
);
),
[name, weight, img, currentWeight, addWeight, removeWeight],
return (
);
...
);
}
}
export default memo(Pokemon);
export default App;

17.

useCallback
useCallback + useMemo позволяют организовать точечный
рендеринг только тех компонентов, которые реально
поменялись, даже если используется useContext.

18.

useReducer

19.

Reducer && useReducer
// contexts/pokemons-context.js
// App.js
import { createContext } from 'react';
import React, { useReducer } from 'react';
import PokemonsContext, { reducer } from './contexts/pokemons-context';
export function reducer(state, action) {
switch (action.type) {
import PokemonList from './PokemonList';
import PokemonsAdditionalWeight from './PokemonsAdditionalWeight';
case 'addAdditionalWeight':
return {
const pokemons = ['slowpoke', 'pikachu', 'psyduck'];
...state,
additionalWeight: state.additionalWeight + action.weight,
function App() {
};
const [state, dispatch] = useReducer(
case 'removeAdditionalWeight':
reducer,
return {
{ additionalWeight: 0 },
...state,
);
additionalWeight: state.additionalWeight - action.weight,
};
return (
default:
<div className="App">
return state;
<PokemonsContext.Provider
}
value={{
}
...state,
dispatch,
export default createContext({
}}
additionalWeight: 0,
>
dispatch: () => {},
<PokemonsAdditionalWeight />
});
<PokemonList pokemons={pokemons} />
</PokemonsContext.Provider>
</div>
);
}
export default App;

20.

На стороне потребителя
// Pokemon.js
// PokemonsAdditionalWeight.js
import React, {
import React, { useContext } from 'react';
useState, memo, useContext, useMemo, useCallback,
import PokemonsContext from './contexts/pokemons-context';
} from 'react';
import PokemonsContext from './contexts/pokemons-context';
function PokemonsAdditionalWeight() {
const { additionalWeight } = useContext(PokemonsContext);
const weightStep = 50;
return (
<h2>
function Pokemon({ name, weight, img }) {
Дополнительный вес покемонов:
const { dispatch } = useContext(PokemonsContext);
{additionalWeight}
const [currentWeight, setCurrentWeight] = useState(weight);
</h2>
const addWeight = useCallback(() => {
setCurrentWeight((x) => x + weightStep);
);
}
dispatch({
type: 'addAdditionalWeight',
weight: weightStep,
});
}, [dispatch]);
const removeWeight = useCallback(() => {
setCurrentWeight((x) => x - weightStep);
dispatch({
type: 'removeAdditionalWeight',
weight: weightStep,
});
}, [dispatch]);
return useMemo(
...
);
}
export default memo(Pokemon);
export default PokemonsAdditionalWeight;

21.

useReducer
Вместо множества функций — dispatch.
Логика изменения данных находится в редьюсере.
Не нужен useCallback на стороне провайдера, т.к. dispatch
не создаётся заново.

22.

CSS modules
SCSS

23.

CSS modules
/* Pokemon.module.css */
// Pokemon.js
.pokemonContainer {
import React, {
width: 40%;
useState, memo, useContext, useMemo, useCallback,
margin: 0 auto;
} from 'react';
display: flex;
import PokemonsContext from './contexts/pokemons-
flex-direction: column;
context';
align-items: stretch;
import styles from './Pokemon.module.css';
}
function Pokemon({ name, weight, img }) {
.pokemonContainer img {
...
order: -1
return useMemo(
}
() => (
<div className={styles.pokemonContainer}>
.pokemonContainer h2 {
...
order: -2
</div>
}
),
[name, weight, img, currentWeight, addWeight,
removeWeight],
);
}
export default memo(Pokemon);

24.

SCSS
/* Pokemon.module.scss */
// Pokemon.js
.pokemonContainer {
import React, {
width: 40%;
useState, memo, useContext, useMemo, useCallback,
margin: 0 auto;
} from 'react';
display: flex;
import PokemonsContext from './contexts/pokemons-
flex-direction: column;
context';
align-items: stretch;
import styles from './Pokemon.module.scss';
img {
order: -1
function Pokemon({ name, weight, img }) {
}
...
h2 {
return useMemo(
order: -2
() => (
}
<div className={styles.pokemonContainer}>
}
...
</div>
),
[name, weight, img, currentWeight, addWeight,
removeWeight],
);
}
export default memo(Pokemon);
English     Русский Rules