## 1 Чтобы переписать JS‑код в стиле функционального программирования (ФП), нужно сместить фокус с «как сделать шаг за шагом» (императив) на «что должно получиться» (декларатив), опираясь на чистые функции, неизменяемость данных и композицию. Если ты работаешь с браузерным JS (в том числе для SVG Viewer и Mermaid-диаграмм), покажу практичные приёмы, которые реально применять в таких проектах. --- ## Ключевые принципы, которые нужно внедрить - **Чистые функции**: одинаковый вход → одинаковый выход, без побочных эффектов. - **Неизменяемость**: не мутируем массивы/объекты, а создаём новые. - **Композиция**: собираем логику из маленьких функций, а не пишем большие блоки. - **Отсутствие явных циклов**: вместо `for` — `map/filter/reduce`. - **Явная передача зависимостей**: никаких скрытых глобальных переменных. --- ## Пример: императивный код → функциональный Допустим, у тебя есть логика для списка SVG‑файлов, где ты собираешь имена, фильтруешь по расширению и формируешь HTML. ### Императивный вариант (как часто пишут) ```js const files = [ { name: 'chart.svg', size: 1200 }, { name: 'logo.png', size: 800 }, { name: 'diagram.svg', size: 950 }, ]; const htmlList = []; for (let i = 0; i < files.length; i++) { const f = files[i]; if (f.name.endsWith('.svg')) { htmlList.push(`
  • ${f.name} (${f.size} B)
  • `); } } document.getElementById('file-list').innerHTML = htmlList.join(''); ``` ### Функциональный вариант ```js const isSvg = file => file.name.endsWith('.svg'); const fileToLi = file => `
  • ${file.name} (${file.size} B)
  • `; const renderFileList = (files, containerId) => { const html = files .filter(isSvg) .map(fileToLi) .join(''); document.getElementById(containerId).innerHTML = html; }; renderFileList(files, 'file-list'); ``` Что здесь функционального: - `isSvg` и `fileToLi` — чистые функции. - `.filter/.map` не мутируют массив, а возвращают новые. - Логика разбита на понятные маленькие функции. --- ## Практические приёмы для твоего SVG Viewer ### 1. Избегай мутаций объектов Вместо: ```js files.forEach(f => { f.displayed = true; }); ``` Лучше: ```js const displayedFiles = files.map(f => ({ ...f, displayed: true })); ``` Это критично, если потом будешь делать ререндеры (например, в React) или кэшировать состояния. ### 2. Выноси условия в предикаты ```js const hasSizeAbove = minSize => file => file.size > minSize; const isSvg = file => file.name.toLowerCase().endsWith('.svg'); const largeSvgs = files.filter(isSvg).filter(hasSizeAbove(1000)); ``` Так легче тестировать и переиспользовать логику. ### 3. Композиция функций через `pipe` (или вручную) Если логика становится сложнее, удобно собирать её в цепочку: ```js const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x); const toListItem = file => `
  • ${file.name}
  • `; const filterSvg = arr => arr.filter(f => f.name.endsWith('.svg')); const mapToHtml = arr => arr.map(toListItem).join(''); const renderSvgList = pipe(filterSvg, mapToHtml, html => { document.getElementById('list').innerHTML = html; }); renderSvgList(files); ``` В реальных проектах можно использовать Ramda/lodash/fp, но даже ручной `pipe` уже сильно улучшает читаемость. --- ## Для Mermaid и диаграмм Функциональный стиль особенно полезен, когда ты генерируешь строки диаграмм из данных. ### Императивно ```js let mermaid = 'graph TD\n'; for (const node of nodes) { mermaid += `${node.id}[${node.label}]\n`; } for (const edge of edges) { mermaid += `${edge.from} --> ${edge.to}\n`; } ``` ### Функционально ```js const nodeLine = node => `${node.id}[${node.label}]`; const edgeLine = edge => `${edge.from} --> ${edge.to}`; const buildMermaid = (nodes, edges) => [ 'graph TD', ...nodes.map(nodeLine), ...edges.map(edgeLine), ].join('\n'); const mermaidSource = buildMermaid(nodes, edges); ``` Теперь эту функцию легко протестировать: просто передай массивы и проверь строку. --- ## Частые ошибки и как их избегать - **Мутация DOM внутри фильтров/мапов** — не делай этого. DOM‑манипуляции выноси в отдельные функции (как `renderFileList`). - **Глобальные переменные** — передавай всё явно в аргументы. - **Сложные функции** — разбивай на маленькие чистые. - **«Магические» строки** — выноси в константы или параметры. --- ## Как адаптировать твой текущий app.js (SVG Viewer) Учитывая твои прошлые задачи (панель лога, свойства, ресайзер), вот как можно рефакторить отдельные части: 1. **Чтение конфига**: оставь обёртку в JS (чтобы обойти CORS), но логику парсинга сделай чистой функцией: ```js const parseConfig = raw => { // валидация, нормализация, возврат структуры без побочных эффектов return { files: raw.files || [], settings: raw.settings || {} }; }; ``` 2. **Формирование списка файлов**: вынеси в отдельную функцию, которая возвращает HTML‑строку, а не сразу пишет в DOM. 3. **Логирование**: вместо «добавить строку в лог» сделай функцию, которая принимает текущее состояние лога и новую запись, и возвращает новый массив: ```js const addLog = (logs, entry) => [...logs, entry]; ``` А обновление DOM — отдельной функцией. 4. **Ресайзер панели**: логику расчёта размеров можно сделать чистой функцией от ширины окна и текущего состояния: ```js const computeLayout = (windowWidth, state) => ({ logPanelWidth: Math.max(200, windowWidth * 0.3), contentWidth: windowWidth - Math.max(200, windowWidth * 0.3), }); ``` --- ## Когда функциональный стиль не нужен - В узких местах, где важна максимальная производительность (например, рендер тысяч элементов). - В коде, который тесно связан с DOM‑событиями и состоянием (иногда императив понятнее). Но даже в этих случаях можно сохранять чистоту в «ядре» логики (парсинг, фильтрация, построение данных), а императив оставить только для взаимодействия с DOM. ---