7. Пропсы и children
В Solid.js пропсы работают принципиально иначе, чем в React. Ключевое отличие: пропсы реактивны. Это значит, что деструктуризация пропсов убивает реактивность — нельзя писать const { name } = props.
Пропсы реактивны — нельзя деструктурировать
Заголовок раздела «Пропсы реактивны — нельзя деструктурировать»В React деструктурировать пропсы — норма. В Solid это антипаттерн:
// ❌ ПЛОХО в Solid — реактивность теряетсяfunction Greeting({ name }: { name: string }) { return <h1>Hello, {name}</h1>;}
// ✅ ХОРОШО — props читается как геттер, реактивность сохраненаfunction Greeting(props: { name: string }) { return <h1>Hello, {props.name}</h1>;}Под капотом props — это объект с геттерами, которые при обращении регистрируют зависимость в текущем контексте отслеживания.
splitProps — разделение пропсов
Заголовок раздела «splitProps — разделение пропсов»splitProps позволяет безопасно разделить пропсы на части, не теряя реактивность. Это замена деструктуризации для компонентов с «сквозными» пропсами:
import { splitProps } from 'solid-js';
interface ButtonProps { label: string; variant?: 'primary' | 'secondary'; class?: string; onClick?: () => void;}
function Button(props: ButtonProps) { // Разделяем «свои» пропсы от остальных (которые пойдут в <button>) const [local, rest] = splitProps(props, ['label', 'variant']);
return ( <button class={`btn btn-${local.variant ?? 'primary'} ${rest.class ?? ''}`} onClick={rest.onClick} > {local.label} </button> );}rest по-прежнему реактивен — если родитель изменит class или onClick, компонент обновится.
mergeProps — дефолтные значения
Заголовок раздела «mergeProps — дефолтные значения»Вместо props.value ?? defaultValue везде по коду используй mergeProps:
import { mergeProps } from 'solid-js';
interface CardProps { title?: string; color?: string; size?: 'sm' | 'md' | 'lg'; children?: any;}
function Card(props: CardProps) { // Дефолты задаются один раз, и они реактивны const merged = mergeProps( { title: 'Без названия', color: 'blue', size: 'md' as const }, props );
return ( <div class={`card card-${merged.size}`} style={{ color: merged.color }}> <h3>{merged.title}</h3> {merged.children} </div> );}children() — правильная работа с дочерними элементами
Заголовок раздела «children() — правильная работа с дочерними элементами»В Solid нельзя просто передавать props.children в другой компонент или в createEffect. Нужен хелпер children():
import { children, createEffect } from 'solid-js';
function List(props: { children: any }) { // children() создаёт мемоизированный доступ к дочерним элементам const resolved = children(() => props.children);
createEffect(() => { // Здесь можно безопасно работать с DOM-элементами const items = resolved.toArray(); console.log(`Количество дочерних элементов: ${items.length}`); });
return <ul>{resolved()}</ul>;}Без children() дочерние компоненты могут вычисляться несколько раз или не отслеживаться корректно.
Паттерн JSX Slots
Заголовок раздела «Паттерн JSX Slots»Solid поддерживает «слоты» через именованные пропсы с JSX:
interface ModalProps { header: JSX.Element; footer: JSX.Element; children: JSX.Element;}
function Modal(props: ModalProps) { return ( <div class="modal"> <div class="modal-header">{props.header}</div> <div class="modal-body">{props.children}</div> <div class="modal-footer">{props.footer}</div> </div> );}
// Использование:function App() { return ( <Modal header={<h2>Заголовок</h2>} footer={<button>Закрыть</button>} > <p>Основной контент</p> </Modal> );}Динамические пропсы и spread
Заголовок раздела «Динамические пропсы и spread»function Input(props: JSX.InputHTMLAttributes<HTMLInputElement>) { return <input {...props} />;}
// Реактивный spread — обновится если props изменятсяfunction App() { const [disabled, setDisabled] = createSignal(false);
return ( <Input type="text" disabled={disabled()} placeholder="Введите текст..." /> );}Частые ошибки
Заголовок раздела «Частые ошибки»// ❌ Деструктуризация убивает реактивностьfunction Bad({ count }: { count: Accessor<number> }) { // count будет захвачено один раз и не обновится return <div>{count()}</div>;}
// ❌ Ранний доступ к props вне отслеживаемого контекстаfunction AlsoBad(props: { items: string[] }) { const items = props.items; // Реактивность потеряна! return <For each={items}>{item => <li>{item}</li>}</For>;}
// ✅ Правильно — читать props прямо в JSX или внутри реактивного контекстаfunction Good(props: { items: string[] }) { return <For each={props.items}>{item => <li>{item}</li>}</For>;}Интерактивный пример
Заголовок раздела «Интерактивный пример»Демонстрация splitProps, mergeProps и реактивных пропсов: