Типы рендеринга
Некоторые библиотеки компонентов или дизайн-системы могут ограничивать способы композиции компонентов. Например, Menu должен отображать в качестве дочерних элементов только MenuItems. Типы рендеринга — встроенный способ поддерживать такие ограничения, сохраняя при этом широкие возможности для использования этих компонентов.
Основное поведение
Компонент может объявить, что он отображает, с помощью ключевого слова renders:
import * as React from 'react';
component Header(size: string, color: string) { return <div /> }
component LargeHeader(color: string) renders Header {
return <Header size="large" color={color} />; // Ok!
} Если вы объявляете, что компонент отображает определённый элемент, можно вернуть любой компонент, который в конечном итоге отображает этот элемент в своей цепочке renders:
import * as React from 'react';
component Header(size: string, color: string) { return <div /> }
component LargeHeader(color: string) renders Header {
return <Header size="large" color={color} />;
}
component LargeBlueHeader() renders Header {
// You could also use `renders LargeHeader` above
return <LargeHeader color="blue" />;
} Компоненты могут указывать свойства, отображающие определённые элементы. Для такого свойства можно передать элемент Header или любой компонент, отображающий Header, но нельзя передать компонент, который не отображает Header:
import * as React from 'react';
component Header(size: string, color: string, message: string) {
return <h1 style={{color}}>{message}</h1>;
}
// Renders a Header in its renders chain.
component LargeBlueHeader() renders Header {
return <Header size="large" color="blue" message="Hello" />;
}
// Does not render a Header.
component Footer() {
return <footer />;
}
// The header prop only accepts elements that render a Header.
component Layout(header: renders Header) {
return (
<div>
{header}
<section>Hi</section>
</div>
);
}
<Layout header={<Header size="large" color="blue" message="Hi" />} />; // OK
<Layout header={<LargeBlueHeader />} />; // OK: LargeBlueHeader renders Header
<Layout header={<Footer />} />; // Error: Footer does not render Header Интеграция с дизайн-системой
Типы рендеринга упрощают интеграцию с дизайн-системой. Если свойство компонента дизайн-системы ожидает тип рендеринга, можно скопировать этот тип и вставить его в свой компонент, чтобы интегрировать его с дизайн-системой:
import * as React from 'react';
component Header() {
return <h1>Header!</h1>;
}
component Layout(header: renders Header) {
return <div>{header}</div>;
}
// Copy-paste the header props' type!
component ProductHeader() renders Header {
// We must return a value that renders a Header to satisfy the signature
return <Header />;
}
// And now you can integrate with the design system!
<Layout header={<ProductHeader />} />; // OK! Рендеринг необязательных элементов
Возможно, вы захотите описать компонент, который может принимать дочерний элемент, отображающий в конечном итоге элемент или ничего. Для этого можно использовать специальный вариант типа рендеринга renders?:
import * as React from 'react';
component DesignSystemCardFooter() {
return <div>Footer Content</div>;
}
component DesignSystemCard(
children: React.Node,
footer: renders? DesignSystemCardFooter,
) {
return <div>{children}{footer}</div>;
}
// With these definitions, all of the following work:
<DesignSystemCard footer={<DesignSystemCardFooter />}>Card</DesignSystemCard>;
<DesignSystemCard footer={null}>Card</DesignSystemCard>;
<DesignSystemCard footer={undefined}>Card</DesignSystemCard>;
<DesignSystemCard footer={false}>Card</DesignSystemCard>;
component ProductFooter(hasFooter?: boolean) renders? DesignSystemCardFooter {
return hasFooter && <DesignSystemCardFooter />;
}
<DesignSystemCard footer={<ProductFooter />}>Card</DesignSystemCard>; Рендеринг списков
Возможно, вы захотите описать компонент, который может принимать в качестве свойств любое количество дочерних элементов, отображающих определённый элемент. Для этого можно использовать специальный вариант типа рендеринга renders*:
import * as React from 'react';
component DesignSystemMenuItem() {
return <li>Menu Item</li>;
}
component DesignSystemMenu(
children: renders* DesignSystemMenuItem,
) {
return <ul>{children}</ul>
}
// With these definitions, all of the following work:
const menu1 = (
<DesignSystemMenu>
<DesignSystemMenuItem />
</DesignSystemMenu>
);
const menu2 = (
<DesignSystemMenu>
<DesignSystemMenuItem />
<DesignSystemMenuItem />
</DesignSystemMenu>
);
const menu3 = (
<DesignSystemMenu>
{[
<DesignSystemMenuItem />,
<DesignSystemMenuItem />,
]}
<DesignSystemMenuItem />
</DesignSystemMenu>
);
component ProductMenuItem() renders DesignSystemMenuItem {
return <DesignSystemMenuItem />;
}
const menu4 = (
<DesignSystemMenu>
{[
<ProductMenuItem />,
<DesignSystemMenuItem />,
]}
<DesignSystemMenuItem />
</DesignSystemMenu>
); Прозрачные компоненты
Компоненты могут быть «прозрачными»:
import * as React from 'react';
component TransparentComponent<T extends React.Node>(children: T) renders T {
// .. do something
return children;
}
component Header(text: string) {
return <h1>{text}</h1>;
}
component InstagramHeader() renders Header {
return <Header text="Instagram" />;
}
component Layout(
header: renders Header,
) {
return <div>{header}</div>;
}
component Page() {
const wrappedHeader = <TransparentComponent><InstagramHeader /></TransparentComponent>
return <Layout header={wrappedHeader} />; // Ok!
} Совместимость с компонентами, не использующими синтаксис Component
Вы также можете использовать renders для аннотирования функциональных компонентов:
import * as React from 'react';
component Header(text: string) {
return <h1>{text}</h1>;
}
component InstagramHeader() renders Header {
return <Header text="Instagram" />;
}
component Layout(
header: renders Header,
) {
return <div>{header}</div>;
}
function FunctionHeader(): renders Header {
return <InstagramHeader />;
}
function InstagramPage() {
return <Layout header={<FunctionHeader />} />; // OK!
} Поведение подтипов
Все типы рендеринга являются подтипами как React.Node, так и React.MixedElement.
import * as React from 'react';
component Header() {
return <h1>Hello Header!</h1>;
}
declare const rendersHeader: renders Header;
declare const rendersMaybeHeader: renders? Header;
declare const rendersHeaderList: renders* Header;
rendersHeader as React.Node;
rendersMaybeHeader as React.Node;
rendersHeaderList as React.Node;
rendersHeader as React.MixedElement;
rendersMaybeHeader as React.MixedElement;
rendersHeaderList as React.MixedElement; renders Foo является подтипом renders? Foo, а renders? Foo — подтипом renders* Foo.
import * as React from 'react';
component Header() {
return <h1>Hello Header!</h1>;
}
declare const rendersHeader: renders Header;
declare const rendersMaybeHeader: renders? Header;
declare const rendersHeaderList: renders* Header;
rendersHeader as renders? Header;
rendersHeader as renders* Header;
rendersMaybeHeader as renders* Header;
rendersMaybeHeader as renders Header; // ERROR
rendersHeaderList as renders Header; // ERROR
rendersHeaderList as renders? Header; // ERROR Распространённые проблемы
Типы рендеринга непрозрачны во время выполнения
Типы рендеринга — это понятие, существующее только на уровне типов: во время выполнения нельзя проверять или сравнивать типы компонентов. Например, нельзя проверить, какой компонент был передан свойству с типом рендеринга:
// This does NOT work — you can't compare component identity at runtime
if (icon === FancyIcon) { ... } Если во время выполнения необходимо различать отрендеренные компоненты, вместо этого используйте обёртку в виде непересекающегося объединения:
import * as React from 'react';
component FancyIcon() { return <span />; }
component BasicIcon() { return <span />; }
type IconProp =
| {type: 'fancy', node: renders FancyIcon}
| {type: 'basic', node: renders BasicIcon};
component Layout(icon: IconProp) {
if (icon.type === 'fancy') {
icon.node as renders FancyIcon; // Works!
}
return <div>{icon.node}</div>;
} См. также
- Объединения — типы рендеринга взаимодействуют с типами объединений, обеспечивая гибкий рендеринг
- Подтипы — принцип работы подтипов типов рендеринга
© 2013–present Facebook Inc.
Licensed under the MIT License.
https://flow.org/en/docs/react/render-types