从 Angular 移植到 React,代码量减少了 20%
【CSDN 编者按】从 Angular 移植到 React,代码量减少了 20%?这是怎么回事呢,让我们跟着作者来看看吧!译者 | 弯月责编 | 宋彤彤出品 | CSDN(ID:CSDNnews)我开发了一款叫 Anita 的产品,当初选择 Angular 是因为之前我曾用它开发过表单生成器。然而,我拥有多年的 React 开发经验,很快我就意识到 Angular 的开发速度非常慢。于是,我决定将
【CSDN 编者按】从 Angular 移植到 React,代码量减少了 20%?这是怎么回事呢,让我们跟着作者来看看吧!
译者 | 弯月 责编 | 宋彤彤
出品 | CSDN(ID:CSDNnews)
我开发了一款叫 Anita 的产品,当初选择 Angular 是因为之前我曾用它开发过表单生成器。然而,我拥有多年的 React 开发经验,很快我就意识到 Angular 的开发速度非常慢。于是,我决定将代码从 Angular 移植到 React。
整个项目耗时不到 10 天,而且我是在业余时间内完成的。我认为这些付出非常值得,因为现在虽然代码库的功能完全相同,但代码量减少了 20%。
下面,我们来看一些 VS Code Counter 生成的统计数据。
Angular 代码库的统计结果如下:
不算 SCSS,Angular 应用程序的总代码行数(包括模板)为 5,999。我将模板算作代码,因为 Angular 遵循 MVVM(模型、视图、视图控制器)模式,这意味着模板会被编译成 JavaScript 代码。简单介绍一下,在 Angular 中,模板是具有特殊语法的 HTML 块,它们可以支持某些 Angular 功能,例如 for 循环。
React 代码库的统计结果如下:
因此,React 应用程序的总代码行数(包括模板)为 4,916。
我们来计算一下:
-
Angular 总代码行数:5,999
-
React 总代码行数:4,916
-
差异:-1,083 (-19.8443%)
这是一个巨大的提升。而实际上,Angular 的应用中使用了一个名为 Nebular 的 UI 库,而在 React 中整个 UI 都是使用 TailwindCSS 定制的,也就是说在 React 中,很多 DOM 元素的目的都是渲染 Angular 中只需一行导入而生成的元素,如果将这个因素也考虑在内,则代码量的减少就会更加显著。
虽然每个人的统计结果会有所不同,但通过上述结果,我们可以看出代码量的减少非常显著,因为这两个代码库都是由我一个人开发的,所以设计选择和风格非常相似。我相信这个结果很好地表现出了 Angular 和 React 之间的差异。Angular 代码库更大,也更复杂;而 React 代码库则更小,也更简单。
准备工作
为了将 Angular 应用转变成全新的 React 应用,最快捷、最简单的方式就是使用 create-react-app。
由于我们希望保留 Angular 应用的离线功能,而且想使用 TypeScript,因此创建 React 应用的命令为:
npx create-react-app anita-react--template cra-template-pwa-typescript
下面,我们运行 yarn start 来检查一下结果:
create-react-app 唯一的缺点是会添加很多新项目的代码。我们来删除没用的代码。
我们不需要 App 组件及其相关文件,另外 index.tsx 也可以清理掉:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import * as serviceWorkerRegistration from'./serviceWorkerRegistration';
ReactDOM.render(
<React.StrictMode>
"Hello World"
</React.StrictMode>,
document.getElementById('root')
);
serviceWorkerRegistration.register();
删除如下文件:
-
App.csss
-
App.test.tsx
-
App.tsx
在 Angular 项目中,所有静态文件通常都在 app/src/assets 文件夹中。而在 React 中,assets 文件夹通常放在 public 文件夹中。所以,我们来移动一下 assets 文件夹。assets 文件夹保存在 src 中有点不太合理,因为它不包含源文件,而是提供给用户的静态文件,所以 React 更合理。
下面,我们来更新 public 文件夹中的 index.html 和 manifest.json,添加应用图标、标题和描述。我们还可以删除 create-react-app 生成的一些初始文件
-
public/favicon.ico
-
public/logo192.png
-
public/logo512.png
下面,我们来看看代码库。
对路径导入
在 Angular 项目中,我们使用绝对路径来简化导入,并使用了自定义前缀 @anita/client。
例如,我们使用这样的语句:
import { MyModule } from'@anita/client/my.module
而不是:
import { AppModule } from'../../../my.module'
为此,我们需要正确配置 tsconfig.json 中的 paths。
"CompilerOptions": {
...
"paths": {
"@anita/client/*": [
"src/app/*"
]
}
...
}
在开发 Angular 应用时,这种导入方法的优点是可以避免大量杂乱无章的导入。随着代码库规模的增加,相对导入的路径会变得很长。举个例子,…/…/…/…/…/my.module.ts 就是一个过长的导航路径。相较而言,@anita/client/my.module 则更加简短,而且方便阅读。
此外,无论文件位于项目树结构的何处,它们的绝对路径都是一样的,因为绝对路径是相对于代码库的根目录而言的。
在将所有代码移植到 React 的过程中,这种导入策略非常方便,因为我们可以将所有代码转移到新项目中,然后使用 replace all 一次性更新所有导入。
因此,React 应用也必须使用绝对路径。
在 React 中,我们不能在 tsconfig.json 中使用 paths。然而,我们可以使用baseUrl,它允许我们根据绝对路径(相对于根目录而言)导入文件。我们的这个项目选择了 src 文件夹:
"CompilerOptions": {
...
"baseUrl": "src"
...
}
有了这个配置,我们就可以根据绝对路径导入文件了。所以,./src/app/app.component 就变成了 app/app.component。
这样,在移植代码时,我们可以简单地将所有 @anita/client 替换为 app,只要保持文件夹层次结构不变,所有导入都会顺利完成。
下面,我们来看看项目结构。
项目结构
为了简化从 Angular 到 React 的转换,我们保留了类似的文件夹层次结构。这样,我们就可以将所有代码直接放入 React 项目中了。
Angular 应用的结构如下:
-
src/app/
data:包含数据模型。
libs:包含非特定于 Angular 的库。
ng-services:包含 Angular 服务。
ui:包含 Angular UI 元素,以及 UI 所需的一些 Angular 服务和流水线。
React 的结构则更简单:
-
src/app/
anita-routes:包含应用的路由。因为这些不再是 Angular 的服务,所以我们可以将它们放在这个文件夹内。
data:包含数据模型,与 Angular 应用相同。
libs:包含 Angular 应用中使用的库,可以直接移植到 React,或者做少量修改。
ui-react-components:包含 React 组件。
设置UI:从 Nebular 到 TailwindCSS
在构建 Angular 的 UI 时,我使用了 Nebular(一个可定制的 Angular UI 库)。Nebular 是一个很棒的工具,但仅适用于 Angular,在 React 中使用就有点大材小用了。
我相信对于我的这个项目,TailwindCSS 是更好的选择。这是一个优秀的 CSS 框架,其中包含 flex、pt-4、text-center 和 rotate-90 等类,可以直接在 HTML 中组合使用。
在 React 中安装 TailwindCSS 也非常简单,具体的说明请参见官方文档(https://tailwindcss.com/docs/guides/create-react-app)。
我的应用的网站首页使用了 TailwindCSS,因此可以在整个应用中采用相同的风格。我们需要做的是在 tailwind.config.js 中定义项目使用的几种颜色:
const colors =require('tailwindcss/colors')
module.exports = {
// Purges the final stylesheet of unused/un-optimized selectors, keepingonly what is used in the app.
purge: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'],
darkMode: false,
theme: {
colors: {
transparent: 'transparent',
current: 'currentColor',
black: colors.black,
white: colors.white,
rose: colors.rose,
pink: colors.pink,
fuchsia: colors.fuchsia,
purple: colors.purple,
violet: colors.violet,
indigo: colors.indigo,
blue: colors.blue,
'prussian-blue': {
DEFAULT: '#002346',
'50': '#2D96FF',
'100': '#1389FF',
'200': '#006FDF',
'300': '#0056AC',
'400': '#003C79',
'500': '#002346',
'600': '#002346',
'700': '#000f20',
'800': '#000e1f',
'900': '#000d1b'
},
sky: colors.sky,
cyan: colors.cyan,
teal: colors.teal,
emerald: colors.emerald,
green: colors.green,
lime: colors.lime,
yellow: colors.yellow,
amber: colors.amber,
orange: colors.orange,
red: colors.red,
warmGray: colors.warmGray,
trueGray: colors.trueGray,
gray: colors.gray,
coolGray: colors.coolGray,
blueGray: colors.blueGray
}
},
variants: {
extend: {},
},
plugins: [
require('@tailwindcss/forms')
],
}
下面,我们用 TailwindCSS 创建一些布局。由于我的应用的主界面是一个管理面板,所以我们就从这里着手。
让我们在 index.tsx 中实现一个非常基本的管理面板:
ReactDOM.render(
<React.StrictMode>
<div className="bg-prussian-blue-400 text-gray-100 flexjustify-between">
<div className="flex-grow relative flex items-center lg:w-autolg:static pl-5">
<a className="text-lg font-bold leading-relaxed inline-blockmr-4 py-2 whitespace-no-wrap uppercase text-white" href="/">
<img src="/assets/logo/logo_square.svg" style={{ height:'30px', width: 'auto' }} alt="Anita" />
</a>
<a className="text-md font-bold leading-relaxed inline-blockmr-4 py-2 whitespace-no-wrap uppercase text-white" href="/">
Anita
</a>
</div>
<button className="mobile-menu-button p-4 focus:outline-nonefocus:bg-gray-700">
<svg className="h-5 w-5"xmlns="http://www.w3.org/2000/svg" fill="none"viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round"strokeWidth="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
</div>
<div className="relative min-h-screen md:flex">
<div className="sidebar bg-gray-200 text-prussian-blue-500 w-64space-y-6 py-7 px-2 absolute inset-y-0 left-0 transform -translate-x-fullmd:relative md:translate-x-0 transition duration-200 ease-in-out">
<nav>
<a href="https://anita-app.com" className="blockpy-2.5 px-4 rounded transition duration-200 hover:bg-prussian-blue-600hover:text-white">
Menu item 1
</a>
<a href="https://anita-app.com" className="blockpy-2.5 px-4 rounded transition duration-200 hover:bg-prussian-blue-600hover:text-white">
Menu item 2
</a>
<ahref="https://anita-app.com" className="block py-2.5 px-4rounded transition duration-200 hover:bg-prussian-blue-600hover:text-white">
Menu item 3
</a>
<a href="https://anita-app.com" className="blockpy-2.5 px-4 rounded transition duration-200 hover:bg-prussian-blue-600hover:text-white">
Menu item 4
</a>
</nav>
</div>
<div className="flex-1 p-10">
Content
</div>
</div>
</React.StrictMode>,
document.getElementById('root')
);
下面,我们来测试一下 TailwindCSS,以及页面布局:
很好。下面我们来添加一些组件。
我们可以将页面划分成三个区域:
-
Header
-
Sidebar
-
Content
每个区域都是一个 React 组件,并且可以导入到容器组件 AdminLayout 中:
export const AdminLayout = () => (
<div>
<Header />
<div className="relative admin-container md:flex">
<Sidebar>
<nav>
<a href="https://anita-app.com" className="blockpy-2.5 px-4 rounded transition duration-200 hover:bg-prussian-blue-600hover:text-white">
Menu item 1
</a>
<a href="https://anita-app.com" className="blockpy-2.5 px-4 rounded transition duration-200 hover:bg-prussian-blue-600hover:text-white">
Menu item 2
</a>
<a href="https://anita-app.com" className="blockpy-2.5 px-4 rounded transition duration-200 hover:bg-prussian-blue-600 hover:text-white">
Menu item 3
</a>
<a href="https://anita-app.com" className="blockpy-2.5 px-4 rounded transition duration-200 hover:bg-prussian-blue-600hover:text-white">
Menu item 4
</a>
</nav>
</Sidebar>
<Content>
Hello world!<br />
</Content>
</div>
</div>
);
占位文本暂时保留,稍后再添加实际的内容。稍后我们还将实现侧面工具栏的开闭功能。下面,我们来处理状态管理。
将状态管理从 NgRx 移植到 Redux
在 Angular 应用中,我使用了 NgRx,这个库是受 Redux 的启发而为 Angular 应用开发的反应式状态管理。我决定使用 NgRx 也是因为它与 Redux 非常相似。
因此,将状态管理的代码从 Angular 移植到 React 非常方便,我们只需将调用 NgRx store 的代码改成调用 Redux 即可。但一般这种修改并不容易,因为 Angular 中的 NgRx 是作为单例服务加载的。单例服务必须由 Angular 初始化,因此它们的实例只能在 Angular 元素中使用,比如服务和组件等。
我根据多年的经验,想出了一个奇妙的方法来解决这个问题。我们可以在一个自定义服务中初始化 NgRx store,并将初始化后的 store 的引用传递给一个常量,而该常量可以导入到 Angular 应用的任何位置。
Angular 服务必须使用 @Injectable 装饰器在类中初始化,如下所示:
export const stateData = {
ngRxStore: undefined
};
@Injectable({
providedIn: 'root'
})
export class StateDataService {
constructor(
private store: Store<ReducerTypes>
) {
this.initRedux();
}
// This is the "dirty" trick
public initRedux(): void {
stateData.ngRxStore = this.store;
}
}
此处有两点需要注意:
-
private store:Store 会根据管理 Angular 单例服务的逻辑初始化 NgRx。
-
stateData 是一个对象,用于保存初始化后的 NgRxstore。
在 JavaScript 中,通过 “=” 将一个对象分配给另一个变量不会创建新对象。运算符 “=” 会将变量赋值为内存中已有对象的引用。这就是我们使用stateData.ngRxStore = this.store 的原因。我们将 store 赋给 stateData.ngRxStore。由于这只是对原对象的引用,所以访问 stateData.ngRxStore 的属性,比如 dispatch,实际上就是访问由 Angular 初始化的 NgRx 单例服务。
接下来,我们可以任意文件中导入 stateData,并通过它来访问 store,而且无需初始化。我们无需在构造函数中注入 NgRx store,就可以从应用的任意置访问 store。如此一来,大部分代码都可以保持普通的 TypeScript/JavaScript,而不是 Angular 服务。我的应用中大部分代码都在 libs 文件夹中,而非 ng-services 文件夹中。
根据以往的经验,这个技巧非常有效,在这里使用也非常方便,因为我们可以将所有非 Angular 的代码转移到 React。在 React 应用中,我们需要做的就是导入 Redux store,并将 stateData.ngRxStore 替换为这个 store。例如,src/app/libs/project-helpers/project-handlers中的 CurrentProjectSetter 类,我们可以将 stateData.ngRxStore.dispatch 替换成 store.dispatch。其余代码保持不变。
然而,首先我们必须在 React 应用中初始化 Redux。下面,我们先在 React 项目中添加 redux 和react-redux:
yarn add react-redux redux
然后输入:
yarn add @types/react-redux --dev
NgRx 到 Redux reducer 的转换很简单:
const projectState: SystemData =undefined;
const _projectReducer =createReducer(projectState,
on(REDUCER_ACTIONS.setCurrentProject, (state, data) => {
return data.payload;
})
);
export function projectReducer(state:SystemData, action: typeof REDUCER_ACTIONS.setCurrentProject): any {
return _projectReducer(state, action);
}
得到的 React 代码如下:
const projectState: SystemData =undefined;
export const projectReducer = (state:SystemData = projectState, action: Action<REDUX_ACTIONS>): SystemData=> {
switch (action.type) {
case REDUX_ACTIONS.setCurrentProject:
return action.payload
default:
return state;
}
}
在移植完所有的 reducer,我们就可以创建 react-redux store了:
const REDUCERS = {
project: projectReducer,
projects: projectsReducer,
sectionsForChildOfSelector: sectionsForChildOfSelectorReducer
};
const combinedReducers =combineReducers(REDUCERS)
export const store =createStore(combinedReducers);
接下来,我们就可以在 React 应用中引用导出的常量 store 了。
路由
在 Angular 中,我们使用 @angular/router 来处理路由:
export const routes: Routes = [
{ path: 'private', redirectTo: 'private/projects/list', pathMatch:'full' },
{ path: 'private', children:
[
{
path: '', component:AdminComponent, children: [
{ path: 'projects', redirectTo:'projects/list', pathMatch: 'full' },
{ path: 'projects', children: [
{ path: 'list', component:ProjectsListComponent, canActivate: [ProjectsGuardService] },
...
]
},
{ path: 'project', redirectTo:'projects/list', pathMatch: 'full' },
{ path: 'project', canActivate:[ProjectGuardService], children: [
{ path: '', redirectTo:'projects/list', pathMatch: 'full' },
{ path:`:${URL_PARAMS.projectId}`, redirectTo: ':projectId/info', pathMatch: 'full' },
{ path:`:${URL_PARAMS.projectId}/info`, component: ProjectInfoComponent },
...
]
}
]
}
]
},
{ path: '', redirectTo: 'private/projects/list', pathMatch: 'full' },
{ path: '**', redirectTo: 'private/projects/list', pathMatch: 'full' }
];
在 React 中,我们使用 react-router 来处理路由。目前 React Router 是最流行的路由库。
首先,我们需要安装 react-router。
yarn add react-router-dom
由于我的应用托管在 GitHub Pages 上,因此我们需要使用 HashRouter(不是 BrowserRouter)。在这种方式下,路由的前缀都是 “#”。
由于所有视图都在同一个地方渲染,AdminLayout 组件的 content 区域就是我们渲染路由的地方。
为了方便阅读,所有路由都在一个单独的 React 组件 AnitaRoutes 中定义。
import { HashRouter as Router } from'react-router-dom';
export const AdminLayout = () => (
<Router>
<Header />
<div className="relative admin-container flex">
<Sidebar>
<SidebarMenu />
</Sidebar>
<Content>
<AnitaRoutes />
</Content>
</div>
</Router>
);
AnitaRoutes 中包含所有的路由定义:
import { Navigate, Route, Routes } from'react-router-dom';
export const AnitaRoutes = () => (
<Routes>
<Route path={ANITA_URLS.projectsList} element={<ProjectsList/>} />
<Route path={ANITA_URLS.projectAdd} element={<AddEditProject/>} />
<Route path={ANITA_URLS.projectEdit} element={<AddEditProject/>} />
<Route path={ANITA_URLS.projectsNone} element={<ProjectsNone/>} />
<Route path={ANITA_URLS.projectDetails} element={<ProjectDetails/>} />
<Route path={ANITA_URLS.projectSectionElesList}element={<SectionElementsList />} />
<Route path={ANITA_URLS.projectSectionEleDetails}element={<SectionElementDetails />} />
<Route path={ANITA_URLS.projectSectionAddEle}element={<AddEditSectionElement />} />
<Route path={ANITA_URLS.projectSectionEditEle}element={<AddEditSectionElement />} />
<Route path="*" element={<Navigateto={ANITA_URLS.projectsList} />}
/>
</Routes>
)
请注意,所有的路由都定义在常量 ANITA_URL 中。这是因为我们希望尽量保持代码整洁,而且也希望尽量简化路由的定义。
export const ANITA_URLS = {
// PROJECTS
projectsNone: '/projects/none',
projectsList: '/projects/list',
projectAdd:`/projects/${EDITOR_MODE.add}`,
projectEdit:`/projects/${EDITOR_MODE.edit}/:${URL_PARAMS.projectId}`,
// PROJECT
projectDetails:`/project/:${URL_PARAMS.projectId}/info`,
projectSectionElesList:`/project/:${URL_PARAMS.projectId}/list/:${URL_PARAMS.sectionId}`,
projectSectionAddEle:`/project/:${URL_PARAMS.projectId}/:${URL_PARAMS.sectionId}/${EDITOR_MODE.add}`,
projectSectionEditEle:`/project/:${URL_PARAMS.projectId}/:${URL_PARAMS.sectionId}/${EDITOR_MODE.edit}/:${URL_PARAMS.elementId}`,
projectSectionEleDetails:`/project/:${URL_PARAMS.projectId}/:${URL_PARAMS.sectionId}/details/:${URL_PARAMS.elementId}`,
如你所见,有些路由是在其他常量 URL_PARAMS 和 EDITOR_MODE 中定义的。这种方式可以避免输入错误,并让整个应用中的所有路由都保持一致。
这两个常量非常简单:
export const URL_PARAMS = {
projectId: 'projectId',
sectionId: 'sectionId',
elementId: 'elementId',
parentId = 'parentId'
}
export const EDITOR_MODE = {
add: 'add',
edit: 'edit',
}
举个例子,路由 projectSectionEditEle:
/project/:${URL_PARAMS.projectId}/:${URL_PARAMS.sectionId}/${EDITOR_MODE.edit}/:${URL_PARAMS.elementId}
转换成了:
/project/:projectId/:sectionId/edit/:elementId
下面,我们只需生成路由的链接。我们可以使用一个函数来填充路由的所有参数。指定一个 URL,以及一组参数和值,该函数就能够自动填充所有的参数,并生成正确的 URL :
export function urlParamFiller(url:string, paramsToFill: Array<{ name: URL_PARAMS; value: string }>): string{
let result = url;
paramsToFill.forEach(params => {
result = result.replace(new RegExp(`:${params.name}`, 'g'),params.value) }
);
return result;
}
我们不需要担心参数的顺序,因为 urlParamFiller 函数会替换路由中出现的所有参数。
我们可以利用该函数生成路由的链接,例如生成指向项目详细信息页面的链接:
<Link
to={urlParamFiller(ANITA_URLS.projectDetails, [{ name:URL_PARAMS.projectId, value: project.id }])}
className="px-4 py-3 text-white inline-flex items-centerleading-none text-sm bg-prussian-blue-400 hover:bg-prussian-blue-500rounded"
>
<i className="bi-info-circle mr-2"></i>Projectdetails
</Link>
这个例子也表明使用 React 构建应用比 Angular 更加容易。我们的路由只是常量连接的字符串,可通过上述函数生成链接。为了避免人为错误,我们使用 urlParamFiller 函数填充路由的参数。虽然有需求的话,可以使用嵌套路由,但此处我们不需要指定路由的层次结构。我们可以简单地使用连接字符串,将生成完整的 URL 的任务委托给 urlParamFiller,就可以导航到任意想去的页面了。
构建用户界面
不幸的是,UI 的构建从 Angular 移植到 React 就没有那么简单了,因为我们使用的 UI 库有很大的不同。
在 Angular 中,我们使用了 Nebular,这是一个基于 Bootstrap 4 的 UI 库,包含大量预定义的 UI 组件。而在 React 中,我们选择使用 TailwindCSS,其中提供了很多组件可用于创建漂亮的 UI,但这个库不附带任何预定义组件。所以,我们必须手动构建。
简单来说,我们需要从零开始构建整个应用的 UI。
在这个过程中,我们需要注意一个问题:我们不需要从 Angular 组件中借用很多代码。有一个非常重要的最佳实践:尽可能将 UI 和代码分开。因此在创建新的 UI 组件时,我们应该尽量将逻辑处理保留在应用的代码中,而不是 UI 组件中。无论是 Angular 还是 React,我都将这些代码都保存到了文件夹 src/app/libs 中。我们可以在组件中导入运行应用运行所需的 libs 类和函数。
比一下 Angular 和 React,我们还会注意到 Angular 版本更为复杂,因为它的样板代码更多。
下面,我们来看一个简单的 Angular 组件,AddBtnComponent,它由三个文件组成:
- add-btn.component.ts:组件本身。
- add-btn.component.html:组件的HTML模板。
- add-btn.component.scss:组件的样式。
add-btn.component.ts
import ...
@Component({
selector: 'app-add-btn',
templateUrl: './add-btn.component.html',
styleUrls: ['./add-btn.component.scss']
})
export class AddBtnComponent {
@Input()
public url: string;
@Input()
public icon = 'plus-outline';
@Input()
public element: SectionElement;
constructor(
private router: Router
) { }
public navigate(): void {
this.router.navigateByUrl(this.url, {
state: { element: this.element }
});
}
}
add-btn.component.html
<button *ngIf="url"nbButton shape="round" status="primary"size="giant" class="position-absolute shadow"(click)="navigate()">
<nb-icon [icon]="icon"></nb-icon> <!-- Nebularicon element -->
</button>
add-btn.component.scss:略。
而在 React 中只需要一个文件 add-edit-element-button.component.tsx,且只有 6 行代码:
export const AddEditElementButton = ({icon, url }) => (
<Link to={url} className="absolute bottom-5 right-7 md:bottom-7md:right-10 bg-prussian-blue-400 text-white text-xl shadow-xl rounded-3xl h-14w-14 flex items-center justify-center">
<i className={icon}></i>
</Link>
)
由于 React 16.8 引入了 Hooks,因此样板代码几乎为零。
你可以根据自己的喜好,选择喜欢的框架。根据我的经验,使用 React 编写应用更快,也更容易维护。
最终的结果
从Angular 移植到 React 是一次非常有趣的尝试,也是一次很棒的学习经历。
下面是最终得到的应用的一些截图:
1.初始页面,项目还未建立:
2.示例项目:
为了方便比较,我们来看看 Angular。
1.初始页面,项目还未建立:
2.示例项目:
原文地址:https://anita-app.com/blog/articles/porting-anita-from-angular-to-react-resulted-in-20-less-code.html
更多推荐
所有评论(0)