Перейти к содержанию

React JSX

Бесплатная серия видео на YouTube о лучших React / TypeScript практиках

PRO Egghead курс по TypeScript и React

Настройка

Наше краткое руководство по браузеру уже позволяет вам разрабатывать react приложения. Вот важные моменты:

  • Используйте файлы с расширением .tsx (вместо .ts).
  • Используйте "jsx": "react" в compilerOptions вашего конфига tsconfig.json.
  • Установите определения для JSX и React в свой проект: (npm i -D @types/react @types/react-dom).
  • Импортируйте react в ваши .tsx файлы (import * as React from "react").

HTML теги против Components

React может отображать либо HTML-теги (строки) либо компоненты React. Итоговый код JavaScript для этих элементов отличается (React.createElement('div') против React.createElement(MyComponent)). Это определяется регистром первой буквы. foo распознаётся как тег HTML, а Foo как компонент.

Проверка типа

HTML теги

HTML-тег foo должен иметь тип JSX.IntrinsicElements.foo. Эти типы уже определены для всех основных тегов в файле react-jsx.d.ts, который вы установили. Вот пример содержимого файла:

declare module JSX {
    interface IntrinsicElements {
        a: React.HTMLAttributes;
        abbr: React.HTMLAttributes;
        div: React.HTMLAttributes;
        span: React.HTMLAttributes;

        /// и так далее ...
    }
}

Функциональные компоненты

Вы можете определить функциональные компоненты просто с помощью React.FunctionComponent интерфейса, например:

type Props = {
    foo: string;
};
const MyComponent: React.FunctionComponent<Props> = (
    props
) => {
    return <span>{props.foo}</span>;
};

<MyComponent foo="bar" />;

Классовые компоненты

Компоненты проверяются по типу на основе свойства компонента props. Это формируется после того, как JSX преобразуется, т. е. атрибуты становятся свойствами компонента.

Файл react.d.ts определяет React.Component<Props,State> класс, который вы должны расширить в своем собственном классе, предоставляя свои собственные Props и State интерфейсы. Это показано ниже:

type Props = {
    foo: string;
};
class MyComponent extends React.Component<Props, {}> {
    render() {
        return <span>{this.props.foo}</span>;
    }
}

<MyComponent foo="bar" />;

React JSX совет: интерфейс для рендеринга

React может рендерить несколько вещей, например JSX или string. Все они объединены в тип React.ReactNode, поэтому используйте его, когда вы хотите принимать рендеримые объекты, например:

type Props = {
    header: React.ReactNode;
    body: React.ReactNode;
};
class MyComponent extends React.Component<Props, {}> {
    render() {
        return (
            <div>
                {this.props.header}
                {this.props.body}
            </div>
        );
    }
}

<MyComponent header={<h1>Header</h1>} body={<i>body</i>} />;

React JSX cовет: принять экземпляр Component

В react определениях типов есть React.ReactElement<T>, чтобы вы могли описывать результат создания экземпляра компонента класса <T/>. Например:

class MyAwesomeComponent extends React.Component {
    render() {
        return <div>Hello</div>;
    }
}

const foo: React.ReactElement<MyAwesomeComponent> = (
    <MyAwesomeComponent />
); // Okay
const bar: React.ReactElement<MyAwesomeComponent> = (
    <NotMyAwesomeComponent />
); // Ошибка!

Конечно, вы можете использовать это для описания параметра функции и даже как свойство компонента React.

React JSX совет: принять компонент, который может воздействовать на свойства и рендериться с помощью JSX

Тип React.Component<Props> объединяет React.ComponentClass<P> | React.StatelessComponent<P> так что вы можете принять что-то, что требует типы Props и отрендерить его с помощью JSX, например:

const X: React.Component<Props> = foo; // откуда-то

// Рендерим X с некоторыми свойствами:
<X {...props} />;

React JSX совет: обобщённые компоненты

Работает именно так, как ожидалось. Вот пример:

/** обобщённый компонент */
type SelectProps<T> = { items: T[] };
class Select<T> extends React.Component<
    SelectProps<T>,
    any
> {}

/** Использование */
const Form = () => <Select<string> items={['a', 'b']} />;

Обобщённые функции

Что-то вроде следующего отлично работает:

function foo<T>(x: T): T {
    return x;
}

Однако использование обобщённой стрелочной функции приведет к:

const foo = <T>(x: T) => x; // ОШИБКА : незакрытый тег `T`

Решение: используйте extends чтобы сказать компилятору, что это обобщённый параметр, например:

const foo = <T extends unknown>(x: T) => x;

React JSX совет: строго типизированные refs

Вы в основном инициализируете переменную как объединение ref и null, а затем инициализируете ее как колбэк, например:

class Example extends React.Component {
    example() {
        // ... что-то
    }

    render() {
        return <div>Foo</div>;
    }
}

class Use {
    exampleRef: Example | null = null;

    render() {
        return (
            <Example
                ref={(exampleRef) =>
                    (this.exampleRef = exampleRef)
                }
            />
        );
    }
}

И то же самое с ref для собственных элементов, например.

class FocusingInput extends React.Component<
    { value: string; onChange: (value: string) => any },
    {}
> {
    input: HTMLInputElement | null = null;

    render() {
        return (
            <input
                ref={(input) => (this.input = input)}
                value={this.props.value}
                onChange={(e) => {
                    this.props.onChange(e.target.value);
                }}
            />
        );
    }
    focus() {
        if (this.input != null) {
            this.input.focus();
        }
    }
}

Утверждения типа

Используйте синтаксис as Foo для утверждений типов, как мы упоминали ранее.

Свойства по умолчанию

  • Stateful компоненты со свойствами по умолчанию: Вы можете сообщить TypeScript, что свойство будет предоставлено извне (посредством React), используя оператор null assertion (это не идеальный вариант, но это простейшее решение с минимальным дополнительным кодом, которое я смог придумать).
class Hello extends React.Component<{
    /**
     * @default 'TypeScript'
     */
    compiler?: string;
    framework: string;
}> {
    static defaultProps = {
        compiler: 'TypeScript',
    };
    render() {
        const compiler = this.props.compiler!;
        return (
            <div>
                <div>{compiler}</div>
                <div>{this.props.framework}</div>
            </div>
        );
    }
}

ReactDOM.render(
    <Hello framework="React" />, // TypeScript React
    document.getElementById('root')
);
  • Функциональные stateless компоненты со свойствами по умолчанию: рекомендуется использовать простые шаблоны JavaScript, поскольку они хорошо работают с системой типов TypeScript, например:
const Hello: React.SFC<{
    /**
     * @default 'TypeScript'
     */
    compiler?: string;
    framework: string;
}> = ({
    compiler = 'TypeScript', // Свойство по умолчанию
    framework,
}) => {
    return (
        <div>
            <div>{compiler}</div>
            <div>{framework}</div>
        </div>
    );
};

ReactDOM.render(
    <Hello framework="React" />, // TypeScript React
    document.getElementById('root')
);

Объявление веб-компонента

Если вы используете веб-компонент, определения типа React по умолчанию (@types/react) не будут знать об этом. Но вы можете легко сказать об этом, например чтобы объявить веб-компонент под названием my-awesome-slider, который принимает Props MyAwesomeSliderProps, вы должны:

declare global {
    namespace JSX {
        interface IntrinsicElements {
            'my-awesome-slider': MyAwesomeSliderProps;
        }

        interface MyAwesomeSliderProps
            extends React.Attributes {
            name: string;
        }
    }
}

Теперь вы можете использовать его в TSX:

<my-awesome-slider name="amazing" />