Skip to content

[Feature][API]: Более гибкая возможность "смешивания" компонентов с помощью render в дополнение к Component #10001

Description

@inomdzhon

Discussed in #9411

Originally posted by andres-kovalev January 29, 2026
Часто возникают ситуации, когда нужно объединить свойства двух компонентов (стиль одного и логику другого, стили двух компонентов, и тд). В большинстве случаев это можно сделать простой композицией.

<Box>
  <Title>Title</Title>
</Box>

Но это увеличивает размер DOM дерева, что на больших интерфейсах может приводить к проблемам производительности, особенно когда такие комбинации используются часто.

Эта проблема достаточно изящно решается с помощью пропа Component, который уже есть в VKUI:

<Box Component={Title}>Title</Box>

Но у этого подхода есть ограничения:

  • работает только для двух компонентов
  • может приводить к коллизии пропов

Обе проблемы можно решить выносом суррогатного компонента, например так:

// объединяем три компонента
const Surrogate = ({ className, style, getRootRef }) => <Component1 Component={Component2} className=... />;

<Component3 Component={Surrogate} />

Минус такого подхода - создание множества "мусорных" суррогатных компонентов когда необходимость объединения возникает во многих местах но комбинации не повторяются.

Технически, API пропа позволяет сделать и так:

<Component3 Component={({ className, style, getRootRef }) => (
  <Component1 Component={Component2} ... />
)} />

Так гораздо лучше. Но фактически это ошибка - такой код создает безымянный компонент при каждом рендере, что приводит к тому, что все поддерево будет ремаунтиться каждый раз.
К сожалению, многие этого не знают и делают так, и даже опытные и знающие ребята рекомендуют такой подход

Предложение:

Что если "починить" последний вариант через добавление отдельного пропа (render) для таких сценариев? Он может работать по аналогии с Component, но более гибко, позволяя не ремаунтить поддерево и даже уменьшить размер vDOM.

<Component3 render={({ className, style, getRootRef }) => (
  <Component1 Component={Component2} ... />
)} />

Новый API может работать параллельно с существующим и на первых порах поддерживать только часть свойств:

const Component = ({ render, ...restProps }) => {
  const className = ...;
  const style = ...;

  if (render) return render({ ...restProps, className, style });

  ...
}

Многие библиотеки реализуют такой API (например, https://base-ui.com/).

В качестве альтернативы некоторые либы (например, https://www.radix-ui.com/) используют свойство asChild, которое позволяет смиксовать два компонента:

<Box asChild>
  <Title>Title</Title>
</Box>

Но по аналогии с пропом Component этот метод не дает такой гибкости, как render (не позволяет динамически манипулировать пропами) и требует чтобы апи объединяемых компонентов соответствовало определенным правилам. render() в свою очередь позволяет производить любые манипуляции, по сути расширяя логику рендера компонентов:

<Box render={
  ({ children }) => Children.length(children) ? <Title>children</Title> : <EmptyIcon />
}>
  {content}
</Box>
```</div>

Metadata

Metadata

Assignees

Fields

No fields configured for Feature.

Projects

Status
🗃 Backlog

Relationships

None yet

Development

No branches or pull requests

Issue actions