Бесплатная серия видео на YouTube о лучших React / TypeScript практиках
Наше краткое руководство по браузеру уже позволяет вам разрабатывать 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"
).
React может отображать либо HTML-теги (строки) либо компоненты React. Итоговый код JavaScript для этих элементов отличается (React.createElement('div')
против React.createElement(MyComponent)
). Это определяется регистром первой буквы. foo
распознаётся как тег HTML, а Foo
как компонент.
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
или 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 определениях типов есть 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}/>;
Работает именно так, как ожидалось. Вот пример:
/** обобщённый компонент */
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;
Вы в основном инициализируете переменную как объединение 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'/>