記一次fis3+react開發經歷

前言:

雖說是記錄fis3+react的一次開發經歷。可是在項目的上線前幾天收到公司TC委員會的郵件,由於react的開源協議讓找到react的替代方案,而且逐步下線線上的react項目。真的是能夠用「出師未捷身先死」來形容此次開發了。javascript

不過通過調研之後發如今業界已經有了一些開源方案來替代react 。最有名的就是preact的了吧。並且按照官網的方案來作遷移的話,遷移的成本也挺小的。後續會介紹下遷移的狀況。本文也是主要介紹下,使用react+fis3開發的一些經驗和其中遇到的問題的解決方案。css

下面的文章也統一用react這個名詞。前端

1.爲何要使用react+fis3

目前在選擇使用Vue或者是React的時候,總要說些爲何要用。其實個人想法很簡單。由於咱們的產品是偏向業務型的,複雜的數據交互很少,可是流程多,並且不少業務要複用一些頁面流程。按照咱們舊的開發模式來講,咱們的模板文件存在着不少的if...else來作流程判斷。這樣對於咱們維護項目來講是很是的不方便的。咱們但願引入一項技術來解決不一樣流程公用一些頁面的問題,並且能夠在不一樣的流程中自定義這個頁面的表現的技術。java

React的組件思想是一種解決方案。頁面的每個元素均可以做爲一個組件來抽象。擴大到一個頁面也能夠做爲一個組件。因此這是我選擇React來開發個人頁面。node

按照業內主流作法使用react搭配webpack是最主流的。可是在公司內主推fis3的狀況下仍是選擇了fis3。並且fi3的維護團隊也產出過一篇引導文章和demo來介紹使用fi3開發react應用(參考文章見文章末尾)。react

在項目開始前也在分析要不要引入redux。咱們的頁面是偏向業務的,不少頁面都是表單的提交和驗證。並無不少的狀態須要維護,並且咱們的數據源也很單一。不過在通過一個頁面的開發後仍是發現一些頁面引入redux會對開發工做帶來很大的便利。webpack

2.項目的目錄結構和做用

react的開發模式基本上都差很少。由於此次我並無引入redux。因此個人目錄結構也相對簡單一些。以下:ios

.
├── components    //組件目錄
├── containers    //組件容器
├── language    //語言包目錄,爲項目作國際化預留的能力
├── node_modules       //npm依賴
├── package-lock.json
├── package.json
├── page    //頁面模塊
├── routes    //路由模塊
├── state    //由於沒有引入redux可是有一個頁面的狀態實在太多仍是單獨爲它作了一個文件管理state
├── static    //靜態文件模塊
└── yarn.lock

3.拆分頁面

使用react的第一項工做就是要拆分本身頁面。把本身的頁面拆分紅「一塊一塊」
的組件。因此看下圖個人頁面結構。web

我是這樣拆分個人頁面的,紅色的線就是個人拆分模式。typescript

因此個人components目錄下的文件是這樣的,分別對應這我對頁面拆分後的組件。

├── components
│   ├── Header.jsx
│   ├── Input.jsx
│   ├── PassBtn.jsx
│   ├── SafeCenter.jsx
│   ├── SafeList.jsx
│   ├── header.styl
│   ├── input.styl
│   ├── passbtn.styl
│   ├── safecenter.styl
│   └── safelist.styl

而個人comtainers目錄就是對應着個人頁面文件,其實就是一個個零散組件的容器。

├── containers
│   ├── BankList.jsx
│   ├── EditUserInfor.jsx
│   ├── EditUserInfor.styl
│   ├── SafeCenterIndex.jsx
│   ├── VerifyBank.jsx
│   ├── VerifyRealName.jsx
│   ├── banklist.styl
│   ├── verifybank.styl
│   └── verifyrealname.styl

還有最有一個值得介紹的就是routes目錄,管理整個項目的路由。

├── routes
│   └── index.jsx

由於使用的preact-router這裏的寫法和react-router的有一些不一樣。代碼以下:

export default (
    <Router history={createBrowserHistory()} >
        <SafeCenterIndex path="/v4/security/"/>
        <VerifyRealName path="/v4/security/verifyname"/>
        <VerifyBank path="/v4/security/verifybank"/>
    </Router>
);

4.寫react確定會面臨的問題

寫react時,咱們在享受着react的visual dom的高性能和不依賴dom的編程的便利時,面對最大的問題就是對組件的state的管理吧。固然最好的解決方案確定是引入redux作狀態管理。可是當咱們沒有引入redux時怎麼辦呢?

我先來舉個簡單的例子。咱們有如下一個對像來管理頁面的顯示狀態:

var obj= {
    "aaa": {
    
        "bbb": {
            "ccc": {
                "ddd": {
                    "header": "istrue"
                }
            }
        }
    }
}

當咱們的一個state嵌套太深時,按照咱們使用js的通常作法要更新header的值的作法以下:

obj.aaa.bbb.ccc.ddd.header='isfalse';

有時候咱們爲了可以讓代碼更健壯可能會這麼寫:

obj.aaa || obj.aaa.bbb || obj.aaa.bbb.ccc || obj.aaa.bbb.ccc.ddd || obj.aaa.bbb.ccc.ddd.header = 'isfalse'

會發現這樣作真的繁瑣。固然我在作的時候也面臨着這樣的問題。經過查詢相關資料,最佳的解決方案固然仍是引入redux。那麼次佳的解決方案呢。其實就是引入第三方庫,最具表明性的就是facebook本身的facebook/immutable-js。這個庫能讓咱們方便、安全、高效的更新一個層次較深的state。可是也有一個缺點就是文件體積較大。固然與之相對應的就是開源大神作了優化後的版本immutability-helperimmutability-helper

這三個庫中文分析介紹的文檔挺多的能夠自行搜索瞭解。固然最後我上面的都沒有用。由於以前的個人項目中已經引入了lodash這個開源庫。而它也提供了較安全的更新一個深層次object的方法

若是使用lodash更新上面header的值,寫法以下:

import * as _ form 'lodash';

_.set(obj, 'aaa.bbb.ccc.ddd.header', 'isfalse');   //更新header的值

var header =  _.get(obj, 'aaa.bbb.ccc.ddd.header') //獲取header的值

還有一個值得注意的地方就是在咱們更新state以前,都是「克隆」並且是「深克隆」一個state去更新。「深克隆」確定是會影響程序性能的,因此facebook的facebook/immutable-js提供了高效的方法去「深克隆」一個對象。

固然使用lodash也會更方便一些。可是這樣的操做不該該常常的發生。

import * as _ form 'lodash';

var initState = {};

var deepCloneState = _.cloneDeep(initState);   // 咱們操做的其實都是這個clone的備份

第三個,出現的比較坑的問題。瀏覽器或者是webview緩存GET請求。

這個問題主要發生在須要屢次以GET的方式請求同一個接口。解決的方案也挺簡單就是在咱們發起的GET請求後面加上時間戳。以使用axios爲例:

axios.get('/v4/xxx/action?v=' + (new Date).getTime())
    .then(data => {})
    .catch(err => {})

5.打包工具的配置

原本想統一的使用typescript插件來編譯jsx的。由於遷移preact緣由,要修改全局pragma。因此編譯前端的jsx就使用了babel-5.x插件,如下是全局的配置文件介紹。

// 定義一個全局的變量目錄
const dirList = '{actions,components,constants,routes,containers,page,state,language,reducers,store}';

fis.match('/client/(' + dirList + '/**.{js,es,jsx,ts,tsx})', {
        parser: fis.plugin('babel-5.x', {
            sourceMaps: false,
            optional: ['es7.decorators', 'es7.classProperties'],
            jsxPragma: 'h'   // 這裏也是最重要的遷移preact後必須加的一個參數
        }),
        // 頁面中顯示的url而且加上自定義的v4前綴
        url: '/v4${static}/${namespace}/$1$2$3$4$5$6$7$8$9$10',
        isJsXLike: true,
        // 設置位模塊目錄最後的編譯結果都是會用define包裹
        isMod: true
    })
    // 這裏都是爲了給靜態文件加上v4自定義前綴
    .match('/client/({components,containers}/**.styl)', {
        url: '/v4${static}/${namespace}/$1',
    })
    // 這裏都是爲了給靜態文件加上v4自定義前綴
    .match('/client/static/({img,js,styl}/**.{png,js,ico,styl})', {
        url: '/v4${static}/${namespace}/static/$1$2$3'
    })
    
    // 由於使用的stylus作位css的預編譯工具,這裏的配置是編譯stylus的配置
    .match('*.styl', {
        rExt: '.css',
        parser: fis.plugin('stylus', {
            sourcemap: false
        }),
        preprocessor: fis.plugin('autoprefixer', {
            'browsers': ['Android >= 2.1', 'iOS >= 4', 'ie >= 8', 'firefox >= 15'],
            'cascade': false
        })

    })

以上的配置文件是開發環境的配置,在頁面加載的時候也是對靜態文件逐條加載的。

並且頁面的加載時間也比較長不符合咱們線上的加載靜態文件的需求。

緊接着對打包腳本進行優化

fis.media('prod')
   // 壓縮css 
    .match('*.{styl,css}', {
        'useHash': true,
        'optimizer': fis.plugin('clean-css')
    })
    .match('/client/node_modules/**.{js,jsx}', {
        'isMod': true
    })
    .match('/client/**.{js,es,jsx,ts,tsx}', {
        'preprocessor': [
            fis.plugin('js-require-file'),
            fis.plugin('js-require-css')
        ]
    })
    
    //合併靜態文件
    .match('::packager', {
        'packager': fis.plugin('deps-pack', {
            // 將全部的npm依賴打包成一個文件
            '/client/pkg/npm/bundle.js': [
                '/client/page/index.js:deps',
                '!/client/' + dirList + '/**'
            ],
            //將全部的業務代碼打包成一個文件
            '/client/pkg/npm/index.js': [
                '/client/page/index.js',
                '/client/page/index.js:deps'
            ],
            //將全部的css文件打包成一個文件
            '/client/pkg/npm/bundle.css': [
                '/client/**.{styl,css}',
                '!/client/static/**.{styl,css}'
            ]
        })
    })
    //給全部打包完的文件加前綴
    .match('/client/(pkg/npm/**.{js,css})', {
        'url': '/v4${static}/${namespace}/$1',
    });

這樣作最後線上的頁面加載時,加載的靜態文件(除了圖片)只有3個。頁面的加載時間也保留在200ms之內。而全部的npm依賴最後的bundle文件也只有80kb的大小。這個對於現代的前端網絡是能夠接受的。

排除非首屏的加載,使用緩存加載頁面的。這個時間已經縮短的極小了。

如下是我在一APP內打開頁面後,每次都使用緩存文件加載的結果。全部的靜態文件都使用了本地緩存,http狀態碼都是304

在這個過程當中也發現一個問題,就是使用時間戳的方式和使用hash戳的方式,緩存靜態文件。觀察下面的截圖發現,使用時間戳的方式,並不能有效的緩存咱們的靜態文件,每次進入頁面,靜態文件都從新發起了請求。由於又沒有使用CDN加速,這樣其實也間接的對咱們的服務器形成壓力。

遷移preact

最後就是介紹下遷移react到preact我都是作了那些工做吧。固然按照官網提供的步驟一步一步走確定是沒有錯的。

首先是修改庫的引入方式

import {h, render, Component} from 'preact';

由於使用了preact-router。因此路由的配置方式和react-router的有些不同的

import {h, render, Component} from 'preact';
import {Router} from 'preact-router';
// import {Router} from 'react-router';
import SafeCenterIndex from '../containers/SafeCenterIndex';
import VerifyRealName from '../containers/VerifyRealName';
import VerifyBank from '../containers/VerifyBank';
import {createBrowserHistory} from 'history';



export default (
    //這裏的寫法和react-router不同。
    <Router history={createBrowserHistory()} >
        <SafeCenterIndex path="/v4/security/"/>
        <VerifyRealName path="/v4/security/verifyname"/>
        <VerifyBank path="/v4/security/verifybank"/>
    </Router>
);

第二就是修改編譯的打包腳本。按照官網的介紹就是要修改最後編譯結果的jsx的包裹方式。在前面的關於打包腳本的配置已經介紹過了.主要就是配置jsxPragma屬性。

fis.match('/client/(' + dirList + '/**.{js,es,jsx,ts,tsx})', {
        parser: fis.plugin('babel-5.x', {
            sourceMaps: false,
            optional: ['es7.decorators', 'es7.classProperties'],
            jsxPragma: 'h'
        }),
        url: '/v4${static}/${namespace}/$1$2$3$4$5$6$7$8$9$10',
        isJsXLike: true,
        isMod: true
    })

後續的計劃

  1. 引入redux作狀態管理
  2. 由於使用了preact,它默認是不提供prop-type作類型檢查的。因此之後準備在項目引入typescript編寫代碼。由於它默認提供了靜態類型檢查的機制。
  3. 由於react的特色。當咱們切換頁面的時候實際上是在不一樣的view(或者說是state)間進行切換。咱們並無從新請求頁面服務器。因此頁面切換的時候能夠作一些相似原生的切換動畫。
  4. 將靜態文件的加載走cdn加速域名。

參考文章:

  1. 如何用 fis3 來開發 React?
相關文章
相關標籤/搜索