Перейти к содержимому

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 позволяет безопасно разделить пропсы на части, не теряя реактивность. Это замена деструктуризации для компонентов с «сквозными» пропсами:

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, компонент обновится.

Вместо 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() дочерние компоненты могут вычисляться несколько раз или не отслеживаться корректно.

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>
);
}
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 и реактивных пропсов: