React.FC - это тип, который поставляется вместе с типами пакета @types/react
для React. Он представляет собой тип функционального компонента, который является основным строительным блоком большинства современных приложений на React.
В то время как FC удобен в использовании и в некоторых случаях может быть полезным, есть несколько причин, почему его не следует использовать.
Давайте взглянем на определение типа FC на примере нескольких последних версий React.
// Тип FC - это сокращение от FunctionComponent
type FC<P = {}> = FunctionComponent<P>;
- React 16.x:
interface FunctionComponent<P = {}> {
(props: P & { children?: ReactNode }, context?: any): ReactElement<any, any> | null;
propTypes?: WeakValidationMap<P>;
contextTypes?: ValidationMap<any>;
defaultProps?: Partial<P>;
displayName?: string;
}
- React 17.x:
type PropsWithChildren<P> = P & { children?: ReactNode };
interface FunctionComponent<P = {}> {
(props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null;
propTypes?: WeakValidationMap<P>;
contextTypes?: ValidationMap<any>;
defaultProps?: Partial<P>;
displayName?: string;
}
- React 18.x (github):
interface FunctionComponent<P = {}> {
(props: P, context?: any): ReactNode;
propTypes?: WeakValidationMap<P> | undefined;
contextTypes?: ValidationMap<any> | undefined;
defaultProps?: Partial<P> | undefined;
displayName?: string | undefined;
}
Среди существенных отличий между версиями типа FC можно отметить:
-
props
теперь не имеет оберткиPropsWithChildren
(ранее наличие свойстваchildren
) -
Возвращаемое значение изменено с
ReactElement<any, any> | null
наReactNode
, что позволяет теперь возвращать простые типы без ошибки TypeScript (например,return 123
)
-
propTypes
иdefaultProps
определяют типы свойств на основе обобщённого типаP
. Эти функции обеспечивают валидацию типов для свойств, которые вы передаете компоненту, и значения свойств по умолчанию, которые вы можете определить с помощьюComponent.defaultProps = {}
. -
contextTypes
был частью старой системы контекста в React, которая использовалась для передачи данных глубоко вложенным компонентам без необходимости передавать промежуточные свойства через все промежуточные компоненты. Однако, начиная с React версии 16.3, рекомендуется использовать новый API контекста:React.createContext
,contextType
, иuseContext
. -
displayName
может пригодится при отладке кода.
До выпуска React 18, тип FC имел неявное свойство children
, что позволяло как передавать дочерние элементы так и оставлять компоненты без них. Это порой приводит к неочевидным ошибкам, так как однозначно непонятно должен ли компонент принимать children
или нет. 🤔
Пример с FC (до React 18):
interface MyComponentProps {
text: string;
}
const MyComponent: React.FC<MyComponentProps> = ({ text }) => <h1>Hello {text}</h1>;
// Применение компонента
<MyComponent text="World">
{"Здесь не будет ошибки TypeScript т.к. children неявно определен в компоненте через FC! ✅"}
</MyComponent>
Пример без FC:
interface MyComponentProps {
text: string;
}
const MyComponent = ({ text }: MyComponentProps): JSX.Element => <h1>Hello {text}</h1>;
// Применение компонента
// Ошибка: Property 'children' does not exist on type 'IntrinsicAttributes & MyComponentProps'.
<MyComponent text="World">
{"Здесь будет ошибка TypeScript, т.к. children не определен в компоненте! ⚠️"}
</MyComponent>
Пример без FC но с добавлением children:
interface MyComponentProps {
text: string;
children?: React.ReactNode;
}
const MyComponent = ({ text }: MyComponentProps): JSX.Element => (
<div>
<h1>Hello {text}</h1>
{children}
</div>
);
// Применение компонента
<MyComponent text="World">
{"Здесь не будет ошибки TypeScript т.к. children явно определен в компоненте! ✅"}
</MyComponent>
Если вы используете FC
, то свойство defaultProps
должно быть определено внутри самой функции компонента, что может вызвать проблемы при типизации и автодополнении в TypeScript.
FC
уже включает свойства children
и props
, а наружу ваши добавляемые свойства пытаются конфликтовать с этими внутренними свойствами. Также есть информация, что defaultProps
будет помечен как "устаревший" тип в будущих версиях React.
В примере ниже, TypeScript не будет корректно распознавать defaultProps
, и автодополнение не будет работать для text
:
interface MyComponentProps {
text: string;
}
const MyComponent: React.FC<MyComponentProps> = ({ text }) => <h1>Hello {text}</h1>;
MyComponent.defaultProps = {
text: "World",
};
// Применение компонента
// Ошибка: Property 'text' is missing in type '{}' but required in type 'MyComponentProps'
{/* Здесь будет ошибка TypeScript ⚠️ */}
<MyComponent />
Пример без FC:
interface MyComponentProps {
text: string;
}
const MyComponent = ({ text }: MyComponentProps): JSX.Element => <h1>Hello {text}</h1>;
MyComponent.defaultProps = {
text: "World",
};
{/* Здесь не будет ошибки TypeScript ✅ */}
<MyComponent />
Использование FC для создания вложенных компонентов может ухудшить читаемость и понимание кода. Также, при возникновении ошибок или предупреждений в компонентах, связанных с типами, выявление источника проблемы может стать сложной задачей из-за более сложных типовых зависимостей.
Пример компонента:
<Menu>
<Menu.Item>Text</Menu.Item>
</Menu>
Пример реализации с FC:
const Select: React.FC<SelectProps> & { Item: React.FC<ItemProps> } = (props) => { /* ... */ }
Select.Item = (props) => { /* ... */ }
Пример реализации без FC:
const Select = (props: SelectProps) => { /* ... */ };
Select.Item = (props: ItemProps) => { /* ... */ };
FC автоматически добавляет свойства, такие как propTypes
, contextTypes
и др., к вашему компоненту. Если вы не используете эти свойства, это может создать лишний шум в вашем коде.
Так как FC - это экспериментальный тип и не всегда документированный как часть официального API, его поведение или поддержка могут измениться в будущих версиях.
Если вы задались вопросом переноса проекта на новую версию React 18 и выше, а в вашем проекте активно используется FC, вам понадобится добавить пару новых определений и сделать несколько замен чтобы добиться обратной совместимости.
- Добавьте определения типов в файл
types.d.ts
- это создаст отдельные версии FC/VFC сPropsWithChildren
, тем самым, включаяchildren
в тип и делая его таким же каким он был в предыдущих версиях React.
// types.d.ts
import { FunctionComponent, PropsWithChildren } from 'react';
declare module 'react' {
type FC17<P = {}> = FunctionComponent<PropsWithChildren<P>>;
type VFC17<P = {}> = FunctionComponent<P>;
}
- Пройдитесь по всему коду и произведите замену
FC
наFC17
иVFC
наVFC17
. Пример компонента после замены:
import { FC17 } from 'react';
export const MyComponent: FC17 = ({ children }) => <div>{children}</div>;
Имейте ввиду, что данная манипуляция необходима только для переноса старых компонентов, не следует использовать это для нового кода. Используйте обновленную версию FC для новых компонентов.
В случае, если вам не нужно мигрировать компоненты и вы хотите просто убрать предупреждения TypeScript связанные с определением children
, добавьте файл react.d.ts
со следующим содержимым, которое вернет тип FC к версии React 17.
// react.d.ts
import { FunctionComponent, PropsWithChildren } from 'react';
declare module 'react' {
interface FunctionComponent<P = {}> {
(props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null;
}
}
Preact - это легковесная альтернатива библиотеки React, которая имеет обратную совместимость с экосистемой React. Вы можете сэкономить до 100 КБ в размере продакшн-версии и использовать независимую библиотеку.
Preact написан на TypeScript (с аннотациями JSDoc), поэтому вы получаете всю информацию о типах как и в React. Поскольку все типы из пакета @types/react
не совместимы с Preact, вам понадобится адаптировать ваш код таким образом, чтобы альтернативный тип принимал children
, например:
// Альтернатива React.FC для Preact
type WithChildren<T = {}> = T & { children?: VNode };
В некоторых проектах FC может быть полезным и не стоит его избегать при первой же возможности. Однако, если вы сталкиваетесь с проблемами в типизации, автодополнении или поддержке, рассмотрите возможность использования обычных функций или альтернативных типов или попробуйте перенести проект на React последней версии.
Дополнено: Facebook убрал тип React.FC из своего базового шаблона для проектов на TypeScript, так как это лишняя функциональность с минимальными преимуществами в сочетании с некоторыми недостатками.
Эта история оказалось полезной? 🤔
Поддержите меня чашечкой кофе и станьте спонсором нового контента!
Похожие публикации
Сравниваем скорость установки пакетов Yarn и NPM на примере нескольких проектов разного размера... Читать далее
Это были лихие 2000-е, мы играли в Lineage 2 как могли... Читать далее
История разработки, обновления и интересные факты об игре Counter-Strike... Читать далее