M3 | WebDev
103 subscribers
105 photos
4 videos
14 links
Your guide to the world of programming 🌐🚀
Download Telegram
Вчера мы говорили про отличия Vue и React, там упоминались "реактивные прокси", хочу описать что это такое.

🌟 Реактивные прокси в JavaScript: мощный инструмент для управления состоянием! 🚀

Если вы работаете с JavaScript и хотите создавать приложения с интерактивным интерфейсом, то реактивные прокси — это то, что нужно. Они позволяют отслеживать изменения в объектах и автоматически реагировать на них. Это основа таких библиотек, как Vue, React (через состояния), MobX и других.

Что такое реактивный прокси?
Это объект, обёрнутый в Proxy, который позволяет вам "шпионить" за его изменениями. С его помощью можно перехватывать чтение, запись и удаление свойств, а также выполнять дополнительные действия, такие как обновление интерфейса.

Как это работает? 🔧
Пример создания реактивного объекта:

const state = {
count: 0,
};

const reactiveState = new Proxy(state, {
get(target, prop) {
console.log(`Чтение свойства: ${prop}`);
return target[prop];
},
set(target, prop, value) {
console.log(`Изменение свойства: ${prop}, новое значение: ${value}`);
target[prop] = value;
// Дополнительно: можно вызывать функции для обновления UI
return true;
},
});

reactiveState.count; // Лог: Чтение свойства: count
reactiveState.count = 5; // Лог: Изменение свойства: count, новое значение: 5


Зачем это нужно?
1. Отслеживание состояния. Прокси позволяет знать, что изменилось, и на основе этого реагировать.

2. Управление данными. Можно добавлять проверки, логику валидации или другие действия при изменении свойств.

3. Упрощение работы с UI. В таких библиотеках, как Vue или MobX, это ключевая концепция для автоматического обновления интерфейса.

Реактивность в популярных фреймворках
• Vue.js: Использует Proxy для создания реактивных данных. Например, reactive() и ref().
• MobX: Строит реактивные цепочки для отслеживания и автоматического пересчёта зависимостей.
• React: Реализует аналог реактивности через хуки (useState, useEffect), но без Proxy.

Важно помнить ⚠️
• Прокси работают только с объектами и массивами. Примитивы не поддерживаются напрямую.
• Следите за производительностью: большие объекты могут замедлить приложение.
В прошлом посте я упомянул, что React реализует аналог реактивности через хуки (useState, useEffect), но без Proxy и мне в личку полетели вопросы, сейчас хочу описать как это делает React.

В React реактивность реализуется через систему виртуального DOM и обновление состояний, но без использования Proxy.

1. Основные концепции React
useState: Позволяет хранить и изменять состояние компонента.
useEffect: Слушает изменения состояния или пропсов и выполняет побочные эффекты.
• React использует механизм сравнения (reconciliation), чтобы определить, какие части интерфейса нужно обновить.

2. Как работает реактивность в React?
Вместо перехвата изменений в объекте (как в Proxy), React применяет декларативный подход:

I. Изменение состояния через useState:
• Когда вы вызываете функцию setState (например, setCount), React получает сигнал о том, что состояние изменилось.
• React планирует перерендерить компонент (это называется re-rendering).

II. Ререндеринг компонента:
• React вызывает компонент-функцию заново.
• На основе нового состояния React генерирует новый виртуальный DOM.

III. Сравнение виртуальных DOM:
• React сравнивает предыдущую и новую версии виртуального DOM (использует алгоритм diffing).
• React обновляет только те части реального DOM, которые изменились.

3. Почему React не использует Proxy?
React делает ставку на контроль и предсказуемость:
Явные обновления через setState: В React вы должны явно вызывать функцию для изменения состояния (setState или setCount). Это упрощает отслеживание источников изменений.
Производительность: Использование Proxy для всех объектов может быть дорогостоящим, особенно если у вас большое состояние.
Детерминированность: React следит за тем, чтобы компоненты обновлялись только в ответ на заранее определённые изменения (через хуки или контекст).

4. Как React "знает", что нужно обновить?
• Когда вы вызываете setState или setCount, React добавляет обновление в очередь.
• После этого React вызывает "обновляющий цикл":
I. Обновляет состояние компонента.
II. Генерирует новый виртуальный DOM.
III. Сравнивает с предыдущим виртуальным DOM.
IV. Обновляет только изменившиеся элементы в реальном DOM.

5. Плюсы подхода React
Простота: Вы явно управляете состоянием.
Производительность: Алгоритм diffing оптимизирует обновления.
Прогнозируемость: Вы всегда знаете, что вызывает изменения.

Хотя Proxy предоставляет автоматическое отслеживание изменений, React предпочитает декларативный и контролируемый подход для обеспечения надёжности и предсказуемости в обновлениях интерфейса.
Многие не задумываются, но даже так можно делать… 🐱💻

Вот вам пример на JavaScript:

const originalPush = Array.prototype.push;

Array.prototype.push = function (...args) {
console.log('Элементы добавлены:', args);
return originalPush.apply(this, args);
};

const arr = [1, 2, 3];

arr.push(4, 5); // Выведет: Элементы добавлены: [4, 5]


🔥 Что происходит? Мы сохраняем оригинальный метод push, переопределяем его, чтобы добавить console.log, а затем вызываем оригинальный метод через apply.

⚡️ Это может быть полезно для отладки или отслеживания изменений массивов в вашем приложении. Но аккуратнее: изменение встроенных методов может привести к неожиданным багам в других местах вашего кода.
Архитектура — это фундамент любого программного проекта. Она определяет, как система будет развиваться, масштабироваться и поддерживаться. Ошибки на этом этапе могут привести к хаосу, увеличенным срокам и непредсказуемости. Именно поэтому архитектура — это самое главное в программировании.

Существует множество подходов к проектированию архитектуры:

• Микросервисная архитектура
• Монолитная архитектура
• Чистая архитектура (Clean Architecture)
• Feature-Sliced Design
• И многие другие.

Каждая из них подходит для разных задач, но лично я выбираю доменно-ориентированную архитектуру (Domain-Oriented Architecture, DOA).

🔑 Почему я выбираю DOA?

1. Четкие границы контекста (Bounded Context):
Система делится на независимые домены с четко определенными правилами, что предотвращает «эффект домино».

2. Фокус на бизнес-логике:
Архитектура строится вокруг потребностей бизнеса, а не технологий. Это позволяет создавать решения, которые действительно решают задачи заказчика.

3. Модульность и масштабируемость:
Каждый домен автономен, что упрощает тестирование, поддержку и развитие проекта.

4. Единый язык (Ubiquitous Language):
Использование общего языка между бизнесом и разработчиками помогает избежать недопонимания.

💡 Почему архитектура важна:

• Она задает основу для всей системы.
• Облегчает внедрение новых разработчиков в проект.
• Упрощает масштабирование и добавление новых функций.
• Делает систему предсказуемой и понятной.

Для меня DOA — это подход, который позволяет создавать системы, развивающиеся вместе с бизнесом. Такие системы не только устойчивы к изменениям, но и обладают важным преимуществом: итоговый продукт становится доступным и понятным для разработчиков любого уровня подготовки.
Про всплытие (или event bubbling) в JavaScript 🌊

Всплытие событий — это основополагающий механизм в браузерном JavaScript.

🔧 Как это работает?

1. При нажатии на элемент событие сначала срабатывает на нём (фаза захвата).

2. Затем оно поднимается кроме других объектов (фаза всплытия).

🎓 Пример:
<div id="parent">
<button id="child">Кликни меня!</button>
</div>

document.getElementById('parent').addEventListener('click', () => {
console.log('Клик на parent!');
});

document.getElementById('child').addEventListener('click', () => {
console.log('Клик на child!');
});


🔎 Что получим при клике на child?

1. Сначала выполнится console.log('Клик на child!');

2. Затем сработает console.log('Клик на parent!');

Вот и всплытие 👇

🏢 Когда это не нужно?
Иногда нам нужно остановить пропагацию события.

document.getElementById('child').addEventListener('click', (event) => {
event.stopPropagation(); // Прекращает всплытие
console.log('Теперь только child!');
});

Этот приём часто используют в сложных взаимодействиях.

Если остались вопросы про всплытие и погружение, пишите в комментарии! 🙋‍♂️
💡 preventDefault — как это работает?

Когда вы кликаете по ссылке или отправляете форму, браузер по умолчанию выполняет определённые действия. Например, переходит на другую страницу или перезагружает текущую. Но что, если вы хотите изменить это поведение? 🤔

👉 Метод event.preventDefault() останавливает стандартное действие браузера. Это полезно, если вы хотите реализовать свою логику при каком-либо событии.

Пример:
document.querySelector('a').addEventListener('click', (event) => {
event.preventDefault();
console.log('Клик перехвачен, но перехода не будет!');
});


🔥 Используйте preventDefault, чтобы:

• Сделать кастомную отправку формы через AJAX 📨
• Остановить переходы по ссылкам 📌
• Управлять кликами, свайпами и любыми действиями пользователя 🎮

Но не путайте с stopPropagation()! Этот метод блокирует всплытие событий, а preventDefault — только стандартное поведение.
Контекст провайдер в React: Что это и как его правильно использовать

Контекст провайдер (Context Provider) в React — это мощный инструмент для передачи данных через дерево компонентов, избегая необходимости явно передавать пропсы на каждом уровне. Это особенно полезно, когда данные, такие как тема, авторизация пользователя или настройки приложения, должны быть доступны во многих компонентах.

Что такое контекст в React?
Контекст позволяет создавать глобальные переменные для React-приложения. Он состоит из двух основных частей:

1. Контекст — создаётся с помощью React.createContext и служит хранилищем значений.
2. Провайдер (Provider) — оборачивает компоненты, которым необходимо передать значения.

Пример использования контекста
1. Создание контекста
import React, { createContext, useState } from 'react';

// Создаём контекст
export const ThemeContext = createContext();

export const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState('light');

const toggleTheme = () => {
setTheme((prevTheme) => (prevTheme === 'light' ? 'dark' : 'light'));
};

return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};


2. Использование провайдера
Провайдер оборачивает компоненты, которые должны получать данные из контекста.
import React from 'react';
import { ThemeProvider } from './ThemeContext';
import App from './App';

const Root = () => (
<ThemeProvider>
<App />
</ThemeProvider>
);

export default Root;


3. Потребление контекста в дочерних компонентах
В дочерних компонентах доступ к данным осуществляется через useContext.
import React, { useContext } from 'react';
import { ThemeContext } from './ThemeContext';

const ThemeSwitcher = () => {
const { theme, toggleTheme } = useContext(ThemeContext);

return (
<div>
<p>Текущая тема: {theme}</p>
<button onClick={toggleTheme}>Сменить тему</button>
</div>
);
};

export default ThemeSwitcher;


Контекст провайдер в React — это мощный инструмент для управления состоянием на уровне приложения. Правильное использование контекста упрощает структуру кода и делает приложение более поддерживаемым. Однако всегда помните, что контекст — это не замена сложных менеджеров состояния, и его следует использовать с умом.
Как избежать ада ререндеров с контекстом в React?
Контекст в React передаёт значения всем дочерним компонентам, и любое изменение значения контекста вызывает ререндер всех потребителей (consumer). Это может существенно повлиять на производительность приложения, особенно если дерево компонентов большое.

Вот несколько стратегий, чтобы избежать "ада ререндеров":

1. Разделяйте контексты для разных данных
Если вы используете один контекст для хранения нескольких типов данных (например, тема, авторизация и настройки), то изменение любой из этих данных вызовет ререндер всех потребителей. Лучше создать отдельный контекст для каждой группы данных.

Пример:
// Тема
export const ThemeContext = createContext();

// Авторизация
export const AuthContext = createContext();

Это позволяет минимизировать область ререндеров, так как изменения в одном контексте не затрагивают другой.

2. Передавайте только необходимые данные
Чем больше данных в контексте, тем больше компонентов может быть затронуто ререндером. Старайтесь передавать только те данные, которые необходимы потребителям.

Плохо:
const UserContext = createContext({
user: { id: 1, name: 'Михаил', email: 'example@mail.com' },
updateUser: () => {},
});


Лучше:
const UserContext = createContext();
const UpdateUserContext = createContext();

Теперь компоненты, которым нужно только имя пользователя, не будут ререндериться при изменении функции updateUser.

3. Используйте мемоизацию контекстных значений
Если значение контекста вычисляется динамически, каждый ререндер провайдера будет пересоздавать объект или функцию, даже если данные не изменились. Это приводит к ререндерам потребителей.

Решение — использовать useMemo для мемоизации значения.

Пример:
import React, { createContext, useState, useMemo } from 'react';

export const ThemeContext = createContext();

export const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState('light');

const value = useMemo(() => ({ theme, toggleTheme: () => setTheme((t) => (t === 'light' ? 'dark' : 'light')) }), [theme]);

return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
};

Теперь значение контекста обновляется только при изменении theme.

4. Потребляйте контекст там, где это необходимо
Избегайте использования useContext в компонентах, где данные контекста не нужны. Чем меньше компонентов напрямую потребляют контекст, тем меньше ререндеров.

Плохо:
const Parent = () => {
const { theme } = useContext(ThemeContext); // Используется только в дочернем компоненте
return <Child />;
};


Лучше:
const Parent = () => <Child />;

const Child = () => {
const { theme } = useContext(ThemeContext);
return <p>Тема: {theme}</p>;
};


5. Используйте селекторы контекста
Селекторы позволяют подписываться только на определённые части данных контекста. Для этого можно использовать кастомные хуки или библиотеки, такие как reselect-context.

Пример кастомного хука:
export const useTheme = () => {
const context = useContext(ThemeContext);
if (!context) throw new Error('useTheme must be used within a ThemeProvider');
return context.theme;
};

export const useToggleTheme = () => {
const context = useContext(ThemeContext);
if (!context) throw new Error('useToggleTheme must be used within a ThemeProvider');
return context.toggleTheme;
};

Теперь можно подписываться только на theme или toggleTheme, а не на оба одновременно.

6. Используйте компонент React.memo
Если компонент использует контекст, но не должен ререндериться при его обновлении, можно обернуть его в React.memo.

Пример:
const ThemedComponent = React.memo(() => {
const { theme } = useContext(ThemeContext);
return <div>Текущая тема: {theme}</div>;
});


7. Подключение контекста через компонент-посредник
Если компоненту нужно только обновлять данные контекста, используйте специальный компонент, который изолирует изменения.
Пример:
const ThemeUpdater = () => {
const { toggleTheme } = useContext(ThemeContext);
return <button onClick={toggleTheme}>Сменить тему</button>;
};

Таким образом, ререндер затронет только ThemeUpdater, а не всю структуру.

Заключение
Чтобы избежать "ада ререндеров" при использовании контекста в React:

1. Разделяйте данные на несколько контекстов.
2. Используйте useMemo для мемоизации значений.
3. Подключайте контекст только в необходимых местах.
4. Используйте кастомные хуки и мемоизацию для работы с контекстом.

Применяя эти подходы, вы сможете эффективно управлять состоянием приложения и обеспечить высокую производительность.
🔍 Интересный JavaScript-кейс:
const obj = {
"1": "a",
1: "6",
[1]: "B"
};

console.log(obj["1"]);
💡 Что выведет этот код в консоль?
Anonymous Quiz
50%
a
14%
6
36%
B
💡 CSS-переменные — что это и зачем они нужны?

CSS-переменные (или custom properties) — это мощный инструмент, который позволяет задавать повторно используемые значения прямо в CSS. Они работают как переменные в JavaScript, но предназначены для стилей. 🎨

📌 Как задать CSS-переменную?
:root {
--main-color: #3498db;
--padding-size: 16px;
}


Переменные определяются в специальном синтаксисе --имя-переменной и обычно задаются в :root, чтобы они применялись глобально.

📌 Как использовать?
button {
background-color: var(--main-color);
padding: var(--padding-size);
}


С помощью функции var() можно подключать переменные в любых свойствах.

📌 Зачем это нужно?

1. 📋 Удобство: Меняете переменную — и обновляете дизайн сразу во всех местах.
2. 🎯 Темизация: Легко переключать темы (светлая/тёмная, брендовые цвета).
3. 🛠 Гибкость: Позволяют динамически изменять стили через JavaScript:
document.documentElement.style.setProperty('--main-color', '#e74c3c');


📌 Поддержка
CSS-переменные поддерживаются всеми современными браузерами (кроме IE 😅). Так что смело используйте!
🔥 Emits в Vue: Как правильно передавать события 🔥

Если вы работаете с компонентами Vue, вы наверняка сталкивались с ситуацией, когда нужно передать событие из дочернего компонента в родительский. Для этого используются emits.

Что такое emits?
emits — это способ декларации событий, которые компонент может отправлять. Это делает ваш код более предсказуемым, типизированным и удобным для отладки.

Пример использования:
Дочерний компонент:
<script setup>
import { defineEmits } from 'vue';

// Декларируем событие "submit"
const emit = defineEmits(['submit']);

function handleSubmit() {
emit('submit', { message: 'Данные отправлены!' });
}
</script>

<template>
<button @click="handleSubmit">Отправить</button>
</template>


Родительский компонент:
<script setup>
function onSubmit(payload) {
console.log(payload.message); // Данные отправлены!
}
</script>

<template>
<ChildComponent @submit="onSubmit" />
</template>


Почему это важно?
1️⃣ Ясность и предсказуемость: Выявление всех событий в одном месте (через defineEmits) упрощает понимание кода. Вы всегда знаете, какие события ожидаются.

2️⃣ Типизация событий: Если вы используете TypeScript, можно типизировать события:
<script setup lang="ts">
const emit = defineEmits<{
(event: 'submit', payload: { message: string }): void;
}>();
</script>


3️⃣ Снижение ошибок: Если вы попробуете отправить событие, не описанное в defineEmits, Vue выведет предупреждение в консоль.

📌 Совет: Используйте defineEmits вместе с defineProps, чтобы максимально упростить связь между компонентами.
Валидация на фронте