Статическая типизация в React приложении

от автора

В 2016 году TypeScript начал брать новые высоты. Разработчики принялись полностью переписывать на него многие популярные технологии и добавлять на существующие платформы поддержку статического анализа. Такой глобальный процесс добавил больше стабильности в кодовую базу тысяч, а то и десятков тысяч проектов.

Почему 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 приложений.


ссылка на оригинал статьи https://habr.com/ru/post/465461/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *