В 2016 году TypeScript начал брать новые высоты. Разработчики принялись полностью переписывать на него многие популярные технологии и добавлять на существующие платформы поддержку статического анализа. Такой глобальный процесс добавил больше стабильности в кодовую базу тысяч, а то и десятков тысяч проектов.
Почему React? По состоянию на сегодняшний день эта библиотека бесспорно доминирует на фоне конкурентов. Вокруг React образовалось самое большое сообщество разработчиков в мире. Каждый третий SPA написан на данной платформе. Также есть множество отличных проектов, связанных с использованием React Native, платформы для iOS, UWP и Android приложений, основанной на React.js.
Поэтому сегодня мы взглянем на возможности, которые дает интеграция двух суперпопулярных инструментов: TypeScript и React.
Для начала разберемся, какие типы мы можем использовать для React.
Начнем с простого и добавим типы в Functional Component.
Для Functional Component или Statless Component мы должны использовать определение типа React.FunctionComponent. Так же мы можем определить типы для аргумента props — полей, которые компоненту передает родитель. В данном случае props может содержать только поле name с типом string.
Все это выглядит не сложно. А что насчет компонентов классов?
В примере с классом мы создали два интерфейса: Props и State. С их помощью мы определили сигнатуры входящих пропсов (пустые) и сигнатуру состояния компонента — как в примере с Functional Components.
Так же мы можем добавить значения пропсов по умолчанию.
Вот и все! Наше маленькое React приложение уже строго типизировано на уровне параметров и значений состояния компонента.
Давайте разберем примущества, которые это нам дало:
Enum — это перечисляемый тип данных. Если мы добавим этот тип к переменной или полю интерфейса, то значением этого поля или переменной могут быть только определенные значения в Enum.
Например.
В уже знакомом нам Functional Component мы хотим показать выбранный пользователем цвет. В типе enum Colors мы указали все возможные варианты цвета, которые могут передаваться в компонент. Если компилятор TypeScript увидит где то несоответствие по типам, он покажет вам это, выдав ошибку.
В 2019 мы все еще имеем много приложений, работающих на Redux. TypeScript может помочь в данной ситуации.
В данном примере мы добавляем типы в приложение сразу на несколько уровней. В первую очередь, это сами редьюсеры. На вход редьюсер принимает Action, а возвращать он должен всегда объект соответствующий типу HelloWorldStateProps. Учитывая какое количество редьюсеров бывает в современном приложении, это очень полезное нововведение. Так же каждый action у нас имеет строгую сигнатуру Action.
Следующий уровень типизации — компонент. Здесь мы применили наследование типов для AppProps и AppState. Зачем писать больше, когда у нас уже есть типы данных с такими сигнатурами? Так и поддерживать систему проще. Если вы поменяете некоторые элементы, изменения произойдут по всем наследникам.
TypeScript — действительно полезный язык, работающий поверх JavaScript. В связке с React он дает действительно впечатляющие практики программирования Frontend приложений.
Почему React? По состоянию на сегодняшний день эта библиотека бесспорно доминирует на фоне конкурентов. Вокруг React образовалось самое большое сообщество разработчиков в мире. Каждый третий SPA написан на данной платформе. Также есть множество отличных проектов, связанных с использованием React Native, платформы для iOS, UWP и Android приложений, основанной на React.js.
Поэтому сегодня мы взглянем на возможности, которые дает интеграция двух суперпопулярных инструментов: TypeScript и React.
Примеры
Для начала разберемся, какие типы мы можем использовать для React.
Начнем с простого и добавим типы в Functional Component.
import * as React from 'react';
const HelloWorld: React.FunctionComponent<{
name: string;
}> = ({ name = 'World' }) => {
return <div>Hello, {props.name}</div>;
};
export default HelloWorld;
Для Functional Component или Statless Component мы должны использовать определение типа React.FunctionComponent. Так же мы можем определить типы для аргумента props — полей, которые компоненту передает родитель. В данном случае props может содержать только поле name с типом string.
Все это выглядит не сложно. А что насчет компонентов классов?
import * as React from 'react';
interface State {
name: string;
}
interface Props {}
class HelloWorld extends React.Component<Props, State> {
state = {
name: 'World'
}
setName(name: string) {
this.setState({ name });
}
redner() {
return (
<React.Fragment>
<hI>Hello, {this.state.name}</hI>
<input value={this.state.name} onChange={(e) => this.setName(e.target.value)} />
</React.Fragment>
);
}
}
В примере с классом мы создали два интерфейса: Props и State. С их помощью мы определили сигнатуры входящих пропсов (пустые) и сигнатуру состояния компонента — как в примере с Functional Components.
Так же мы можем добавить значения пропсов по умолчанию.
import * as React from 'react';
interface Props {
name?: string;
}
export default class HelloWorld extends React.Component<Props> {
static defaultProps: Props = {
name: 'World'
};
render () {
return <hI>Hello, {this.props.name}</hI>;
}
}
Вот и все! Наше маленькое React приложение уже строго типизировано на уровне параметров и значений состояния компонента.
Давайте разберем примущества, которые это нам дало:
- на этапе компиляции мы увидим все несоответствия типов;
- правильно настроенный редактор поможет нам избежать ошибок еще на этапе разработки, просто подсвечивая несхождения сигнатур или типов данных;
- документация из интерфейсов и определений типов.
Enum в параметрах
Enum — это перечисляемый тип данных. Если мы добавим этот тип к переменной или полю интерфейса, то значением этого поля или переменной могут быть только определенные значения в Enum.
Например.
import * as React from 'react';
enum Colors {
RED,
BLUE,
GREEN
}
const ColorResult: React.FunctionComponent<{
color: Colors;
}> = ({ color = Colors.Red }) => {
return <div>Your color is {props.color}</div>;
};
export default ColorResult;
В уже знакомом нам Functional Component мы хотим показать выбранный пользователем цвет. В типе enum Colors мы указали все возможные варианты цвета, которые могут передаваться в компонент. Если компилятор TypeScript увидит где то несоответствие по типам, он покажет вам это, выдав ошибку.
Строгий Redux
В 2019 мы все еще имеем много приложений, работающих на Redux. TypeScript может помочь в данной ситуации.
import * as React from 'react';
const initialState = { name: 'World' };
type HelloWorldStateProps = Readonly<typeof initialState>;
interface Action {
type: string;
name?: string;
}
const worldNameReducer = (
state: HelloWorldStateProps = initialState,
action: Action
): HelloWorldStateProps => {
switch (action.type) {
case "SET":
return { name: action.name };
case "CLEAR":
return { name: initialState.name };
default:
return state;
}
};
const set = (name): Action => ({ type: "SET", name });
const clear = (): Action => ({ type: "CLEAR" });
const store = createStore(
combineReducers({
world: worldNameReducer
})
);
type StateProps = ReturnType<typeof mapStateToProps>;
type DispatchProps = typeof mapDispatchToProps;
interface AppProps extends StateProps, DispatchProps {}
interface AppState extends StateProps {}
class App extends React.Component<AppProps, AppState> {
state = {
name: initialState.name
}
setName(name: string) {
this.setState({ name });
}
render() {
const { set, clear, name } = this.props;
return (
<div>
<hI>Hello, {name}</hI>
<input value={this.state.name} onChange={(e) => this.setName(e.target.value)} />
<button onClick={() => set(this.state.name)}>Save Name</button>
<button onClick={() => clear()}>Clear</button>
</div>
);
}
}
const mapStateToProps = ({ world }: { world: HelloWorldStateProps }) => ({
name: world.name,
});
const mapDispatchToProps = { set, clear };
const AppContainer = connect(
mapStateToProps,
mapDispatchToProps
)(App);
render(
<Provider store={store}>
<AppContainer />
</Provider>,
document.getElementById("root")
);
В данном примере мы добавляем типы в приложение сразу на несколько уровней. В первую очередь, это сами редьюсеры. На вход редьюсер принимает Action, а возвращать он должен всегда объект соответствующий типу HelloWorldStateProps. Учитывая какое количество редьюсеров бывает в современном приложении, это очень полезное нововведение. Так же каждый action у нас имеет строгую сигнатуру Action.
Следующий уровень типизации — компонент. Здесь мы применили наследование типов для AppProps и AppState. Зачем писать больше, когда у нас уже есть типы данных с такими сигнатурами? Так и поддерживать систему проще. Если вы поменяете некоторые элементы, изменения произойдут по всем наследникам.
Заключение
TypeScript — действительно полезный язык, работающий поверх JavaScript. В связке с React он дает действительно впечатляющие практики программирования Frontend приложений.
Комментарии (2)
serf
30.08.2019 15:08+1interface Action {
Для нормальной строгости все возможные экшены с их пайлоадом должны быть предетерминированы, а здесь сырые типы.
type: string;
name?: string;
}
MOZGoEZIK
Я прошу прощения, но кажется, что даже в простом примере не стоит писать вызов стрелочных функций в методе рендеринга. Потому что использование стрелочной функции в методе render создает новую функцию каждый раз при рендеринге компонента, что может влиять на производительность. Ну, типо это признак плохого тона что ли. За статью спасибо, легка к прочтению и усвоению