React移動專案總結

麥芽糖發表於2016-05-02

React移動專案總結

背景

接觸React半年了,一路走過來,團隊做了幾個專案,不斷的總結經驗,不斷的重構,也看了很多大牛總結的react經驗。
嘗試把自己遇到的問題總結分享出來,希望更多前輩指導指導。

總結基於兩個專案

  • react-mgm 程式碼在Github上,一個元件庫,包括了大部分的總結內容,內部的demo也算一個小SPA專案。

  • 下單系統

Mac下開發

js

react

用class的寫法寫元件,和React.createClass不太一樣。具體babel有文字介紹react-on-es6-plus

有自己團隊的一套簡單的規範standard,就是airbnb搬過來的

redux

用redux做資料流,redux-thunk做非同步。

貌似actions reducers沒法非同步按需載入,於是自己倒騰了非同步載入的redux-async-actions-reducers

and 如果你的應用不夠大的話,就沒有必要非同步載入了,比如手機web。 全部打包也不會很大。

比如說下單系統的公共檔案 common.xxxx.js 佔了80%的程式碼量,在開發的時候達到1.4M,webpack -p 後是 500+kb,經過gzip壓縮之後是100kb。所以檔案大小壓根不是什麼事,沒有必要糾結太多。之前是否使用immutable就糾結了很久。

然而,更多應該關注到js的執行時間上,css和html的渲染處理上,檔案大小真的沒有這麼重要。

react-router

browserHistory需要後臺配合,所以用了hashHistory。 但肯定前者更友好。

路由過場動畫react-addons-css-transition-group。做到了想微信那樣前進後退的切換,很爽。(後來考慮到移動端效能不足問題,沒有采用動畫)

class App extends React.Component{
    render(){
        const action = this.props.location.action;
        let transitionName = `page`;
        // REPLEASE
        if (action === `PUSH`) {
            transitionName = `page-r2l`;
        } else if (action === `POP`) {
            transitionName = `page-l2r`;
        }
        return (
            <ReactCSSTransitionGroup
                component="div"
                transitionName={transitionName}
                transitionEnterTimeout={200}
                transitionLeaveTimeout={200}
            >
                {React.cloneElement(this.props.children, {
                    key: this.props.location.pathname
                })}
            </ReactCSSTransitionGroup>
        );
    }
}

es6/7

寫程式碼太爽了,跟著潮流走。需要在.babelrc檔案上配置好

{
  "presets": [
    "react",
    "es2015",
    "stage-0"
  ]
}

至於es7的stage-x是啥,看http://www.csdn.net/article/2…。也可以無腦的設定stage-0

immutable

確實是會因為一些引用問題導致資料不正確,問題難以發現和排查。和元件的多次渲染。

於是引用immutable,需要克服的是團隊的接受能力,需要點學習成本,但是帶來的效能提升是很高的(做shouleComponentUpdate)。

至於很多人都提到包大小問題,個人認為不用擔心,webpack -p壓縮+gzip,基本壓到很小的體積。我的在120kb,這可是全部程式碼(js+css)啊。

fastclick

在移動端會點選延遲,原因百度吧。用了react-fastclick來處理,具體看這裡 移動端300ms點選延遲和點選穿透問題

相容

專案用了很多es6特性,瀏覽器不支援。可以引入 babel-polyfill。
當然babel-polyfill比較大,你也可以根據專案的具體情況來引入指定的方法。如core-js/es6/object.js core-js/es6/promise.js等等

直接和commons一起打包即可

entry: {
    `commons`: [
        `core-js/es6/object.js`,
        `core-js/es6/promise.js`,
        ...
    ]
}

btw,我在移動埠的時候引入core-js的2.x版本,直接就報錯了。 遇到的同學可以降級到1.x版本。(具體原因還沒有排查)

如果需要相容到ie,據說有挺多坑。 推薦看下這篇文章使用ES6的瀏覽器相容性問題

css

產品主要場景在微信端,所以選擇了weui,使用的感覺是目前weui提供的元件相對少,但是足夠用。weui的剋制也保證了weui的質量。有時候讀程式碼時候發現weui確實沉澱了很多精華在裡面。 配色方面基於weui做改造覆蓋,所以我們選擇了引入weui的less,方便用裡面的已經定義好的變數。

用了大量的Flex佈局,很靈活,降低CSS難度。

字型檔案用了阿里的iconfont。 收集好圖片下載下來,推送到github,然後在釋出到npm。

border

Retina屏的boder和pc的不一樣。 有很多解決方案,可以參考weui對於border的處理。 還可以看這裡 Retina屏的移動裝置如何實現真正1px的線

然後實踐過程中1px遇到的問題遠不止於此,上一個連結提到的只是點也不夠全面,獨立總結了下 移動端1px border

構建

babel

babel做es6/7的轉換,以前一般在loaders上直接寫babel的配置,比如

// 寫在webpack.config.js中
loaders: [{
    test: /.js$/,
    loader: `babel?presets[]=react,presets[]=es2015,presets[]=stage-0`
}]

現在切換到用.babelrc配置上.(也是官方推薦的方法)

{
  "presets": [
    "react",
    "es2015",
    "stage-0"
  ]
}

熱載入

可以在webpack.config.dev.js中配置,不過用命令列的形式更簡潔

webpack-dev-server --inline --hot ...

css

用了postcss來處理css3的相容性。

然而你可能會有機會發現開發的時候會生成 -webkit-flex 這種字首,釋出後卻丟失了。(日了狗)也許是國外的瀏覽器環境及比較好(國內android被微信內建瀏覽器統一了,iOS微信還有大約10%的iOS8的使用者,有些css屬性需要-webkit-字首)

鑑於此,特別注意這個寫法css?-autoprefixer,具體看-webkit-flex 被移除了

js版本控制

官方介紹的很詳細 long-term-caching

用hash做js的版本號,通過AssetsPlugin生成記錄版本號的檔案build/webpack-assets.js,然後頁面引入這個檔案就可以得到js檔案的版本號了。

output: {
    path: path.join(__dirname, `build`),
    filename: `[name].[hash].js`,
    publicPath: `/react-mgm/build/`
},
plugins: [
    new webpack.NoErrorsPlugin(),
    new AssetsPlugin({
        filename: `build/webpack-assets.js`,
        processOutput: function (assets) {
            return `window.WEBPACK_ASSETS = ` + JSON.stringify(assets);
        }
    })
],
// index.html
<script>
    document.write(`<script src="../build/webpack-assets.js"></script>`);
</script>
<script
    document.write(`<script src="` + window.WEBPACK_ASSETS[`index`].js + `"></script>`);
</script>

公共檔案commons處理


webpack commons hash
如何確定哪些檔案應該打包在commons

構建加速

1
http://webpack.github.io/docs…
devtool 設定 eval

2
一些檔案直接引用打包好的版本可以加快構建。

module: {
    (...省略)
    noParse: [
        // `react/dist/react.min.js`,
        // `react-dom/dist/react-dom.min.js`,
        `react-router/umd/ReactRouter.min.js`,
        `redux/dist/redux.min.js`,
        `react-redux/dist/react-redux.min.js`,
        `underscore/underscore-min.js`
    ]
},
resolve: {
    alias: {
        // react 沒法加速build,因為react-addons-css-transition-group
        // `react`: `react/dist/react.min.js`,
        // `react-dom`: `react-dom/dist/react-dom.min.js`,
        `react-router`: `react-router/umd/ReactRouter.min.js`,
        `redux`: `redux/dist/redux.min.js`,
        `react-redux`: `react-redux/dist/react-redux.min.js`,
        `underscore`: `underscore/underscore-min.js`
    }
},

構建環境

目前知道需要設定兩個地方,命令列中加入 NODE_ENV

NODE_ENV=production webpack xxxxx

and webpack配置裡面加入plugin,這樣程式碼就能通過if(__DEBUG__)這種程式碼做環境差異。

new webpack.DefinePlugin({
    __DEBUG__: env === `development` ? true : false,
    "process.env": { // 幹掉 https://fb.me/react-minification 提示
        NODE_ENV: env === `development` ? JSON.stringify("development") : JSON.stringify("production")
    }
})

打包庫檔案

webpack.config.js webpack.config.min.js
有些庫作為依賴項,不應該打包進庫檔案中,用externals來描述

externals: {
    `react`: `react`,
    `react-dom`: `react-dom`,
    `underscore`: `underscore`,
    `classnames`: `classnames`
},

css檔案獨立打包,用ExtractTextPlugin來描述。

最後做壓縮

new webpack.optimize.UglifyJsPlugin({
    compressor: {
        screw_ie8: true,
        warnings: false
    }
})

npm

用了npm script來統一開發規範。
npm start來開啟開發
npm run deploy來發布
npm run publishpatch來發布到npm,並同步到淘寶映象來做加速

"scripts": {
    "precommit": "eslint ./src/component/",
    "pre": "npm install;",
    "clear": "rm -rf build; mkdir build;",
    "start": "npm run clear; webpack-dev-server --config webpack.config.dev.js --port 4000 --host 0.0.0.0 --inline --hot --devtool eval --progress --color --profile",
    "deploy": "npm install; npm run build && npm run build:min",
    "build": "webpack --progress --color --profile",
    "build:min": "webpack --config webpack.config.min.js",
    "publishpatch": "npm run deploy; git add --all; git commit -m `c`; npm version patch; git push origin master:master; npm publish; npm publish --registry=`https://registry.npmjs.org`; cnpm sync react-mgm; npm version;"
  },

另外在專案中遇到版本依賴的問題。開發的時候好好的,釋出後就出問題了。 原因是npm依賴不一致問題。要麼固定版本號,但是隻能固定專案的依賴,依賴的依賴就沒法固定了。 有個方案不錯 npm shrinkwrap

規範

eslint

安裝npm install husky的時候會自動往你的git hooks上加程式碼,提交程式碼的時候觸發想要的npm scripts。

我們用eslint來做檢測,配置見package.json的npm run precommit

eslint的配置用eslintrc.js官方推薦的寫法,具體配置弄成一個自己的庫了。

用了eslint推薦的配置再結合eslint-plugin-react的配置

具體見eslint-plugin-gm

module.exports = {
    "plugins": [
        "gm"
    ],
    "extends": ["plugin:gm/recommended"]
}

server服務

在開發時間避免等後臺api,找了json-server來做api服務,rest風格,很方便。
等後臺ready了,再通過上面提到的server代理呼叫聯調。

效能優化

react效能優化

其他

鍵盤呼氣

如果你的輸入框比較低的話,鍵盤呼氣就會擋住輸入框。 iphone會自動把input移到可見的位置,而android不會。 可以在android上對輸入框使用 scrollIntoViewIfNeed 使元素可見。

and在react下,會出現本來點輸入框的,結果卻是點了其他東西,觸發其他邏輯了。 所以這裡就搞了500ms的延遲。

自動呼氣鍵盤

在android鍵盤需要使用者觸發才可以呼氣。iOS 加個autoFocus即可。

判斷元素可見

一開始是慢慢的算offsetTop,如果層次很深的話,還要算多個parent的offsetTop,然後才能得出,如此必然很煩。 可以用 getBoundingClientRect 即可。

微信title的處理

https://segmentfault.com/a/11…

相關文章