Как я мигрировал проект с Angular 1 на React
- пятница, 1 июня 2018 г. в 00:18:46
Всем привет!
Хочу поделиться своим опытом и инструментами, которые я использовал для миграции проекта с Angular 1 на React.
TLTR: Я написал модуль, с помощью которого можно трасформировать Angular компоненты (контроллер + шаблон) в React компоненты.
В данной статье я не буду доказывать почему и какой фреймворк лучше. Да, кроме React есть Vue, уже вышел Angular 6, а еще Ember, Svelte и многие другие… В общем, хочу рассказать, как я решал поставленную задачу, надеюсь мой опыт и наработки кому-то пригодятся.
У каждого могут быть свои причины перехода на другой фреймворк/библиотеку. В моей компании основной проект был написан когда React еще пешком под стол ходил довольно давно, для этого был выбран Angular 1.x. Иногда он приносил боль (дайджест цикл, магия с вотчерами и ангуляровскими промисами), но в целом дело свое делал.
Во всех новых смежных проектах, в том числе и в мобильной версии основного проекта, используется связка Redux + React + Typescript + CSS Modules. В итоге появилась своя библиотека компонентов, стилей, все проекты строго стандартизированы, разработка новых компонентов и подпроектов ускорилась в разы.
Основной проект продолжал жить параллельно на Angular и требовал на поддержку всё больше и больше времени, потому что нельзя просто так взять и использовать готовый код приходилось решать заново уже решённые задачи, писать с нуля компоненты. Тем более на горизонте появилась перспектива объединить основную и мобильную версии проекта в один проект с адаптивной вёрсткой.
Да, есть ngReact, но превратить проект в этакого монстра Франкенштейна не было особого желания. Поэтому было принятно решение перенести проект на React для упрощения его развития и поддержки.
Основной проект
Смежные проекты и мобильная версия
Отмечу, что большая часть всей бизнес-логики (валидаторы, отправка запросов, утилиты и т.д.) была реализована отдельно на Typescript в виде NPM-модулей, что позволяет легко переиспользовать код между проектами независимо от фреймворка.
"Я люблю рутину и рефакторинг!" — ни один разработчик на свете
Я думаю, многие согласятся, что рефакторинг это не самое интересное занятие. Поэтому я решил частично автоматизировать этот процесс.
Даже поверхностно сравнив компоненты React и Angular, можно вывести (конечно сильно упрощённую) формулу:
React.Component = Angular Controller + Angular Directive + Angular Template;
Так получился ng2react-builder
Вы, наверное, уже поняли, что я мастер давать названия модулям. Ну ладно, не об этом...
Что умеет модуль
Лучше всего посмотреть пример из документации, а еще лучше примеры компонентов в тест-кейсах.
Мы можем скормить модулю наш шаблон и контроллер (директивы пока в пролёте), и на выходе получится собранный React компонент (React.PureComponent или React.Component) с JSX разметкой.
Без контроллера можем легко собрать простой компонент без состояния (React.StatelessComponent).
ng2react-builder Пытается преобразовать все Angular-выражения в валидные JS/JSX конструкции:
ng-repeat
мы получим нативный JS'ный .map()
с JSX выводом<!-- было -->
<div>
<span ng-repeat="item in list as | limitTo:5 as results">{{item.name}}</span>
</div>
<!-- стало -->
<div>
{results.slice(0, 5).map((item, index) => {
return <span key={`child-${ index }`}>{item.name}</span>
})}
</div>
{{expression}}
будет преобразован в {expression}
<!-- было -->
<a ng-click="$event.preventDefault(); selectItem(item)">{{item.name}}</a>
<my-icon="calendar"><my-icon/>
// часть настроек для ng2rect-builder'а
directivesToTags: {
'my-icon': {
tagName: 'MyReactIcon',
valueProp: 'type'
}
}
<!-- стало -->
<a onClick={(event) => {
event.preventDefault();
selectItem(item);
}}>
{item.name}
</a>
<MyReactIcon type="calendar"/>
Ещё особо полезной вещью может стать возможность преобразования директив в вызов JS функции (сейчас поддерживаются только директивы, заданные как атрибут):
<!-- было -->
<span my-directive="some.value"></span>
// часть настроек для ng2rect-builder'а
directivesToTextNodes: {
myDirective: {
callee: 'myFunc',
calleeArguments: ['arg1']
}
}
<!-- стало -->
<span>{myFunc(arg1, 'some.value')}</span>
Как это работает
render
с полученным JSX шаблономПочему Typescript Compiler API
Увы. Как я не пытался.
Как я написал выше, придётся брать напильник и продолжать рефакторинг вручную, но этот модуль сэкономил мне огромную часть времени на рутинных задачах, особенно в переводе Angular шаблонов на JSX.
Надеюсь, вам понравилось, и я помог кому-то сэкономить время в процессе рефакторинга ;)