雖說是記錄fis3+react的一次開發經歷。可是在項目的上線前幾天收到公司TC委員會的郵件,由於react的開源協議讓找到react的替代方案,而且逐步下線線上的react項目。真的是能夠用「出師未捷身先死」來形容此次開發了。javascript
不過通過調研之後發如今業界已經有了一些開源方案來替代react 。最有名的就是preact的了吧。並且按照官網的方案來作遷移的話,遷移的成本也挺小的。後續會介紹下遷移的狀況。本文也是主要介紹下,使用react+fis3開發的一些經驗和其中遇到的問題的解決方案。css
下面的文章也統一用react
這個名詞。前端
目前在選擇使用Vue
或者是React
的時候,總要說些爲何要用。其實個人想法很簡單。由於咱們的產品是偏向業務型的,複雜的數據交互很少,可是流程多,並且不少業務要複用一些頁面流程。按照咱們舊的開發模式來講,咱們的模板文件存在着不少的if...else
來作流程判斷。這樣對於咱們維護項目來講是很是的不方便的。咱們但願引入一項技術來解決不一樣流程公用一些頁面的問題,並且能夠在不一樣的流程中自定義這個頁面的表現的技術。java
React
的組件思想是一種解決方案。頁面的每個元素均可以做爲一個組件來抽象。擴大到一個頁面也能夠做爲一個組件。因此這是我選擇React
來開發個人頁面。node
按照業內主流作法使用react搭配webpack是最主流的。可是在公司內主推fis3的狀況下仍是選擇了fis3。並且fi3的維護團隊也產出過一篇引導文章和demo來介紹使用fi3開發react應用(參考文章見文章末尾)。react
在項目開始前也在分析要不要引入redux
。咱們的頁面是偏向業務的,不少頁面都是表單的提交和驗證。並無不少的狀態須要維護,並且咱們的數據源也很單一。不過在通過一個頁面的開發後仍是發現一些頁面引入redux
會對開發工做帶來很大的便利。webpack
react的開發模式基本上都差很少。由於此次我並無引入redux
。因此個人目錄結構也相對簡單一些。以下:ios
. ├── components //組件目錄 ├── containers //組件容器 ├── language //語言包目錄,爲項目作國際化預留的能力 ├── node_modules //npm依賴 ├── package-lock.json ├── package.json ├── page //頁面模塊 ├── routes //路由模塊 ├── state //由於沒有引入redux可是有一個頁面的狀態實在太多仍是單獨爲它作了一個文件管理state ├── static //靜態文件模塊 └── yarn.lock
使用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> );
寫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-helper
和immutability-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 => {})
原本想統一的使用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加速,這樣其實也間接的對咱們的服務器形成壓力。
最後就是介紹下遷移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 })