Надявам се, че тази статия споделя малко светлина за това как да изградите по-добри компоненти на React, като използвате TypeScript. Тази публикация е резултат от усилията за изграждане на taggr, фокусираната върху поверителността AI галерия.

Докато изграждах taggr, навлязох по-дълбоко в TypeScript и досега харесвам добавените възможности за анотиране на типове и прихващане на грешки по време на компилиране, вместо изключено по време на изпълнение.

Може да се почувствате обезсърчително и допълнителна работа да коментирате всеки компонент и функция в началото, но тъй като кодовата база нараства по размер и сложност, ползите започват да блестят.

Наличието на компонентите и кода на бизнес-логиката е правилно въведен, запазва уникален източник на истина за обектите на даден домейн, минимизирайки човешките грешки в приложните слоеве.

Освен това дефинициите на TypeScript могат да се генерират автоматично от OpenAPI, схеми на GraphQL… Пълна печалба 🎉

Когато изграждам React компоненти, се опитвам да поддържам техните API възможно най-строги и чисти. 🧹💨

Компонентите с ясни граници са лесни за повторна употреба, разширяване и като цяло приятни за работа.

Нека анализираме конкретен пример за това как можем да направим по-чисти компонентни API с помощта на TypeScript, нали?

Неизлагайте Prop типове от компонент😧

// paragraph.tsx
export type Props = {
  text: string; 
}
const Paragraph = ({text}: Props) => <p>{text}</p>
// title.tsx
// Title is now tightly coupled to paragraph.txs > Props
import {Props} from './paragraph' 
const Title = ({text}: Props) => <h1>{text}</h1>

Защо това е лошо?

  • Когато разкривате директно типовете Prop, нищо не спира другите разработчици (дори бъдещото ви аз 😂) да импортират и разширяват тези типове в други части на приложението. Това нарушава капсулирането на компонента и създава ненужни зависимости между компонентите.
  • Промените в типовете Prop на оригиналния компонент потенциално могат да повредят други части на приложението 💥
  • Претрупан API, модулът експортира компонента и типовете. Това може бързо да се превърне в съставни файлове, експортиращи множество типове, така че бъдете внимателни 🧐

По-добър начин ✅

Не излагайте директно компонентни типове Props. Не.

Ами ако искам достъп до Props на друг компонент, така че да не се налага да декларирам отново специфични за домейна типове?

Реквизитите на компонента дефинират интерфейса на компонента с останалата част от вашето приложение (или света 🌎).

Ако имате UserProfile компонент и Props декларира тип User, който искате да използвате някъде другаде в приложението си, той трябва да бъде извлечен от UserProfile.

Извлечете специфични за домейна типове в ./types, така че да могат да се използват повторно в приложението.

// user-types.ts
export interface User {
 name: string,
 age: number;
};
// user-profile.tsx
import {User} from './user-types';
type Props = {
 user: User,
 date: string, // other props
};
const UserProfile = ({user, date}: Props) => ...
export default UserProfile;
// user-list.tsx
import {User} from './user-types';
type Props = {
 users: User[],
};
const UserList = ({users}: Props) => ...
export default UserList;

Какво ще стане, ако трябва да получа достъп до свойствата на компонент от някъде другаде?

Те са основателни причини да искате да получите достъп до типовете на компонент, като например подобряване на компонент с HOC.

Нека проверим следващия раздел, за да разрешим това!

Търсене на тип опора ✨

Можем да използваме резолюцията на типа на TypeScript, за да активираме търсене на тип Prop.

  1. Настройте помощник за търсене на тип проп, `GetComponentProps`:
// utils.ts
export type GetComponentProps<T> = T extends
| React.ComponentType<infer P>
| React.Component<infer P>
? P
: never;

2. Дефинирайте компонента, който искаме да разширим, Title:

// title.tsx
type Color = "RED" | "BLUE" | "GREEN";
type Props = {
 title: string;
 color: Color;
};
const Title = ({ title, color }: Props) => (
  <h1 style={{ color }}>{title}</h1>
);
export default Title;

3. Разширете компонента Title, като запазите пълната безопасност на типа:

// title-wrapper.tsx
import Title from './title';
import {GetComponentProps} from './utils';
type Props = GetComponentProps<typeof Title> & {
 onClick: () => void;
};
const TitleWrapper = ({onClick, ...rest}: Props) => (
  <button onClick={onClick}>
    <Title {...rest} /> 
  </button>
);
export default TitleWrapper;
// index.ts
import TitleWrapper from 'title-wrapper';
// Full type safety and autocompletion! 🎉
const App = () =>  (
 <TitleWrapper
  title="Hello there"
  color="GREEN"
  onClick={() => window.alert("title pressed")} 
 />
);

Успяхме да получим достъп до свойствата на Title от TitleWrapper, без да ги излагаме ръчно и да нарушаваме капсулирането, страхотно! 🎉