Spec-Zone.ru › Flow

Типы рендеринга

Некоторые библиотеки компонентов или дизайн-системы могут ограничивать способы композиции компонентов. Например, 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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API