IONIC介绍

发现国内前端圈里面,现在知道IONIC的人已经很少了,但毕竟这东西也是一门不错的移动端开发技术,感觉可能大多数人对于新技术的追求和尝试,让这个技术看起来地位略微尴尬。

但是,毫无疑问,这门技术,在全球还是相对比较流行的。

IONIC是什么?如果在利用前端技术做手机app的起步阶段,大家大概会听到phonegap、Xamarin。

其中Xamarin是基于C#的一套移动开发框架,当然,现如今团队已经被微软收购,生命力看起来也并不算多顽强,喜欢C#的人可能会用到,但前端同事估计很难去接触。

而phonegap则是最初始的前端技术做手机App的开发框架,当时社区内部也有不少前端在讨论这个技术,以至于后来催生了国内前端移动化的一个趋势。

例如Dcloud、APICloud、AppCan这些国内的前端做移动App的框架,其中Dcloud和APICloud曾经还因为源码事件撕逼过一段时间,这一切都是因为phonegap这样技术催生了国内开发者的视野。

后来phonegap优化成了一部分,发展成了Cordova这门技术。

在RN还未出现的时刻,Cordova可谓是如日中天,当时我曾经也做过几个Cordova项目,在商业趋于移动化的年代,Cordova技术每个移动前端必会的技术。

而当时比较火热的技术angularjs,当时还是1.0脏检查阶段,于是结合Cordova、angularjs发展出来的移动端开发UI框架IONIC应运而生。

IONIC在angularjs在国内前端市场占据百分之九十以上市场的时候曾经也算火热一时,国内涌现了许多利用该技术制作的App。

但,angularjs后来被新兴的单向数据流技术React抢占了大量的市场,以至于市场份额落后到了百分之十左右程度,导致IONIC也受到了发展局限,性能问题频出。

更别说后来React Native的出现,利用JS写原生组件这个概念当时甚嚣尘上,我当时的公司也有将App专项RN开发的想法。

虽然后来Angular更新换代,有了2.0、4和Angular5,性能得到了很大的提升,避免了过量脏检查机制,但好似错过了黄金年代,市场份额则是不愠不火。

IONIC从2018年开始集成React技术,并且在2019年可以投入使用,中间一直是Angular和TS在支撑着平台前进。

但说到底,IONIC只是开发浏览器应用,而不是真正的原生应用,和React Native有很大的区别的。

不过,最近随着大家手机性能的提升,其实原生应用和浏览器应用,在性能上几乎是看不出差异,但如今国内前端圈很多人只知道React Native、Flutter而不知IONIC,实在是有些尴尬。

最近我也重新捡起这门技术,看看这个React应用到底在IONIC上做了哪些事情。

React + TypeScript

IONIC的React引入,同样和Angular一样,都是利用TypeScript做类型检查。并且直接引入的React的高级版本,现如今支持FC和Hooks特性。

下面来一段生成的应用中的代码:

const App: React.FunctionComponent = () => (
  <IonApp>
    <IonReactRouter>
      <IonPage id="main">
        <IonTabs>
          <IonRouterOutlet>
            <Route path="/:tab(books)" component={Books}/>
            <Route path="/:tab(home)" component={Home} exact={true} />
            <Route path="/:tab(books)/details" component={Details} />
            <Route path="/:tab(cate)" component={Categaray} />
            <Route exact path="/" render={() => <Redirect to="/home" />} />
          </IonRouterOutlet>
          <IonTabBar slot="bottom">
            <IonTabButton tab="schedule" href="/books">
              <IonIcon icon={book} />
              <IonLabel>书架</IonLabel>
            </IonTabButton>
            <IonTabButton tab="speakers" href="/home">
              <IonIcon icon={apps} />
              <IonLabel>书城</IonLabel>
            </IonTabButton>
            <IonTabButton tab="map" href="/cate">
              <IonIcon icon={send} />
              <IonLabel>书语</IonLabel>
            </IonTabButton>
          </IonTabBar>
        </IonTabs>
      </IonPage>
    </IonReactRouter>
  </IonApp>
);

组件化

IONIC是个UI框架,已经把组建平台分装成了React组件,可以直接引用就可。

import { RouteComponentProps } from 'react-router';
import { IonItem, IonLabel, IonList, IonThumbnail, IonContent } from '@ionic/react';

不过,我尝试写了一个组件,引入发生报错信息:

Type '{}' is missing the following properties from type 'Readonly<RouteComponentProps<{}, StaticContext, any>>': history, location, matchts(2739)

可见,在IONIC下写一些组件,似乎是失去了自由,感觉略微有些尴尬,这个组件化似乎是不彻底?

不过,在项目中可以直接用div去写当前组件,但这样似乎是不能共用?

稍等,这是TypeScript的校验,于是我们需要做一些修改:

import { Header } from '../../components'


interface props {
    Header?: PureComponent
}

class Home extends React.PureComponent<props, {}> {}

Header组件经过改造终于可以用了,那么,我们就可以通过写Web的方式,来开发移动端的应用了。

工程化

1、Icon

对一个对应用有想法的人,ionicons满足不了项目需求,于是,这个时候可以选择引入iconfont或者font asome

因为我并不是美工,也没人上传到iconfont一些图标,于是我选择引入fontasome:

直接在public/index.html以CDN的方式引入:

 <link href="http://netdna.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">

项目中直接用:

 <i className="fa fa-home fa-fw"></i>
 <i className="fa fa-book fa-fw"></i>

2、Request

利用fetch添加通用请求方式(其实也可以用别的):

import _ from 'lodash'

interface HttpProps {
    url: string,
    method: string,
    body: object
}

export default function httpRequest(httpProps: HttpProps) {
    const init = {
        method: httpProps.method,
        headers: {
            Accept: 'application/json',
            'Content-Type': 'application/json',
            'Accept-language': 'zh-CN',
        },
        body: ''
    }

    if (httpProps.method !== 'GET') {
        init.body = JSON.stringify(httpProps.body);
    }

    return fetch(httpProps.url, init).then(checkStatus, (err) => console.warn('===>request checkStatus error', err)).then(
        (val) => {
                const result = val.json();
                console.log(
                    `%c fetch end: ${httpProps.url}`,
                    'color: green;font-size: 14px;font-weight:bold;',
                    result,
                );

                return result;
        },
        (err) => {
            console.warn('===>parse json error', err);
        },
    ).catch((err) => {
        console.error('###ERROR###', err);
    });
}

function checkStatus(response: any) {
    const status = _.get(response, 'status');

    if (status >= 200 && status < 300) {
        return response;
    }
    throw new Error('status wrong');
}
  

请求文件可以直接用这个写好的封装:

// import _ from 'lodash';
import { config, httpRequest } from '../utils';

interface ReturnProps{
    result: object,
}

interface RequestBody {
    url: string
}

export default class HttpService<ReturnProps, RequestBody> {
    getHomePages = async (requestBody: RequestBody) => {
        const url = config.baseUrl;
        const result = await httpRequest({url, method: 'GET', body: {}})
        console.log('result==============>', result)
        return result
    }
}

这个时候,是不是想要引入Redux?

3、Redux

在引入Redux主要是通过saga这种方式,这时候基本上要添加的是sagas、reducer、actions。

首先添加Actions:

export const FAIL_GETHOME = 'FAIL_GETHOME'
export const SUCCESS_GETHOME = 'SUCCESS_GETHOME'
export const GET_HOMEDATA = 'GET_HOMEDATA'

添加Reducers:

// import _ from 'lodash';
import { FAIL_GETHOME, SUCCESS_GETHOME, GET_HOMEDATA } from '../actions/home';

const initState = {
    loading: false
}

export default function homeReducer(state = initState, action: any) {
    if (action.type === GET_HOMEDATA) {
        return {
            ...state,
            loading: true,
        };
    } else if (action.type === SUCCESS_GETHOME) {
        return {
            ...state,
            loading: false,
        };
    } else if (action.type === FAIL_GETHOME) {
        return {
            ...state,
            loading: false,
        };
    }
    return state;
}

添加sagas处理请求:

import _ from 'lodash';
import { call, put, takeEvery } from 'redux-saga/effects';
import { FAIL_GETHOME, SUCCESS_GETHOME, GET_HOMEDATA } from '../actions/home';
import HttpService from '../services/http-service';

function* getHomeData(action: any) {
    const key = _.get(action, 'key');
    const httpSerice = new HttpService()
    try {
        const data = yield call(httpSerice.getHomePages, action.payload);
        if (!data) {
            yield put({ type: FAIL_GETHOME, status: 2, key });
        } else {
            yield put({ type: SUCCESS_GETHOME, status: 0, payload: data, key });
        }
    } catch (e) {
        yield put({ type: FAIL_GETHOME, status: 1, key });
    }
}

export function* watchGetHomeData() {
    yield takeEvery(GET_HOMEDATA, getHomeData)
}

到这里一个基本的Redux流程添加完毕。接下来可以在项目中触发Actions,来进行数据请求了。

IONIC和RN

最后谈谈IONIC这门技术和React Native的区别。

两者都是基于某种技术的一种UI开发App方式,IONIC的组件相对比较完善,RN的组件则是比较简单。

开发方式来说,IONIC则是浏览器开发方式,相当于是开发的是一个webapp,但是RN开发出来的是原生应用。这是两者主要的技术区别。

上手来说,毫无疑问,IONIC的开发上手起来更快,其实是IONIC的开发就是普通的web+UI框架开发,而RN则还有些前置理解成本。

总的来说,现如今性能方面渐渐被日渐性能提升的手机抹平,其实在这个时候,回过头再看IONIC,也是一种不错的选择,不是吗?