14. Refs и работа с DOM
🔗 Refs и работа с DOM в Solid.js
Заголовок раздела «🔗 Refs и работа с DOM в Solid.js»Яша, в React ты привык к useRef — создаёшь объект { current: null }, вешаешь на JSX, и React заполняет .current после монтирования. В Solid.js всё проще и честнее: ref — это просто присваивание переменной. Никаких обёрток, никаких .current. Давай разберёмся! 🎯
🏗️ Базовый синтаксис ref
Заголовок раздела «🏗️ Базовый синтаксис ref»В Solid ref работает как обычная переменная — компилятор JSX превращает ref={el} в присваивание el = element при монтировании.
import { onMount } from 'solid-js';
function App() { let inputEl: HTMLInputElement; // просто переменная, не ref-объект!
onMount(() => { inputEl.focus(); // уже доступен console.log(inputEl.tagName); // "INPUT" });
return <input ref={inputEl!} placeholder="Автофокус" />;}⚠️ Важно:
inputElбудетundefinedдо монтирования. TypeScript об этом предупредит — поэтомуref={inputEl!}с!. ИспользуйonMountилиcreateEffectдля работы с ним.
⚡ ref со сигналом (signal as ref)
Заголовок раздела «⚡ ref со сигналом (signal as ref)»Это мощная фича Solid — можно передать setter сигнала как ref. Элемент будет записан прямо в сигнал:
import { createSignal, createEffect } from 'solid-js';
function MeasuredBox() { const [el, setEl] = createSignal<HTMLDivElement | null>(null); const [size, setSize] = createSignal({ width: 0, height: 0 });
// Реактивно реагируем на появление элемента createEffect(() => { const element = el(); if (element) { const rect = element.getBoundingClientRect(); setSize({ width: rect.width, height: rect.height }); } });
return ( <div> {/* setter сигнала — прямо в ref! */} <div ref={setEl} style={{ padding: '20px', background: '#1e293b' }}> Измеряемый блок </div> <p>Размер: {size().width}×{size().height}px</p> </div> );}Это работает потому что Solid под капотом вызывает ref(element) — а setEl как раз функция-сеттер!
🔄 Callback ref
Заголовок раздела «🔄 Callback ref»Помимо переменной и сигнала, можно использовать функцию-колбэк — она вызовется сразу при монтировании:
function App() { function setupCanvas(canvas: HTMLCanvasElement) { const ctx = canvas.getContext('2d')!; ctx.fillStyle = '#2c67d5'; ctx.fillRect(10, 10, 100, 100); ctx.strokeStyle = '#34d399'; ctx.strokeRect(20, 20, 80, 80); }
return ( <canvas ref={setupCanvas} width={200} height={150} /> );}💡 Колбэк получает DOM-элемент как первый аргумент. Отлично подходит для инициализации canvas, интеграции с D3.js, Three.js и другими библиотеками.
👶 Refs дочерних компонентов
Заголовок раздела «👶 Refs дочерних компонентов»Чтобы получить ref из дочернего компонента, используй props.ref + ref={props.ref}:
import { JSX } from 'solid-js';
interface FancyInputProps { ref?: HTMLInputElement | ((el: HTMLInputElement) => void); placeholder?: string;}
function FancyInput(props: FancyInputProps) { return ( <input ref={props.ref} // пробрасываем ref вниз class="fancy-input" placeholder={props.placeholder} /> );}
function Parent() { let inputRef: HTMLInputElement;
return ( <div> <FancyInput ref={inputRef!} placeholder="Фокус из родителя" /> <button onClick={() => inputRef.focus()}> Фокус! </button> </div> );}🎭 Интеграция со сторонними библиотеками
Заголовок раздела «🎭 Интеграция со сторонними библиотеками»Один из главных кейсов для ref — подключение DOM-библиотек. Вот пример с Chart.js:
import { onMount, onCleanup } from 'solid-js';
function ChartComponent() { let canvasRef: HTMLCanvasElement; let chartInstance: any;
onMount(() => { // Импортируем Chart.js только на клиенте import('chart.js/auto').then(({ Chart }) => { chartInstance = new Chart(canvasRef, { type: 'line', data: { labels: ['Янв', 'Фев', 'Мар', 'Апр', 'Май'], datasets: [{ label: 'Продажи', data: [12, 19, 3, 5, 2], borderColor: '#2c67d5', }] } }); }); });
onCleanup(() => { chartInstance?.destroy(); // очищаем при размонтировании });
return <canvas ref={canvasRef!} />;}🚨 Частые ошибки
Заголовок раздела «🚨 Частые ошибки»// ❌ ОШИБКА: обращение к ref вне onMountfunction Bad() { let el: HTMLDivElement; console.log(el!.offsetHeight); // ошибка! el ещё undefined
return <div ref={el!}>Текст</div>;}
// ✅ ПРАВИЛЬНО: внутри onMountfunction Good() { let el: HTMLDivElement;
onMount(() => { console.log(el.offsetHeight); // теперь доступен });
return <div ref={el!}>Текст</div>;}
// ❌ ОШИБКА: React-стиль с useRef (не нужен в Solid!)function AlsoWrong() { const ref = { current: null }; // лишняя обёртка! return <div ref={ref}>...</div>; // ref.current не будет заполнен!}🔀 createEffect vs onMount для работы с ref
Заголовок раздела «🔀 createEffect vs onMount для работы с ref»function App() { let el: HTMLElement; const [visible, setVisible] = createSignal(true);
// onMount — один раз при монтировании onMount(() => { el.style.opacity = '1'; });
// createEffect — реактивно, при изменении зависимостей createEffect(() => { if (visible()) { el.classList.add('active'); } else { el.classList.remove('active'); } });
return ( <div> <div ref={el!} class="box">Содержимое</div> <button onClick={() => setVisible(v => !v)}> Переключить </button> </div> );}⚡
onMount— это простоcreateEffectс отложенным запуском (после рендера). ИспользуйonMountдля инициализации,createEffect— для реактивных изменений DOM.