Корректно типизировать передаваемый в контейнер компонент

Сделайте ContainerFc generic-функцией, выводящей props из переданного Component. NoInfer не позволит другим входным props изменить этот выведенный контракт.

Короткий ответ

Что ответить на собеседовании

Сделайте ContainerFc generic-функцией, выводящей props из переданного Component. NoInfer не позволит другим входным props изменить этот выведенный контракт.

Подробный разбор

Ответ с пояснениями

Условие

При использовании компонента OtherFC в качестве значения свойства Component у ContainerFc TypeScript должен подсвечивать и предлагать остальные свойства, которые можно передать в ContainerFc помимо его собственных, с точным указанием их типов.

import { FC } from "react";

type OtherProps = {
  name: string;
  age: number;
};

const OtherFC: FC<OtherProps> = ({ age, name }) => {
  return (
    <div>
      {name} {age}
    </div>
  );
};

/** Начало блока, где разрешено менять и дополнять типы */
type IProps = {
  Component: any;
  height: number;
};

const ContainerFc: FC<IProps> = (props) => {
  const { Component, height } = props;

  return (
    <div style={{ height: `${height}px` }}>
      <Component {...props} />
    </div>
  );
};
/** Конец блока, где можно редактировать типы */

/** Здесь типовые ошибки не должны возникать */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const AnyComponent1 = () => {
  return <ContainerFc height={5} Component={OtherFC} name="Макс" age={30} />;
};

/** Здесь должны появляться ошибки типизации */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const AnyComponent2 = () => {
  return <ContainerFc height={5} Component={OtherFC} name={30} age="Макс" />;
};

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const AnyComponent3 = () => {
  return <ContainerFc height={5} Component={OtherFC} role="Макс" />;
};

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const AnyComponent4 = () => {
  return <ContainerFc height={5} Component={OtherFC} name="Макс" />;
};

Ответ

Сделайте ContainerFc generic-функцией, выводящей props из переданного Component. NoInfer не позволит другим входным props изменить этот выведенный контракт.

В TypeScript 5.4+:

type IProps<P extends object> = {
  Component: React.ComponentType<P>;
  height: number;
} & NoInfer<P>;

function ContainerFc<P extends object>(props: IProps<P>) {
  const { Component, height, ...rest } = props;
  return <div style={{ height: `${height}px` }}>
    <Component {...(rest as P)} />
  </div>;
}

Name/age выводятся из OtherFC, поэтому неверные типы, лишний role и отсутствие age отвергаются. Height и Component зарезервированы контейнером: решение предполагает отсутствие одноимённых обязательных props у вложенного компонента. Rest assertion здесь лишь обходит ограничение вывода при generic-деструктуризации.

Источник: www.typescriptlang.org.

ИИ-помощник для собеседований

Хочешь уверенно проходить собеседования?

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

Подробнее