Skip to content

Instantly share code, notes, and snippets.

@zolotyh
Created October 7, 2025 15:45
Show Gist options
  • Save zolotyh/2b47c78eda3ef3626e33aef458b4e1ae to your computer and use it in GitHub Desktop.
Save zolotyh/2b47c78eda3ef3626e33aef458b4e1ae to your computer and use it in GitHub Desktop.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<title>reveal.js</title>
<link rel="stylesheet" href="dist/reset.css" />
<link rel="stylesheet" href="dist/reveal.css" />
<link rel="stylesheet" href="dist/theme/black.css" />
<!-- Theme used for syntax highlighted code -->
<link rel="stylesheet" href="plugin/highlight/monokai.css" />
</head>
<body>
<div class="reveal">
<div class="slides">
<section>
Эффект бабочки
<aside class="notes">
<ul>
<li>
Фильм эффект бабочки операется на вполне себе научное
размышление
</li>
<li>
Мы принимаем вроде бы незначительные решения, решения влияют на
проект
</li>
<li>Иногда непринятие решение, это тоже решение</li>
</ul>
</aside>
</section>
<section></section>
<section>Старые слайды</section>
<section>
Про что доклад
<aside class="notes">
<ul>
<li>
Почему нельзя слепо доверять принципам написания хорошего кода
</li>
</ul>
</aside>
</section>
<section>
<h2 class="r-fit-text">Переиспользуй это</h2>
</section>
<section>
Создаём универсальную карточку для отображения клиентов, заказов и
продуктов
</section>
<section>
<pre><code data-trim class="typescript">
interface DataCardProps {
title: string;
description: string;
type: &#39;client&#39; | &#39;order&#39; | &#39;product&#39;;
onClick?: () =&gt; void;
}
</code></pre>
</section>
<section>
<pre><code class="typescript" data-trim>
function DataCard({ title, description, type, onClick }: DataCardProps) {
const typeStyles = {
client: &#39;border-l-4 border-blue-500 bg-blue-50&#39;,
order: &#39;border-l-4 border-green-500 bg-green-50&#39;,
product: &#39;border-l-4 border-purple-500 bg-purple-50&#39;,
};
return (
&lt;div
className={`p-4 rounded shadow-md cursor-pointer ${typeStyles[type]}`}
onClick={onClick}
&gt;
&lt;h3 className=&quot;text-lg font-semibold&quot;&gt;{title}&lt;/h3&gt;
&lt;p className=&quot;text-gray-600&quot;&gt;{description}&lt;/p&gt;
&lt;/div&gt;
);
}
</code></pre>
</section>
<section>
<p>
Бизнес требует добавить действия, статусы и поддержку кастомных
иконок
</p>
</section>
<section>
<pre><code data-trim class="typescript">
interface DataCardPropsV2 extends DataCardProps {
icon?: React.ReactNode;
status?: &#39;active&#39; | &#39;inactive&#39; | &#39;pending&#39;;
actions?: { label: string; action: () =&gt; void }[];
className?: string;
}
</code></pre>
</section>
<section>
<pre><code data-trim class="typescript" data-line-numbers>
function DataCardV2({
title,
description,
type,
onClick,
icon,
status,
actions,
className,
}: DataCardPropsV2) {
const typeStyles = {
client: &#39;border-l-4 border-blue-500 bg-blue-50&#39;,
order: &#39;border-l-4 border-green-500 bg-green-50&#39;,
product: &#39;border-l-4 border-purple-500 bg-purple-50&#39;,
};
const statusStyles = {
active: &#39;text-green-600&#39;,
inactive: &#39;text-red-600&#39;,
pending: &#39;text-yellow-600&#39;,
};
return (
&lt;div
className={`p-4 rounded shadow-md ${typeStyles[type]} ${className || &#39;&#39;}`}
onClick={onClick}
&gt;
&lt;div className=&quot;flex items-center&quot;&gt;
{icon &amp;&amp; &lt;span className=&quot;mr-2&quot;&gt;{icon}&lt;/span&gt;}
&lt;h3 className=&quot;text-lg font-semibold&quot;&gt;{title}&lt;/h3&gt;
&lt;/div&gt;
&lt;p className={`text-gray-600 ${status ? statusStyles[status] : &#39;&#39;}`}&gt;
{description}
&lt;/p&gt;
{actions &amp;&amp; (
&lt;div className=&quot;mt-2 flex gap-2&quot;&gt;
{actions.map((action, index) =&gt; (
&lt;button
key={index}
className=&quot;text-sm text-blue-500 hover:underline&quot;
onClick={(e) =&gt; {
e.stopPropagation(); // Предотвращаем вызов onClick карточки
action.action();
}}
&gt;
{action.label}
&lt;/button&gt;
))}
&lt;/div&gt;
)}
&lt;/div&gt;
);
}
</code></pre>
</section>
<section>Проблемы начинаются</section>
<section>
Добавляются поддержка метаданных, аналитики и кастомизации для разных
отделов
</section>
<section>
<pre><code data-trim data-line-numbers>
interface DataCardPropsV3 extends DataCardPropsV2 {
metadata?: Record&lt;string, string&gt;;
trackEvent?: string;
layout?: &#39;compact&#39; | &#39;detailed&#39;;
isDraggable?: boolean;
}
</code></pre>
</section>
<section>
<pre><code data-trim data-line-numbers>
function DataCardV3({
title,
description,
type,
onClick,
icon,
status,
actions,
className,
metadata,
trackEvent,
layout = &#39;detailed&#39;,
isDraggable,
}: DataCardPropsV3) {
const typeStyles = {
client: &#39;border-l-4 border-blue-500 bg-blue-50&#39;,
order: &#39;border-l-4 border-green-500 bg-green-50&#39;,
product: &#39;border-l-4 border-purple-500 bg-purple-50&#39;,
};
const statusStyles = {
active: &#39;text-green-600&#39;,
inactive: &#39;text-red-600&#39;,
pending: &#39;text-yellow-600&#39;,
};
const layoutStyles = {
compact: &#39;p-2 text-sm&#39;,
detailed: &#39;p-4 text-base&#39;,
};
const handleClick = () =&gt; {
if (trackEvent) {
// Отправка события в аналитику (имитация)
console.log(`Track event: ${trackEvent}`);
}
onClick?.();
};
return (
&lt;div
className={`rounded shadow-md ${typeStyles[type]} ${layoutStyles[layout]} ${className || &#39;&#39;} ${
isDraggable ? &#39;cursor-move&#39; : &#39;cursor-pointer&#39;
}`}
onClick={handleClick}
draggable={isDraggable}
onDragStart={isDraggable ? (e) =&gt; e.dataTransfer.setData(&#39;text/plain&#39;, title) : undefined}
&gt;
&lt;div className=&quot;flex items-center&quot;&gt;
{icon &amp;&amp; &lt;span className=&quot;mr-2&quot;&gt;{icon}&lt;/span&gt;}
&lt;h3 className=&quot;text-lg font-semibold&quot;&gt;{title}&lt;/h3&gt;
&lt;/div&gt;
&lt;p className={`text-gray-600 ${status ? statusStyles[status] : &#39;&#39;}`}&gt;
{description}
&lt;/p&gt;
{metadata &amp;&amp; (
&lt;div className=&quot;mt-2 text-sm text-gray-500&quot;&gt;
{Object.entries(metadata).map(([key, value]) =&gt; (
&lt;div key={key}&gt;
&lt;span className=&quot;font-medium&quot;&gt;{key}:&lt;/span&gt; {value}
&lt;/div&gt;
))}
&lt;/div&gt;
)}
{actions &amp;&amp; (
&lt;div className=&quot;mt-2 flex gap-2&quot;&gt;
{actions.map((action, index) =&gt; (
&lt;button
key={index}
className=&quot;text-sm text-blue-500 hover:underline&quot;
onClick={(e) =&gt; {
e.stopPropagation();
action.action();
}}
&gt;
{action.label}
&lt;/button&gt;
))}
&lt;/div&gt;
)}
&lt;/div&gt;
);
}
</code></pre>
</section>
<section>Хаос и технический долг</section>
<section>
Компонент обрастает новыми фичами: поддержка тем, мультимедиа,
локализация и асинхронные действия
</section>
<section>
<pre><code data-trim data-line-numbers>
interface DataCardPropsV4 extends DataCardPropsV3 {
theme?: &#39;light&#39; | &#39;dark&#39;;
media?: { type: &#39;image&#39; | &#39;video&#39;; url: string };
locale?: &#39;en&#39; | &#39;ru&#39; | &#39;es&#39;;
asyncAction?: () =&gt; Promise&lt;void&gt;;
isSelectable?: boolean;
}
</code></pre>
</section>
<section>
<pre><code data-trim data-line-numbers>
function DataCardV4({
title,
description,
type,
onClick,
icon,
status,
actions,
className,
metadata,
trackEvent,
layout = &#39;detailed&#39;,
isDraggable,
theme = &#39;light&#39;,
media,
locale = &#39;en&#39;,
asyncAction,
isSelectable,
}: DataCardPropsV4) {
const typeStyles = {
client: &#39;border-l-4 border-blue-500 bg-blue-50&#39;,
order: &#39;border-l-4 border-green-500 bg-green-50&#39;,
product: &#39;border-l-4 border-purple-500 bg-purple-50&#39;,
};
const statusStyles = {
active: &#39;text-green-600&#39;,
inactive: &#39;text-red-600&#39;,
pending: &#39;text-yellow-600&#39;,
};
const layoutStyles = {
compact: &#39;p-2 text-sm&#39;,
detailed: &#39;p-4 text-base&#39;,
};
const themeStyles = {
light: &#39;bg-opacity-90&#39;,
dark: &#39;bg-gray-800 text-white bg-opacity-80&#39;,
};
const localeContent = {
en: { view: &#39;View&#39;, edit: &#39;Edit&#39; },
ru: { view: &#39;Просмотр&#39;, edit: &#39;Редактировать&#39; },
es: { view: &#39;Ver&#39;, edit: &#39;Editar&#39; },
};
const handleClick = async () =&gt; {
if (trackEvent) {
console.log(`Track event: ${trackEvent}`);
}
if (asyncAction) {
try {
await asyncAction();
} catch (error) {
console.error(&#39;Async action failed:&#39;, error);
}
}
onClick?.();
};
return (
&lt;div
className={`rounded shadow-md ${typeStyles[type]} ${layoutStyles[layout]} ${themeStyles[theme]} ${
isSelectable ? &#39;ring-2 ring-blue-300&#39; : &#39;&#39;
} ${className || &#39;&#39;} ${isDraggable ? &#39;cursor-move&#39; : &#39;cursor-pointer&#39;}`}
onClick={handleClick}
draggable={isDraggable}
onDragStart={isDraggable ? (e) =&gt; e.dataTransfer.setData(&#39;text/plain&#39;, title) : undefined}
&gt;
{media &amp;&amp; (
&lt;div className=&quot;mb-2&quot;&gt;
{media.type === &#39;image&#39; ? (
&lt;img src={media.url} alt={title} className=&quot;w-full h-32 object-cover rounded&quot; /&gt;
) : (
&lt;video src={media.url} controls className=&quot;w-full h-32 rounded&quot; /&gt;
)}
&lt;/div&gt;
)}
&lt;div className=&quot;flex items-center&quot;&gt;
{icon &amp;&amp; &lt;span className=&quot;mr-2&quot;&gt;{icon}&lt;/span&gt;}
&lt;h3 className=&quot;text-lg font-semibold&quot;&gt;{title}&lt;/h3&gt;
&lt;/div&gt;
&lt;p className={`text-gray-600 ${status ? statusStyles[status] : &#39;&#39;}`}&gt;
{description}
&lt;/p&gt;
{metadata &amp;&amp; (
&lt;div className=&quot;mt-2 text-sm text-gray-500&quot;&gt;
{Object.entries(metadata).map(([key, value]) =&gt; (
&lt;div key={key}&gt;
&lt;span className=&quot;font-medium&quot;&gt;{key}:&lt;/span&gt; {value}
&lt;/div&gt;
))}
&lt;/div&gt;
)}
{actions &amp;&amp; (
&lt;div className=&quot;mt-2 flex gap-2&quot;&gt;
{actions.map((action, index) =&gt; (
&lt;button
key={index}
className=&quot;text-sm text-blue-500 hover:underline&quot;
onClick={(e) =&gt; {
e.stopPropagation();
action.action();
}}
&gt;
{localeContent[locale][action.label.toLowerCase()] || action.label}
&lt;/button&gt;
))}
&lt;/div&gt;
)}
&lt;/div&gt;
);
}
</code></pre>
</section>
<section>
<pre><code data-line-number data-trim>
function CRMPage() {
return (
&lt;div&gt;
&lt;DataCardV4
title=&quot;John Doe&quot;
description=&quot;VIP Client since 2022&quot;
type=&quot;client&quot;
status=&quot;active&quot;
layout=&quot;compact&quot;
theme=&quot;dark&quot;
isSelectable={true}
media={{ type: &#39;image&#39;, url: &#39;/client-photo.jpg&#39; }}
metadata={{ email: &#39;[email protected]&#39;, phone: &#39;+1234567890&#39; }}
trackEvent=&quot;client_card_click&quot;
locale=&quot;ru&quot;
asyncAction={async () =&gt; {
await new Promise((resolve) =&gt; setTimeout(resolve, 1000));
console.log(&#39;Client data fetched&#39;);
}}
actions={[
{ label: &#39;view&#39;, action: () =&gt; console.log(&#39;View client&#39;) },
{ label: &#39;edit&#39;, action: () =&gt; console.log(&#39;Edit client&#39;) },
]}
/&gt;
&lt;DataCardV4
title=&quot;Order #1234&quot;
description=&quot;Pending delivery&quot;
type=&quot;order&quot;
status=&quot;pending&quot;
isDraggable={true}
className=&quot;mt-4&quot;
/&gt;
&lt;/div&gt;
);
}
</code></pre>
</section>
<section>
Специализация вместо универсальности
<aside class="notes">
Вместо одного DataCard создать отдельные компоненты: ClientCard,
OrderCard, ProductCard. Каждый компонент должен решать конкретную
задачу с минимальным набором пропсов. Пример: ClientCard для
отображения email и телефона, OrderCard с поддержкой drag-and-drop,
ProductCard с мультимедиа.
</aside>
</section>
<section>
Использование композиции
<aside class="notes">
<ul>
<li>
Разделить общую логику и стили на хуки и утилиты. Например,
использовать хук useCardStyles для стилей и useAnalytics для
трекинга событий.
</li>
<li>
Пример: Общие стили (границы, отступы) можно вынести в Tailwind
CSS или CSS-модули, а специфические — оставить в компонентах.
</li>
</ul>
</aside>
</section>
<section>
Чёткое разделение ответственности
<aside class="notes">
<ul>
<li>
UI-компоненты должны отвечать только за отображение, а
бизнес-логика (аналитика, асинхронные действия) — выноситься в
хуки или сервисы.
</li>
<li>
Пример: Логика аналитики могла быть реализована через хук
useAnalyticsTrack, а локализация — через библиотеку i18next.
</li>
</ul>
</aside>
</section>
<section>
<pre><code data-trim data-line-numbers="1-200|1-10|15-23|25-37|39-47|49-70">
// Утилита для стилей (заменяем объектные маппинги на Tailwind CSS)
const getCardStyles = (type: &#39;client&#39; | &#39;order&#39; | &#39;product&#39;, theme: &#39;light&#39; | &#39;dark&#39; = &#39;light&#39;) =&gt; {
const typeStyles = {
client: &#39;border-l-4 border-blue-500 bg-blue-50&#39;,
order: &#39;border-l-4 border-green-500 bg-green-50&#39;,
product: &#39;border-l-4 border-purple-500 bg-purple-50&#39;,
};
const themeStyles = {
light: &#39;bg-opacity-90&#39;,
dark: &#39;bg-gray-800 text-white bg-opacity-80&#39;,
};
return `p-4 rounded shadow-md ${typeStyles[type]} ${themeStyles[theme]}`;
};
// Хук для аналитики
const useAnalyticsTrack = (trackEvent?: string) =&gt; {
const track = () =&gt; {
if (trackEvent) {
console.log(`Track event: ${trackEvent}`);
}
};
return track;
};
// Хук для асинхронных действий
const useAsyncAction = (asyncAction?: () =&gt; Promise&lt;void&gt;) =&gt; {
const execute = async () =&gt; {
if (asyncAction) {
try {
await asyncAction();
} catch (error) {
console.error(&#39;Async action failed:&#39;, error);
}
}
};
return execute;
};
// Специализированный компонент для клиентов
interface ClientCardProps {
title: string;
description: string;
metadata: Record&lt;string, string&gt;;
theme?: &#39;light&#39; | &#39;dark&#39;;
onClick?: () =&gt; void;
trackEvent?: string;
}
function ClientCard({ title, description, metadata, theme = &#39;light&#39;, onClick, trackEvent }: ClientCardProps) {
const track = useAnalyticsTrack(trackEvent);
const handleClick = () =&gt; {
track();
onClick?.();
};
return (
&lt;div className={getCardStyles(&#39;client&#39;, theme)} onClick={handleClick}&gt;
&lt;h3 className=&quot;text-lg font-semibold&quot;&gt;{title}&lt;/h3&gt;
&lt;p className=&quot;text-gray-600&quot;&gt;{description}&lt;/p&gt;
&lt;div className=&quot;mt-2 text-sm text-gray-500&quot;&gt;
{Object.entries(metadata).map(([key, value]) =&gt; (
&lt;div key={key}&gt;
&lt;span className=&quot;font-medium&quot;&gt;{key}:&lt;/span&gt; {value}
&lt;/div&gt;
))}
&lt;/div&gt;
&lt;/div&gt;
);
}
// Специализированный компонент для заказов с поддержкой drag-and-drop
interface OrderCardProps {
title: string;
description: string;
status: &#39;active&#39; | &#39;inactive&#39; | &#39;pending&#39;;
isDraggable?: boolean;
theme?: &#39;light&#39; | &#39;dark&#39;;
onClick?: () =&gt; void;
trackEvent?: string;
}
function OrderCard({ title, description, status, isDraggable, theme = &#39;light&#39;, onClick, trackEvent }: OrderCardProps) {
const track = useAnalyticsTrack(trackEvent);
const statusStyles = {
active: &#39;text-green-600&#39;,
inactive: &#39;text-red-600&#39;,
pending: &#39;text-yellow-600&#39;,
};
const handleClick = () =&gt; {
track();
onClick?.();
};
return (
&lt;div
className={`${getCardStyles(&#39;order&#39;, theme)} ${isDraggable ? &#39;cursor-move&#39; : &#39;cursor-pointer&#39;}`}
draggable={isDraggable}
onDragStart={isDraggable ? (e) =&gt; e.dataTransfer.setData(&#39;text/plain&#39;, title) : undefined}
onClick={handleClick}
&gt;
&lt;h3 className=&quot;text-lg font-semibold&quot;&gt;{title}&lt;/h3&gt;
&lt;p className={statusStyles[status]}&gt;{description}&lt;/p&gt;
&lt;/div&gt;
);
}
// Специализированный компонент для продуктов с мультимедиа
interface ProductCardProps {
title: string;
description: string;
media?: { type: &#39;image&#39; | &#39;video&#39;; url: string };
theme?: &#39;light&#39; | &#39;dark&#39;;
onClick?: () =&gt; void;
trackEvent?: string;
}
function ProductCard({ title, description, media, theme = &#39;light&#39;, onClick, trackEvent }: ProductCardProps) {
const track = useAnalyticsTrack(trackEvent);
const handleClick = () =&gt; {
track();
onClick?.();
};
return (
&lt;div className={getCardStyles(&#39;product&#39;, theme)} onClick={handleClick}&gt;
{media &amp;&amp; (
&lt;div className=&quot;mb-2&quot;&gt;
{media.type === &#39;image&#39; ? (
&lt;img src={media.url} alt={title} className=&quot;w-full h-32 object-cover rounded&quot; /&gt;
) : (
&lt;video src={media.url} controls className=&quot;w-full h-32 rounded&quot; /&gt;
)}
&lt;/div&gt;
)}
&lt;h3 className=&quot;text-lg font-semibold&quot;&gt;{title}&lt;/h3&gt;
&lt;p className=&quot;text-gray-600&quot;&gt;{description}&lt;/p&gt;
&lt;/div&gt;
);
}
// Пример использования рефакторинга
function CRMPage() {
return (
&lt;div&gt;
&lt;ClientCard
title=&quot;John Doe&quot;
description=&quot;VIP Client since 2022&quot;
metadata={{ email: &#39;[email protected]&#39;, phone: &#39;+1234567890&#39; }}
theme=&quot;dark&quot;
trackEvent=&quot;client_card_click&quot;
onClick={() =&gt; console.log(&#39;Client clicked&#39;)}
/&gt;
&lt;OrderCard
title=&quot;Order #1234&quot;
description=&quot;Pending delivery&quot;
status=&quot;pending&quot;
isDraggable={true}
theme=&quot;light&quot;
trackEvent=&quot;order_card_click&quot;
onClick={() =&gt; console.log(&#39;Order clicked&#39;)}
/&gt;
&lt;ProductCard
title=&quot;Product XYZ&quot;
description=&quot;New product&quot;
media={{ type: &#39;image&#39;, url: &#39;/product-photo.jpg&#39; }}
theme=&quot;light&quot;
trackEvent=&quot;product_card_click&quot;
onClick={() =&gt; console.log(&#39;Product clicked&#39;)}
/&gt;
&lt;/div&gt;
);
}
</code></pre>
</section>
<section>
<pre><code data-line-number data-trim>
function CRMPage() {
return (
&lt;div&gt;
&lt;ClientCard
title=&quot;John Doe&quot;
description=&quot;VIP Client since 2022&quot;
metadata={{ email: &#39;[email protected]&#39;, phone: &#39;+1234567890&#39; }}
theme=&quot;dark&quot;
trackEvent=&quot;client_card_click&quot;
onClick={() =&gt; console.log(&#39;Client clicked&#39;)}
/&gt;
&lt;OrderCard
title=&quot;Order #1234&quot;
description=&quot;Pending delivery&quot;
status=&quot;pending&quot;
isDraggable={true}
theme=&quot;light&quot;
trackEvent=&quot;order_card_click&quot;
onClick={() =&gt; console.log(&#39;Order clicked&#39;)}
/&gt;
&lt;ProductCard
title=&quot;Product XYZ&quot;
description=&quot;New product&quot;
media={{ type: &#39;image&#39;, url: &#39;/product-photo.jpg&#39; }}
theme=&quot;light&quot;
trackEvent=&quot;product_card_click&quot;
onClick={() =&gt; console.log(&#39;Product clicked&#39;)}
/&gt;
&lt;/div&gt;
);
}
</code></pre>
</section>
<section>
Вывод:
<p>Стремление к универсальности может привести к хаосу</p>
</section>
<section>
<p>Мета выводы:</p>
<ul>
<li>Cемантика</li>
<li>Разделение ответсвенности</li>
<li>Эмпатия</li>
<li>Будущее</li>
</ul>
</section>
<section>
<h2 class="r-fit-text">Еще раз про DRY</h2>
</section>
<section>
<h2>Webpack</h2>
<pre><code class="javascript" data-trim>
const path = require(&#39;path&#39;);
module.exports = {
entry: &#39;./src/index.js&#39;,
output: {
path: path.resolve(__dirname, &#39;dist&#39;),
filename: &#39;bundle.js&#39;,
clean: true,
},
mode: &#39;development&#39;,
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: &#39;babel-loader&#39;,
options: {
presets: [&#39;@babel/preset-env&#39;, &#39;@babel/preset-react&#39;],
},
},
},
{
test: /\.css$/,
use: [&#39;style-loader&#39;, &#39;css-loader&#39;],
},
],
},
resolve: {
extensions: [&#39;.js&#39;, &#39;.jsx&#39;],
},
devServer: {
static: path.join(__dirname, &#39;dist&#39;),
compress: true,
port: 3000,
},
};
</code></pre>
</section>
<section>
У нас много команд, давайте переиспользуем этот замечательный config
</section>
<section>
<h2>Все просто</h2>
<pre><code data-trim class="javascript">
module.exports = require(&#39;@my-company/configs/webpack.config&#39;);
</code></pre>
</section>
<section>
<h2>&mdash; Мне нужно добавить плагин!</h2>
</section>
<section data-transition="slide none">
<h2>Все просто</h2>
<pre><code data-trim class="javascript">
const commonConfig = require(&#39;@my-company/configs/webpack.config&#39;);
// свой плагин
module.exports = {
...commonConfig,
plugins : [mySuperPlugin]
}
</code></pre>
</section>
<section data-transition="none slide">
<h2>Все просто</h2>
<pre><code data-trim class="javascript" data-line-numbers>
const commonConfig = require(&#39;@my-company/configs/webpack.config&#39;);
module.exports = {
...commonConfig,
plugins : [
...commonConfig.plugins,
mySuperPlugin
]
}
</code></pre>
</section>
<section>
Добро пожаловать в ад!
<ul>
<li class="fragment">Некоторые плагины нужно перенастраивать</li>
<li class="fragment">У всех разные правила</li>
<li class="fragment">У всех разные сервера разработки</li>
</ul>
</section>
<section>
<h2>Что происходит дальше</h2>
<ul>
<li>Нам нужен frontops!</li>
<li>Пишем фреймворк на настройке конфигов</li>
<li>Не меняй корневой пакет, не понятно где все упадет</li>
</ul>
</section>
<section>Помните react-scripts?</section>
<section>
<h2>Craco</h2>
<blockquote>
allows you to get all of the benefits of Create React App without
ejecting
</blockquote>
<pre class="fragment">
CREATE
REACT
APP
CONFIGURATION
OVERRIDE
</pre
>
</section>
<section style="text-align: left">
<p>&mdash; Зачем мы это делали?</p>
<p>&mdash; Хотели единый конфиг!</p>
</section>
<section style="text-align: left">
<p>&mdash; Зачем нам единый конфиг?</p>
<p>
&mdash; Чтобы не настраивать webpack каждый раз, чтобы менять все
настройки в одном месте
</p>
</section>
<section style="text-align: left">
<p>
&mdash; Сколько стоит настроить вебпак для каждого проекта и менять
каждый конфиг отдельно?
</p>
<p>&mdash; Да фиг его знает!</p>
</section>
<!-- <section>Про инженерную интуицию и тягу к прекрасоному</section> -->
<section>
<h2 class="r-fit-text">Имена и типы</h2>
</section>
<section>
<img src="img/login.png" alt="" />
</section>
<section>
<pre><code>
type LoginData = {
"login": "string",
"password": "string"
}
</code></pre>
</section>
<section>
<h2>JSON</h2>
<pre><code data-trim>
{
"username": "aleksei.zolotykh",
"password": "123456"
}
</code></pre>
</section>
<section>
<pre><code data-trim>
const login = ({login, password}) =&gt; {
return fetch(&#39;/login&#39;, {
body: JSON.stringify({username: login , password}
})
}
</code></pre>
</section>
<section>login =&gt; username</section>
<section>
<pre><code class="js" data-trim>
const users = [
{login: "admin", ...},
{login: "superadmin", ...},
{login: "zolotyh", ...}
]
</code></pre>
</section>
<section>
<pre><code class="javascript" data-trim>
users.map(
(user) =&gt; {
...user,
username: user.login
}
)
</code></pre>
<pre class="fragment"><code class="json" data-trim>
{
"login": "admin",
"username": "admin,
}
</code></pre>
<pre class="fragment"><code class="javascript" data-trim>
users.map(
({login, ...rest}) =&gt; {
...rest,
username: login
}
)
</code></pre>
</section>
<section>Эта проблема может быть масштабней</section>
<section>Проект может до 80% состоять из маппингов</section>
<section>
Этой проблемы бы не было, если бы контракт был бы прописан вначале!
</section>
<section>
<pre><code class="yaml" style="font-size: 0.5em; line-height: 1.1" data-line-numbers="1-31|5|13|17-32">
openapi: 3.0.0
...
paths:
/auth/login:
post:
...
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/LoginCredentials'
...
components:
schemas:
LoginCredentials:
type: object
required:
- username
- password
properties:
username:
type: string
description: User's username or email
example: johndoe
password:
type: string
description: User's password
format: password
example: securePassword123!
writeOnly: true
</code></pre>
</section>
<section>Типы</section>
<section>
<pre><code class="typescript" data-trim>
interface User {
id?: string; // теоретически может быть undefined
name: string;
}
</code></pre>
</section>
<section>
<pre><code class="typescript" data-trim>
function processUser(user: User) {
// Ошибка TS: 'id' возможно undefined
console.log(user.id.toUpperCase());
}
</code></pre>
</section>
<section>
<pre><code data-trim>
interface User {
id: string;
name: string;
}
type UnsavedUser = Omit&lt;User, &quot;id&quot;&gt;
</code></pre>
</section>
<section>
<h2>Знай cвои данные!</h2>
</section>
<section>
<h2 class="r-fit-text">3. Про незаконченные миграции</h2>
</section>
<section>
<h2>Большая миграция через правило туриста</h2>
</section>
<section>
<ul>
<li>Я хочу уйти от Redux к Redux Saga</li>
<li class="fragment">Вот прототип</li>
<li class="fragment">Вот первая фича на Saga</li>
<li class="fragment">Остальное по правилу туриста</li>
</ul>
</section>
<section>
<blockquote>
A scout leaves no trace… and tries to leave the world a little
better than he found it
</blockquote>
</section>
<section>
Когда будем переписывать &mdash; переведем на новый стейт менеджер
</section>
<section>
<h2>Теперь у нас есть</h2>
<ul>
<li>Redux</li>
<li>Redux-saga</li>
<li>Redux-toolkit</li>
<li>RxJS</li>
</ul>
<p class="fragment">Надежда, что когда-нибудь это получится</p>
</section>
<section>
<h2>Что нужно было сделать</h2>
<ul>
<li>Будет ли миграция оправдана?</li>
<li>Можете ли вы позволить себе миграцию?</li>
<li>Кто будет отвечать за миграцию?</li>
</ul>
</section>
<section>
<h2 class="r-fit-text">4. Про оптимизации и overengeneering</h2>
</section>
<section>TBD</section>
<section>
<h2 class="r-fit-text">Послесловие</h2>
</section>
</div>
</div>
<script src="dist/reveal.js"></script>
<script src="plugin/notes/notes.js"></script>
<script src="plugin/markdown/markdown.js"></script>
<script src="plugin/highlight/highlight.js"></script>
<script>
// More info about initialization & config:
// - https://revealjs.com/initialization/
// - https://revealjs.com/config/
Reveal.initialize({
hash: true,
// Learn about plugins: https://revealjs.com/plugins/
plugins: [RevealMarkdown, RevealHighlight, RevealNotes],
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment