Type guards

Type guards — это специальные проверки типов, позволяющие точно определить тип значения во время выполнения. В контексте JavaScript и MDX это особенно важно, так как MDX объединяет Markdown и JSX, где данные могут иметь динамический и разнородный характер. Type guards позволяют безопасно работать с такими данными, избегая ошибок типов и некорректного рендеринга компонентов.


Базовые принципы Type Guards

Type guard — это функция или конструкция, которая сужает тип переменной. С помощью type guards можно гарантировать, что определённая ветка кода будет работать только с конкретным типом.

Пример с typeof

function formatValue(value: string | number) {
  if (typeof value === 'string') {
    return value.toUpperCase();
  } else {
    return value.toFixed(2);
  }
}

Ключевой момент: typeof позволяет сужать примитивные типы (string, number, boolean, symbol, undefined).

Пример с instanceof

class Article {
  constructor(public title: string) {}
}

class Video {
  constructor(public duration: number) {}
}

function renderContent(content: Article | Video) {
  if (content instanceof Article) {
    return `<h1>${content.title}</h1>`;
  } else {
    return `<p>Duration: ${content.duration} minutes</p>`;
  }
}

Особенность: instanceof проверяет принадлежность объекта к определённому классу и подходит для сложных объектов.


Пользовательские Type Guards

TypeScript позволяет создавать собственные функции-проверки с возвращаемым типом value is Type, что делает их полноценными type guards.

interface Image {
  url: string;
  alt: string;
}

interface Text {
  content: string;
}

function isImage(node: Image | Text): node is Image {
  return (node as Image).url !== undefined;
}

function renderNode(node: Image | Text) {
  if (isImage(node)) {
    return `<img src="${node.url}" alt="${node.alt}" />`;
  } else {
    return `<p>${node.content}</p>`;
  }
}

Преимущество: Пользовательский type guard можно использовать для любых сложных структур данных, включая MDX-узлы и компоненты.


Type Guards в MDX

MDX позволяет вставлять JSX в Markdown. При работе с MDX необходимо часто проверять типы узлов, особенно при динамическом рендеринге компонентов или при трансформации контента.

Пример работы с AST MDX

import { Node } from 'unist';
import { visit } from 'unist-util-visit';

interface HeadingNode extends Node {
  type: 'heading';
  depth: number;
  children: Node[];
}

function isHeading(node: Node): node is HeadingNode {
  return node.type === 'heading';
}

function collectHeadings(tree: Node) {
  const headings: HeadingNode[] = [];
  
  visit(tree, (node) => {
    if (isHeading(node)) {
      headings.push(node);
    }
  });

  return headings;
}

Суть: С помощью type guard isHeading можно безопасно работать с узлами AST MDX, не боясь, что доступ к depth или children вызовет ошибку.


Комбинированные проверки

Type guards можно комбинировать для более сложных условий, что часто встречается при обработке динамических компонентов в MDX.

interface ButtonProps {
  label: string;
  onClick?: () => void;
}

interface LinkProps {
  href: string;
  text: string;
}

type ComponentProps = ButtonProps | LinkProps;

function isButton(props: ComponentProps): props is ButtonProps {
  return (props as ButtonProps).label !== undefined;
}

function renderComponent(props: ComponentProps) {
  if (isButton(props)) {
    return `<button>${props.label}</button>`;
  } else {
    return `<a href="${props.href}">${props.text}</a>`;
  }
}

Преимущество: Позволяет безопасно рендерить компоненты с разной структурой данных без runtime ошибок.


Type Guards и условный рендеринг в MDX

MDX-компоненты часто получают props динамически, поэтому type guards полезны для условного рендеринга внутри JSX:

import { ComponentProps } from './types';

export function DynamicRenderer(props: ComponentProps) {
  if ('label' in props) {
    return <button>{props.label}</button>;
  }
  return <a href={props.href}>{props.text}</a>;
}

Важно: Проверка через in является упрощённой формой type guard, которая хорошо подходит для небольших интерфейсов.


Резюме по Type Guards

  • Позволяют безопасно сужать типы данных в JavaScript и TypeScript.
  • Ключевые способы: typeof, instanceof, in, пользовательские функции value is Type.
  • Особенно полезны при работе с MDX AST, динамическими компонентами и пропсами.
  • Позволяют избегать ошибок доступа к свойствам и обеспечивают корректный рендеринг контента.

Type guards — фундаментальный инструмент для безопасной работы с динамическими структурами данных, повышающий стабильность и предсказуемость MDX-приложений.