Skip to content

Commit 7dac46d

Browse files
authored
Merge pull request #19 from JavaScript-Basic-OTUS/lesson31
feat: добавлен lesson-31
2 parents f67d6eb + e770816 commit 7dac46d

10 files changed

Lines changed: 1024 additions & 1 deletion

File tree

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/* eslint-disable react/prop-types */
2+
/* eslint-disable react/display-name */
3+
/* eslint-disable react/react-in-jsx-scope */
4+
import React, { useState, memo, useMemo, useCallback } from "react";
5+
6+
const heavyDouble = (num) => {
7+
let result = 0;
8+
// Двойной цикл для большей задержки
9+
for (let i = 0; i < 100000; i++) {
10+
for (let j = 0; j < 10000; j++) {
11+
result = num * 2 + i + j; // Какая-то "полезная" работа
12+
}
13+
}
14+
return result;
15+
};
16+
17+
export default function ExamplePage() {
18+
const [count, setCount] = useState(0);
19+
const [name, setName] = useState("Alla");
20+
const [isAdmin, setIsAdmin] = useState(false);
21+
const [dark, setDark] = useState(false);
22+
23+
const themeStyle = {
24+
backgroundColor: dark ? "tomato" : "white",
25+
};
26+
27+
const setDarkTheme = () => {
28+
setDark((prev) => !prev);
29+
};
30+
31+
const memoUser = useMemo(
32+
() => ({
33+
name,
34+
isAdmin,
35+
}),
36+
[name, isAdmin]
37+
);
38+
39+
const incrementCount = () => {
40+
setCount((count) => count + 1);
41+
};
42+
43+
const resetCount = useCallback(() => {
44+
setCount(0);
45+
}, []);
46+
47+
const doubleCount = useMemo(() => heavyDouble(count), [count]);
48+
49+
return (
50+
<div style={{ ...themeStyle }}>
51+
<Title user={memoUser} resetCount={resetCount} />
52+
<CountInfo count={doubleCount} />
53+
<button onClick={incrementCount}> + 1 </button>
54+
<button onClick={setDarkTheme}>Dark</button>
55+
</div>
56+
);
57+
}
58+
59+
const Title = React.memo(({ user, resetCount }) => {
60+
console.log("Title rendered");
61+
const { name, isAdmin } = user;
62+
63+
return (
64+
<>
65+
<h1>{isAdmin ? name : "Example"}</h1>
66+
<button onClick={resetCount}>RESET</button>
67+
</>
68+
);
69+
});
70+
71+
const CountInfo = ({ count }) => <h3>Count value {count}</h3>;
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/* eslint-disable react/prop-types */
2+
/* eslint-disable react/react-in-jsx-scope */
3+
import { useState } from "react";
4+
5+
// const heavyDouble = (num) => {
6+
// let result = 0;
7+
// // Двойной цикл для большей задержки
8+
// for (let i = 0; i < 100000; i++) {
9+
// for (let j = 0; j < 10000; j++) {
10+
// result = num * 2 + i + j; // Какая-то "полезная" работа
11+
// }
12+
// }
13+
// return result;
14+
// }
15+
16+
export default function ExamplePage() {
17+
const [count, setCount] = useState(0);
18+
19+
const incrementCount = () => {};
20+
21+
return (
22+
<>
23+
<Title />
24+
<CountInfo />
25+
<button> + 1 </button>
26+
</>
27+
);
28+
}
29+
30+
const Title = () => {
31+
<h1>Example</h1>;
32+
};
33+
34+
const CountInfo = ({ count }) => <h3>Count value {count}</h3>;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/* eslint-disable react/prop-types */
2+
/* eslint-disable react/display-name */
3+
/* eslint-disable react/react-in-jsx-scope */
4+
import { memo } from "react";
5+
6+
// Дочерний компонент, который мы хотим оптимизировать
7+
export const CounterButton = memo(({ onClick, label }) => {
8+
console.log("Кнопка перерендерилась:", label);
9+
10+
return <button onClick={onClick}>{label}</button>;
11+
});
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/* eslint-disable react/react-in-jsx-scope */
2+
import { useCallback, useState } from "react";
3+
4+
import { CounterButton } from "./CounterButton";
5+
6+
function UseCallbackPage1() {
7+
const [counter, setCounter] = useState(0);
8+
9+
const incrementCounter = useCallback(() => {
10+
setCounter(counter + 1);
11+
}, [counter]);
12+
13+
return (
14+
<>
15+
<h1>useCallback</h1>
16+
<h3>{counter}</h3>
17+
<button onClick={incrementCounter}> + 1 </button>
18+
</>
19+
);
20+
}
21+
22+
function UseCallbackPage() {
23+
const [counter, setCounter] = useState(0);
24+
const [dummy, setDummy] = useState(0);
25+
26+
// Функция не меняется между рендерами
27+
const incrementCounter = useCallback(() => {
28+
setCounter((prev) => prev + 1); // Используем функциональное обновление
29+
}, []); // Пустые зависимости - функция стабильна
30+
31+
// Без useCallback - создается новая функция при каждом рендере
32+
const badIncrement = () => {
33+
setCounter((prev) => prev + 1);
34+
};
35+
36+
return (
37+
<div>
38+
<h1>useCallback</h1>
39+
<h3>Счетчик: {counter}</h3>
40+
41+
{/* С useCallback - кнопка не перерендеривается лишний раз */}
42+
<CounterButton onClick={incrementCounter} label="+1 (с useCallback)" />
43+
44+
{/* Без useCallback - кнопка перерендеривается всегда */}
45+
<CounterButton onClick={badIncrement} label="+1 (без useCallback)" />
46+
47+
<button onClick={() => setDummy((d) => d + 1)}>
48+
Принудительный перерендер ({dummy})
49+
</button>
50+
51+
<p>Откройте консоль и нажимайте Принудительный перерендер</p>
52+
</div>
53+
);
54+
}
55+
56+
export default UseCallbackPage;
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/* eslint-disable react/prop-types */
2+
/* eslint-disable react/react-in-jsx-scope */
3+
import { useEffect, useState } from "react";
4+
5+
// 1. Не передать ничего
6+
function UseEffectPage1() {
7+
const [, setValue] = useState({});
8+
9+
useEffect(() => {
10+
// Эффект срабатывает при монтировании компонента и при каждом рендере.
11+
console.log("Срабатываю на каждый рендер");
12+
});
13+
14+
return (
15+
<div>
16+
<h1>useEffect</h1>
17+
<button onClick={() => setValue({})}>Обновить состояние</button>
18+
</div>
19+
);
20+
}
21+
22+
// 2. Передать пустой массив - []
23+
function UseEffectPage2() {
24+
const [count, setCount] = useState(0);
25+
26+
useEffect(() => {
27+
// Эффект срабатывает только при монтировании компонента.
28+
console.log("Сработаю 1 раз");
29+
}, []);
30+
31+
return (
32+
<div>
33+
<h1>useEffect</h1>
34+
<h3>{count}</h3>
35+
<button onClick={() => setCount((prev) => prev + 1)}> + 1</button>
36+
</div>
37+
);
38+
}
39+
40+
// 3. Передать массив с переменными для отслеживания - [value]
41+
function UseEffectPage3({ userId = 2 }) {
42+
const [user, setUser] = useState(null);
43+
const [isLoading, setIsLoading] = useState(false);
44+
45+
// Эффект, зависящий от userId
46+
useEffect(() => {
47+
if (!userId) return;
48+
49+
console.log("Сработаю при монтировании и изменении userId");
50+
51+
fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
52+
.then((response) => response.json)
53+
.then((json) => setUser(json))
54+
.catch((error) => console.error("Ошибка загрузки:", error))
55+
.finally(() => setIsLoading(false));
56+
}, [userId]); // Запускается заново, если изменился userId
57+
58+
if (isLoading) return <div>Загрузка...</div>;
59+
if (!user) return <div>Выберите пользователя</div>;
60+
61+
return (
62+
<div>
63+
<h1>useEffect</h1>
64+
<h3>{user.name}</h3>
65+
<p>{user.email}</p>
66+
</div>
67+
);
68+
}
69+
70+
// 4. Пример очистки функции с интервалом
71+
function UseEffectPage() {
72+
const [time, setTime] = useState(new Date().toLocaleTimeString());
73+
74+
useEffect(() => {
75+
console.log("✅ Таймер запущен");
76+
77+
// Запускаем интервал при монтировании
78+
const intervalId = setInterval(() => {
79+
const currentTime = new Date().toLocaleTimeString();
80+
setTime(currentTime);
81+
console.log("🕒 Время обновлено:", currentTime);
82+
}, 1000);
83+
84+
// Функция очистки
85+
return () => {
86+
console.log("Таймер остановлен");
87+
clearInterval(intervalId);
88+
};
89+
}, []); // Пустой массив - эффект только при монтировании/размонтировании
90+
91+
return (
92+
<div>
93+
<h3>Текущее время: {time}</h3>
94+
</div>
95+
);
96+
}
97+
98+
export default UseEffectPage;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/* eslint-disable react/react-in-jsx-scope */
2+
/* eslint-disable react/prop-types */
3+
import { useMemo } from "react";
4+
5+
export default function TodoList({ todos, filter }) {
6+
// БЕЗ useMemo: фильтрация происходит при каждом рендере
7+
// const visibleTodos = todos.filter(todo => {
8+
// console.log("🔴 ФИЛЬТРАЦИЯ (без useMemo)"); // Это будет логироваться часто
9+
// console.log("--------------------"); // Это будет логироваться часто
10+
// return filter === "all" ||
11+
// (filter === "completed" && todo.completed) ||
12+
// (filter === "active" && !todo.completed);
13+
// });
14+
15+
// С useMemo: фильтрация происходит только при изменении todos или filter
16+
const visibleTodos = useMemo(() => {
17+
console.log("🟢 ФИЛЬТРАЦИЯ (с useMemo)"); // Логируется только при изменении зависимостей
18+
return todos.filter(
19+
(todo) =>
20+
filter === "all" ||
21+
(filter === "completed" && todo.completed) ||
22+
(filter === "active" && !todo.completed)
23+
);
24+
}, [todos, filter]); // Зависимости: todos и filter
25+
26+
return (
27+
<ul>
28+
{visibleTodos.map((todo) => (
29+
<li key={todo.id}>{todo.text}</li>
30+
))}
31+
</ul>
32+
);
33+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/* eslint-disable react/react-in-jsx-scope */
2+
import { useState } from "react";
3+
import TodoList from "./TodoList";
4+
5+
const todos = [
6+
{ id: 1, text: "Изучить React", completed: true },
7+
{ id: 2, text: "Написать компонент", completed: false },
8+
{ id: 3, text: "Разобраться с хуками", completed: false },
9+
{ id: 4, text: "Создать приложение", completed: true },
10+
{ id: 5, text: "Оптимизировать производительность", completed: false },
11+
{ id: 6, text: "Изучить useMemo", completed: true },
12+
{ id: 7, text: "Понять useCallback", completed: false },
13+
{ id: 8, text: "Сделать домашнее задание", completed: false },
14+
];
15+
16+
function UseMemoPage() {
17+
const [filter, setFilter] = useState("all");
18+
19+
return (
20+
<div style={{ padding: "20px" }}>
21+
<h1>useMemo</h1>
22+
23+
{/* Кнопки фильтров */}
24+
<div style={{ marginBottom: "20px" }}>
25+
<h3>Выберите фильтр:</h3>
26+
<button
27+
onClick={() => setFilter("all")}
28+
style={{ marginRight: "20px" }}
29+
>
30+
Все задачи
31+
</button>
32+
<button
33+
onClick={() => setFilter("active")}
34+
style={{ marginRight: "20px" }}
35+
>
36+
Активные
37+
</button>
38+
<button onClick={() => setFilter("completed")}>Завершенные</button>
39+
</div>
40+
41+
{/* Компонент со списком задач */}
42+
<TodoList todos={todos} filter={filter} />
43+
</div>
44+
);
45+
}
46+
47+
export default UseMemoPage;
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/* eslint-disable react/react-in-jsx-scope */
2+
import { useState } from "react";
3+
import code from "./../../assets/useState.png";
4+
5+
function UseStatePage() {
6+
const [count, setCount] = useState(0);
7+
8+
const incrementCount = () => setCount((count) => count + 1);
9+
10+
// Функциональное обновление
11+
const decrementCount = () => setCount((prev) => prev - 1);
12+
13+
return (
14+
<>
15+
<h1>Hook useState</h1>
16+
17+
<h3>{count}</h3>
18+
19+
<div
20+
style={{
21+
display: "flex",
22+
justifyContent: "space-around",
23+
marginBottom: "20px",
24+
}}
25+
>
26+
<button onClick={incrementCount}> + 1 </button>
27+
28+
<button onClick={decrementCount}> - 1 </button>
29+
</div>
30+
31+
<img src={code} alt="code" width="500px" />
32+
</>
33+
);
34+
}
35+
36+
export default UseStatePage;
87.5 KB
Loading

0 commit comments

Comments
 (0)