前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版

Ionic3 升级 Ionic4 变更对比与迁移避坑记录

首页2019-06-08 18:10:24Front-End
IonicAngular迁移升级

Ionic 3 升 Ionic 4 这件事,最容易让人低估的地方在于它不是一次版本升级,而是一次「换底座」。Ionic 3 是把 Angular 项目包了一层,你写的是 Ionic 的页面、Ionic 的导航、Ionic 的懒加载;Ionic 4 把这层壳拆了,底下就是一个标准 Angular 项目,组件改成了 Web Components,路由交还给 Angular Router。

结果就是,你以为改改包名就能跑,实际上路由、生命周期、样式、控制器调用方式全都要动。这篇是我当年对着两个版本逐项比对留下的记录,按九个维度列清楚哪里变了、旧写法对应的新写法长什么样。

在本篇文章中,我们将从浅入深,和大家一起学习以下知识:

  • Ionic 4 项目结构变成了什么样,ionic start 的参数要怎么带才不会又建出一个 v3 项目
  • provider 改叫 service、ionic-angular 改叫 @ionic/angular 这些命名和依赖的变动
  • 样式为什么不能再写 page-xxx {},全局样式该放哪
  • 从 Push/Pop 导航切到 Angular Router 要改哪些地方,两套导航能不能混用
  • 页面生命周期哪些保留、哪些被 Angular 的钩子接管了
  • 懒加载从 @IonicPage 换成 loadChildren 的完整对照
  • Loading / Toast / Alert 从同步创建改成 Promise 之后,代码要怎么改
  • 这套迁移在今天还值不值得做

# 一、项目结构的差异

先说结论,Ionic 4 项目就是一个 Angular 项目,没有别的了。

不ionic了,以后都angular了。命名方式都用angular的了;provider也改成angular的叫法了,以后请叫service

这句是当年记的原话,糙但准。Ionic 3 里 ionic g provider MyData 生成的那个东西,在 Ionic 4 里就是 Angular 的 @Injectable() 服务,连生成命令都建议直接用 ng generate service。心里先完成这个切换,后面很多变更就顺理成章了。

创建项目的命令签名是这样:

ionic start <name> <template> [options]
@前端进阶之旅: 代码已经复制到剪贴板
ionic start myApp
ionic start myApp blank
ionic start myApp tabs --cordova
ionic start myApp tabs --capacitor
ionic start myApp tabs --type=angular
ionic start myApp blank --type=ionic1
@前端进阶之旅: 代码已经复制到剪贴板

这里有个坑要注意。原生能力那一层除了 Cordova 之外多了 Capacitor 的选项,这是 Ionic 官方自己做的替代方案;而想创建 Angular 版本的 Ionic 4 项目,必须带 --type=angular 参数,不带参数创建出来的是 ionic3 项目:

ionic start myApp tabs --type=angular
@前端进阶之旅: 代码已经复制到剪贴板

这条当时坑了不少人,命令跑完没报错,项目也能起,结果照着 v4 文档写代码处处对不上,回头一看目录结构还是 v3 的。

当然也可以用angular-cli创建普通Angular项目,然后npm添加@ionic/core模块,创建完成后到目录结构如下图所示,它不再像ionic3那样封装了angular项目,而是直接就是一个angular项目,而且默认懒加载

下面这张是新项目的顶层目录,注意 src 平级多出来的 angular.json、e2e、karma.conf.js 这些标准 Angular CLI 产物。

Ionic 4 项目顶层目录结构,可以看到 angular.json 等标准 Angular CLI 文件

再往里看 src/app,每个页面自带一个 xxx-routing.module.ts,这就是「默认懒加载」的来源。

src/app 下的页面目录,每个页面自带独立的 routing module

看到这两张图就该明白了,往后你查文档的顺序要反过来:先查 Angular 怎么做,再看 Ionic 在这基础上加了什么。

# 二、配置和代理

v4同angular配置保持一致,可以在angular.json中进行一系列配置,代理则通过proxy.config.json配置

Ionic 3 时代改构建配置是件麻烦事,得去动 @ionic/app-scripts 的 config 目录,能改的口子还很有限。v4 之后这些统统落到 angular.json 里,assets、styles、scripts、budgets、多环境的 fileReplacements 全都是 Angular CLI 那一套。

本地联调跨域走 proxy.config.json,配好之后启动命令要带上,比如 ionic serve --proxy-config proxy.config.json,或者干脆写进 angular.json 的 serve 配置里,省得每次手打。这块完全是 Angular 的能力,Ionic 只是透传。

# 三、依赖变更

  • ionic-angular 引入变为了@ionic/angular
  • rxjs变化主要是由于rxjs5.5引入了Pipeable Operator, 参考这里rxjs

第一条是全局替换就能解决的机械劳动,但要小心从 ionic-angular 里导出的那些东西不是一一对应搬过去的,ViewController 这类在 v4 里干脆没了(第八节会说)。

第二条影响更大一些。RxJS 5.5 引入了 Pipeable Operator 之后,链式的 .map().filter() 要改成 pipe(map(), filter()),导入路径也从 rxjs/add/operator/map 换成了 rxjs/operators。项目里 HTTP 请求处理写得多的话,这一块的改动量比换包名大得多。

// 旧写法
import 'rxjs/add/operator/map';
this.http.get(url).map(res => res.json()).subscribe(...)

// 新写法
import { map } from 'rxjs/operators';
this.http.get(url).pipe(map(res => res)).subscribe(...)
@前端进阶之旅: 代码已经复制到剪贴板

顺带提一句,Angular 的 HttpClient 已经自动帮你解析 JSON 了,那句 res.json() 在新写法里是多余的,留着反而会报错。这个我排查过一次,一直以为是 pipe 用错了。

# 四、CSS 的变更

  • 和angular保持一致,采用style或styleUrls方式引入,不再使用page-**{}方式
  • 全局样式:可以将v3中全局样式放置到global.scss中,也可以创建一个新的scss引入到angular.json中

那为什么 v3 要写 page-home { ... } 这么一层包裹呢?因为当年没有样式隔离,全靠这个类名手工圈定作用域,谁忘了包一层,样式就会漏到别的页面去。v4 用的是 Angular 的组件样式封装,写在 home.page.scss 里的规则默认只作用于这个组件,那层包裹就没有存在的必要了,留着反而会因为多一层选择器导致规则失效。

全局样式统一放 global.scss:

Ionic 4 的 global.scss 全局样式入口以及在 angular.json 中的引入位置

迁移的时候一个偷懒但有效的做法,是先把 v3 那些 page-xxx 包裹整体挪进 global.scss,保证页面先跑起来不变形,再一个页面一个页面往组件样式里拆。一次性全拆容易改到崩,改完还找不出是哪条规则丢了。

# 五、路由差异

angular 路由官方文档 https://angular.cn/guide/router

这是整个迁移里改动最大的一块。

也许Ionic 4中最显着的变化,以及需要对现有应用程序进行最大改变的变化,是转向Angular风格的路由。Ionic过去使用的典型Push/Pop风格导航仍然可用,您甚至可以直接通过Ionic的Web组件使用这种导航方式,但推荐的方法是使用Angular Router

观察目录结构,很容易发现这是一个 angular 项目,是因为它有一个 routing 模块:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

const routes: Routes = [
  { path: '', loadChildren: './tabs/tabs.module#TabsPageModule' }
];
@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule {}
@前端进阶之旅: 代码已经复制到剪贴板

loadChildren 这里写的是字符串形式 '路径#模块名',这是当年的写法,我原样保留。Angular 8 之后官方改推动态 import 的形式(loadChildren: () => import('./tabs/tabs.module').then(m => m.TabsPageModule)),原因是字符串形式没法被 TypeScript 类型检查和 IDE 跳转,写错模块名要跑起来才知道。老项目升 Angular 大版本时这块是必改项。

而对应的路由组件是ion-router-outlet,是对Angular的router-outlet扩展,以兼容旧的导航方式,打开tabs.page.html可看到下面内容

<ion-tabs>
  <ion-tab label="Home" icon="home" href="/tabs/(home:home)">
    <ion-router-outlet name="home"></ion-router-outlet>
  </ion-tab>
  <ion-tab label="About" icon="information-circle" href="/tabs/(about:about)">
    <ion-router-outlet name="about"></ion-router-outlet>
  </ion-tab>
  <ion-tab label="Contact" icon="contacts" href="/tabs/(contact:contact)">
    <ion-router-outlet name="contact"></ion-router-outlet>
  </ion-tab>
</ion-tabs>
@前端进阶之旅: 代码已经复制到剪贴板

这段是 Ionic 4 beta 阶段的 tabs 写法,用的是 Angular 的辅助路由(那对括号 (home:home) 就是 named outlet 语法)。正式版之后 tabs 的结构调整成了 ion-tabs 里放 ion-tab-bar 加 ion-tab-button,标签的路由改由 tabs-routing.module.ts 里的 children 配置,不再用辅助路由。你手上的模板长什么样以你安装的版本和官方文档为准,我把当年这段留在这,是因为不少 2018 到 2019 年间起的项目里就是这个样子,看到了不至于以为写错了。

ion-router-outlet 值得单独说一句。它是 Angular router-outlet 的扩展版,多做的事情是维护一个页面栈并处理转场动画。所以在 Ionic 应用里别用原生的 router-outlet 替换它,换了之后路由能跳,但页面切换会变成生硬的整屏替换,返回手势也没了。

fe
  • 一、项目结构的差异
  • 二、配置和代理
  • 三、依赖变更
  • 四、CSS 的变更
  • 五、路由差异
  • 六、生命周期
  • 七、懒加载
  • 八、组件和指令的变更
  • 九、主题样式的变更
  • 总结
  • 参考

← React 设计模式和最佳实践总结,从组件拆分到 HooksTaro跨平台开发实践 多端编译原理与踩坑总结 →