猫窝私语 — Makumo's Blog

玛酷猫的温馨小窝,记录生活点点滴滴。

@玛酷猫6 年前

02/16
16:33
其他

Ionic 3 升级 Ionic 4 迁移踩坑系列(四)

路由
Ionic4整体舍弃了Ionic3中的使用IonicPage设置路由地址的方法,而完全使用Angular的路由器(虽然在ionic3中也可以使用Angular路由,不过之前项目都是使用Ionic3的路由方法)
在初始化的项目结构目录里src/app/app-routing.module.ts文件,一个简单的结构类似下方:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { PermissionGuard, WelcomeGuard } from './guard/';

const routes: Routes = [
  { path: '', redirectTo: '/welcome', pathMatch: 'full' },
  { path: '', canActivate: [PermissionGuard], children: [
    { path: 'app', loadChildren: './pages/tabs/tabs.module#TabsPageModule' },
  ]},
  { path: 'welcome', canActivate: [WelcomeGuard], loadChildren: './pages/welcome/welcome.module#WelcomePageModule' },
  { path: 'sign-in', loadChildren: './pages/sign-in/sign-in.module#SignInPageModule' }
];
@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule {}

在这个简单结构中包含一个tabs主模块,一个欢迎页,一个登录认证页面。用户第一次访问时,由第一条路由配置导向欢迎页,在欢迎页的配置上有路由守卫(Route guards)的设置,用来判断是否第一次访问,如果不是则引导到主模块,在主模块上同样配置一个路由守卫,用来检验用户权限,如权限不正确则引导到登录页面。此外路由不仅可以配置在总的路由配置文件中,也可以配置在各级子模块中,一方面清晰模块关系,同时也方便模块的添加和移动。路由相关配置也有很多,详情可以参考官方文档,这里是传送门

路由守卫
在Ionic3中,使用ionViewCanEnter和ionViewCanLeave实现Route guards的功能,而在Ionic4中,由于使用Angular的路由,同样也使用其对应的路由接口,在官方文档中,有5种接口,分别是

CanActivate to mediate navigation to a route.
CanActivateChild to mediate navigation to a child route.
CanDeactivate to mediate navigation away from the current route.
Resolve to perform route data retrieval before route activation.
CanLoad to mediate navigation to a feature module loaded asynchronously.

在上面的例子中,使用了两个路由守卫用来判断是否第一次访问和是否具有用户权限,其中判断初次访问的例子如下

import { Injectable } from '@angular/core';
import { Router, CanActivate, RouterStateSnapshot, ActivatedRouteSnapshot } from '@angular/router';
import { Storage } from '@ionic/storage';

@Injectable({
  providedIn: 'root'
})
export class WelcomeGuard implements CanActivate {

  constructor(public router: Router, private storage: Storage) {}

  async canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
    const first = await this.storage.get('firstIn');
    if (first) {
      this.router.navigateByUrl('app/tabs/(home:home)');
    }
    return !first;
  }
}

在路由守卫中判断本地存储中是否存在首次访问的标识,有的话就直接进入tabs模块的home子模块。更多详细用法,同样参见官网文档,传送门

Ionic 3 升级 Ionic 4 迁移踩坑系列(四)

@玛酷猫6 年前

01/10
14:39
Ionic(Angular)

Ionic 3 升级 Ionic 4 迁移踩坑系列(三)

接下来就是页面的迁移,先用命令创建原来项目对应的页面,例如登录页面

ionic g page pages/signIn

这是会在src/app/pages目录下面生成sign-in文件夹,里面包含5个文件,相对于Ionic3,除了文件名命名有些许区别外,同时还多了一个测试文件,对比下Ionic3的sign-in.ts和4的sign-in.page.ts文件的主体结构

# ionic3
import { Component, OnInit } from '@angular/core';
@IonicPage({
  segment: 'sign-in'
})
@Component({
  selector: 'page-sign-in',
  templateUrl: 'sign-in.html',
})
export class SignInPage implements OnInit {
  ngOnInit() {}
}

# inoic4
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
@Component({
  selector: 'app-sign-in',
  templateUrl: './sign-in.page.html',
  styleUrls: ['./sign-in.page.scss'],
  encapsulation: ViewEncapsulation.None
})
export class SignInPage implements OnInit {
  ngOnInit() {}
}

整体上差别不大,主要区别在@Component部分,选择器的命名,模板文件和样式文件的引用,3中的@IonicPage部分在4种移除,部分功能被放在Angular的路由中。此外特别提下encapsulation: ViewEncapsulation.None的问题,这部分我也没完全弄明白,大概理解是Ionic4使用了标准的Web Components,可以在页面上看到大量的shadow-dom,默认情况下,由于Angular提供的样式包装机制来封装组件,使得组件的样式不受外部影响,就是说无法修改组件的样式,所以这里要将封装模式调整成none,以便于修改组件的样式,详细说明请参考官方文档,传送门。此外由于使用标准Web Components,样式也要大量的使用CSS4 Variables的写法。

页面生命周期

在Ionic3中,常用的页面生命周期函数有下面6种

  • ionViewDidLoad 当页面加载的时候触发,仅在页面创建的时候触发一次,如果被缓存了,那么下次再打开这个页面则不会触发
  • ionViewWillEnter 当将要进入页面时触发
  • ionViewDidEnter 当进入页面时触发
  • ionViewWillLeave 当将要从页面离开时触发
  • ionViewDidLeave 离开页面时触发
  • ionViewWillUnload 当页面将要销毁同时页面上元素移除时触发

在Ionic4中,使用Angular的生命周期函数,具体ionViewDidLoad被ngOnInit替代,ionViewCanEnter和ionViewCanLeave被路由守卫替代,其他的暂时可以正常使用,不过可能在以后的版本中被其他用法做代替。

组件的标准化

由于使用Web Components标准,很多组件和属性的写法也发生变化,譬如

// ionic 3
<button type="button" ion-button clear small icon-start (click)="findPassword()" class="option-password">忘记密码</button>

<ion-list radio-group no-lines [(ngModel)]="schoolCode">
  <ion-item *ngFor="let s of schools">
    <ion-avatar item-start><img src="{{s.logo}}"></ion-avatar>
    <ion-label>{{s.name}}</ion-label>
    <ion-radio value="{{s.code}}"></ion-radio>
  </ion-item>
</ion-list>

// ionic 4
<ion-button type="button" fill="clear" size="small" (click)="findPassword()" class="option-password">忘记密码</ion-button>

<ion-list lines="none">
  <ion-radio-group [(ngModel)]="schoolCode">
    <ion-item *ngFor="let s of schools">
      <ion-avatar solt="start"><img [src]="s.logo" alt=""></ion-avatar>
      <ion-label>{{s.name}}</ion-label>
      <ion-radio [value]="s.code"></ion-radio>
    </ion-item>
  </ion-radio-group>
</ion-list>

除了button标签替换成ion-button以外,很多属性也发生变化,譬如button的所有属性都进行了更换,clear更换成fill=“clear”等,其他标签的属性,譬如item-start更换为solt=“start”,此外类似radio-group的用法也发生大变化。由于不少用法发生变化,具体还是对应着官方文档进行迁移,传送门

Ionic 3 升级 Ionic 4 迁移踩坑系列(三)

@玛酷猫6 年前

01/4
10:59
Ionic(Angular)

Ionic 3 升级 Ionic 4 迁移踩坑系列(二)

首先是更新NodeJS版本,Angular6要求版本不低于8.X,由于之前安装高版本的nodeJS一直有着不可描述的问题,导致node-sass无法安装,无奈只能安装6.X的版本使用本地文件来强行安装node-sass,不过现在貌似没这个问题了。更新也比较方便,卸掉重新安装就好了。
开启一个新的tab项目,详细的安装可以参考官方文档

ionic start myApp tabs --type=angular

provider迁移
项目建好后,先将原来项目中的provider复制到新项目中,这时候会看到满篇的错误提示,一个一个来调整。
1、storage在Ionic4中不再是默认自带,需要单独安装

npm install --save @ionic/storage

2、换用新的http模块

//ionic3 部分代码
import { Http, Headers, RequestOptions } from '@angular/http';
//……中间其他代码……
const headers = new Headers();
let Token: string = '';
headers.append('Token', Token);
this.options = new RequestOptions({ headers: headers });

//ionic4 部分代码
import { HttpClient, HttpHeaders } from '@angular/common/http';
//……中间其他代码……
const headers = new HttpHeaders();
let Token: string = '';
headers.set('Token', Token);
this.options = { headers };

由于需要和数据服务器API通讯,用户的信息token是存放在请求的header中,在Ionic3中使用的Headers, RequestOptions来处理请求头部,这两个组件在3中也属于不推荐使用的,当时因为还能用,图省事就继续使用了。4种更换为HttpClient、HttpHeaders,和以前方法使用变化不大,调整下就好。

3、调整Components用法,这部分也是整个项目变动最多的地方,几乎所有的Controller都有参数变动和使用方法的变动,想像迁移文档说的那样直接复制进来就能用基本没法实现。举个例子,项目中有个全局的顶部消息提示控件

//ionic3
toast(message: string) {
  this.toastCtrl.create({
    message: message,
    duration: 1500,
    position: 'top'
  }).present();
}

//ionic4
async toast(message: string) {
  const toast = await this.toastCtrl.create({
    message: message,
    duration: 1500,
    position: 'top'
  });
  toast.present();
}

在Ionic3中可以直接创建后接present()来触发使用,而在4中,需要配合async/await来使用。

4、RxJS的调整,官方有个迁移文档,传送门,并且给出了一个迁移工具,不过试了下不是太好用,可能原本的项目写的就不是很规范,只能自动修复一小部分。还是老老实实手动来吧,顺便加深下印象。以下是一个登录的代码片段

//ionic3
import { Observable } from 'rxjs';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
//……中间其他代码……
PostSignIn(request: PostUserSignInRequest): Observable<any> {
  return this.http.post(this.coreService.baseUrl + '/tokens', request)
    .map(res => res.json());
}

//ionic4
import { Observable } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
//……中间其他代码……
PostSignIn(request: PostUserSignInRequest): Observable<any> {
  return this.http.post(this.coreService.baseUrl + '/tokens', request)
  .pipe(map(res => res));
}

首先是引入调整,这个全项目批量替换下就好,然后是原来的链式操作(dot-chained operators)要更换为管道操作(pipeable operators),简单点说就是将原先所有的.xxx操作全部放置到pipe()这个方法中,其他的用法项目中用到的少,可以去查看官方文档。

Ionic 3 升级 Ionic 4 迁移踩坑系列(二)

@玛酷猫6 年前

12/28
16:31
Ionic(Angular)

Ionic 3 升级 Ionic 4 迁移踩坑系列(一)

Angular今年的发展真是快,去年年底5刚刚发布,年中就发布6,到年底7也来了,大版本的升级也导致它的很多组件也随之升级,慢慢的放弃了对低版本的支持,这对于使用基于Angular的Ionic开发的本人来说就是件很郁闷的事情。之前一直使用Ionic 3开发项目,还是基于Angular5的,年中的时候本地开发环境更新组件没注意,直接把Angular升级到6了,然后可想而知,项目直接跑不起来了。还原package.json后每次更新组件都检查下版本,避免错误升级,很是麻烦。随着Ionic4的测试版本不断的更新,Ionic cli动不动就提示更新Ionic4,稍不留神就傻逼,再加上越来越多的组件默认使用Angular 6,主要是其中RxJS的语法变动很大,导致没法向下兼容5,这也将Ionic升级到4的想法提上日程。毕竟ios和android还在不停的更新版本,每次更新或多或少都会出现兼容性问题,组件导致的兼容性问题主要还是靠组件作者更新,虽然可以自己fork一个分支处理,毕竟费时费力。

去Ionic官网翻了翻迁移指南,官方写的实在是太简单,全文如下:

  1. Generate a new project using the blank starter (see Starting an App)
  2. Copy any Angular services from src/providers to src/app/services
    • Services should include { providedIn: ‘root’ } in the @Injectable() decorator. For details, please see Angular provider docs.
  3. Copy the app’s other root level items (pipes, components, etc) keeping in mind that the directory structure changes from src/components to src/app/components, etc.
  4. Copy global Sass styling from src/app/app.scss to src/global.scss
  5. Copy the rest of the application, page by page or feature by feature, keeping the following items in mind:
    • Emulated Shadow DOM is turned on by default
    • Page/component Sass should no longer be wrapped in the page/component tag and should use Angular’s styleUrls option of the @Component decorator
    • RxJS has been updated from v5 to v6 (see RxJS Changes)
    • Certain lifecycle hooks should be replaced by Angular’s hooks (see Lifecycle Events)
    • Markup changes may be required (migration tool available, see Markup Changes)

In many cases, using the Ionic CLI to generate a new object and then copying the code also works very well.

大概意思就是用命令新建一个空的项目,将原来项目各个部分拷贝进来,注意4的路径有所调整。服务的声明有所调整、全局样式文件不一样、RxJS请自己看升级文档、页面生命周期时间沿用Angular。看来不麻烦呀,于是乎我就傻呵呵的按着文档操作一顿后,都不用试跑的,IDE上满屏幕的红色波浪线(各种错误)仿佛在嘲笑。看了看IDE上面提示的各种错误,连最基本的ionic、angular的引用方式都有所变化,我就不信有人能按照这个智障的迁移文档,把项目跑起来。尤其是看到文档最后一句话:“In many cases, using the Ionic CLI to generate a new object and then copying the code also works very well. ”,还信誓旦旦的说大部分情况下按照这个操作项目都能跑起来,我&……¥&*……&#,还是老老实实一个模块一个页面的迁移好了。

经历的大半个月的迁移工作,虽说还没有完全结束,迁移过程中踩的坑可真不少,准备做一个系列来写了。一方面记录下的这些问题,方便以后查看,另一方面也希望帮助其他的人,毕竟目前在国内,相对于React和Vue,Angular还太小众,各种文档和解决方案都很少,遇到问题都要爬google搜索,要不就去啃官方论坛和stackoverflow。

随带一提的是Ionic(Angular)的发展简直是坑神一样的存在,当年AngularJs升级Angular(ionic1到2)的时候,惊呼上当受骗,本以为是个版本升级,能迁移的时候菜发现这明明就是两种不同的东西,没办法相当于重新学了一遍,ionic2/3到4又相当于再重学一边,4为了更标准化web component,修正了以前版本各种不标准的用法和属性,而且可以说不在依赖框架,也就是说以前Ionic和Angular是捆绑在一起的,从4版本开始,也可以使用React和Vue来做开发,更加灵活。并且使用标准化的web component后,以后也不会出现类似1到2、2/3到4这种痛苦的迁移过程了,应该会很平滑的升级,版本迭代的速度应该会更快。

Ionic 3 升级 Ionic 4 迁移踩坑系列(一)