1. 程式人生 > >react技術棧實踐(1)

react技術棧實踐(1)

本文來自網易雲社群

作者:汪洋

背景

最近開發一個全新AB測試平臺,思考了下正好可以使用react技術開發。

實踐前技術準備

首先遇到一個概念,redux。這貨還真不好理解,大體的理解:Store包含所有資料,檢視觸發一個Action,Store收到Action後,返回一個新的 State,這樣檢視就發生變化,State計算過程叫做 Reducer,Reducer其實就是一個處理資料的函式,接受 Action和 當前State作為引數,返回一個新的 State。
明白這個後,就可以開始實踐了。

搭建平臺的腳手架

對於我這方面沒搞過的菜鳥,還真是不容易。接下來說下作為新手如何實踐的。

  1. 第一步:依賴包

    "devDependencies": {
     "babel-core": "^6.26.0",
     "babel-eslint": "^8.2.2",
     "babel-loader": "^7.1.2",
     "babel-plugin-import": "^1.6.6",
     "babel-preset-es2015": "^6.22.0",
     "babel-preset-react": "^6.24.1",
     "babel-preset-stage-0": "^6.24.1",
     "css-loader": "^0.28.7",
     "eslint": "^4.18.2",
     "eslint-config-airbnb": "^16.1.0",
     "eslint-loader": "^2.0.0",
     "eslint-plugin-import": "^2.9.0",
     "eslint-plugin-jsx-a11y": "^6.0.3",
     "eslint-plugin-react": "^7.7.0",
     "extract-text-webpack-plugin": "^3.0.2",
     "html-webpack-plugin": "^3.0.4",
     "less": "^2.7.3",
     "less-loader": "^4.0.6",
     "style-loader": "^0.19.1",
     "url-loader": "^1.0.1",
     "webpack": "^3.1.0"},"dependencies": {
     "normalize.css": "^8.0.0",
     "react": "^16.2.0",
     "react-dom": "^16.2.0",
     "react-redux": "^5.0.7",
     "react-router-dom": "^4.2.2",
     "redux": "^3.7.2"}

    dependencies 中引入的依賴包,是react的標配了,不用解釋。
    devDependencies 中引入了 webpack,babel,babel外掛,eslint語法檢測,eslint配置包airbnb,html模板資源替換外掛 html-webpack-plugin,css提取外掛 extract-text-webpack-plugin,less編譯相關外掛,圖片等靜態資源路徑處理外掛 url-loader。
    這裡作為新手,一般都是參考網上的配置,比如我就是github上找了個專案,摸索一下。推薦一本教程書《React全棧》,作者寫的很詳細,對入門絕對有幫助。
    至此,基本依賴包已載入完。

  2. 第二步:webpack配置 這裡不得不說,新手真不容易。 首先介紹下專案結構:
    views/entry.html(靜態模板),
    src/entry.jsx(入口檔案),
    src/actions(redux概念中Actions所在的資料夾) ,
    src/reducers(redux概念中Reducers所在的資料夾) ,
    src/store(redux概念中Store所在的資料夾) ,
    src/pages(存放頁面的資料夾,jsx),
    src/compinents(存放業務元件的資料夾,jsx),
    src/style(公共樣式資料夾,less),
    src/utils(幫助類資料夾),
    src/constants(常量所在資料夾,儲存各自的actions的type),
    src/plugins(第三方外掛資料夾),
    build/(編譯後文件),
    webpack/(webpack編譯配置所在資料夾),
    .eslintrc(eslint配置檔案),
    .gitignore(git配置檔案),
    package.json

接下來就是webpack的配置了,先上程式碼

const path = require('path');const webpack = require('webpack');// html中替換編譯後的jsconst HtmlwebpackPlugin = require('html-webpack-plugin');// css提取const ExtractTextPlugin = require('extract-text-webpack-plugin');const ROOT_PATH = path.resolve(__dirname);const APP_PATH = path.resolve(ROOT_PATH, '../src');const BUILD_PATH = path.resolve(ROOT_PATH, '../build');module.exports = {
  entry: {
    entry: path.resolve(APP_PATH, './entry.jsx'),
    vendor: ['react', 'react-dom', 'pace']
  },
  output: {
    filename: '[name].js',
    path: BUILD_PATH,
    chunkFilename: '[name].js',
    publicPath: '../'
  },
  devtool: 'eval-source-map',  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'babel-loader',
            query: {
              presets: ['es2015', 'react', 'stage-0'],
              plugins: ['syntax-dynamic-import', ['import', { libraryName: 'antd', style: 'css' }]]
            }
          }
        ]
      },
      {
        test: /\.(css|less)$/,
        use: ExtractTextPlugin.extract({
          fallback: 'style-loader',
          use: [            'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]',            'less-loader'
          ]
        }),
        exclude: /node_modules/
      },
      {
        test: /\.(css)$/,
        use: ExtractTextPlugin.extract({
          fallback: 'style-loader',
          use: [            'css-loader'
          ]
        }),
        include: /node_modules/
      },
      {
        test: /\.(jpg|jpeg|png|svg|gif|bmp)/i,
        use: [          'url-loader?limit=5000&name=img/[name].[sha512:hash:base64:8].[ext]'
        ]
      },
      {
        test: /\.(woff|woff2|ttf|eot)($|\?)/i,
        use: [          'url-loader?limit=5000&name=fonts/[name].[sha512:hash:base64:8].[ext]'
        ]
      }
    ]
  },
  resolve: {
    extensions: ['.js', '.jsx', '.less', '.css', '.png', '.jpg', '.svg', '.gif', '.eot'],
    alias: {
      pace: path.resolve(ROOT_PATH, '../src/plugins/pace/index.js'),
      ImagesPath: path.resolve(ROOT_PATH, '../src/')
    }
  },
  devServer: {
    historyApiFallback: true,
    hot: true,
    inline: true,
    progress: true
  },
  plugins: [    new webpack.optimize.CommonsChunkPlugin({
      name: ['commons', 'vendor'],
      minChunks: 2
    }),    new ExtractTextPlugin('commons.css', {
      allChunks: true
    }),    new HtmlwebpackPlugin({
      template: path.resolve(ROOT_PATH, '../views/entry.html'),
      filename: path.resolve(ROOT_PATH, '../build/entry.html'),
      chunks: ['entry', 'vendor'],
      hash: false
    }),    // 加署名
    new webpack.BannerPlugin('Copyright by xxx')
  ]
};

第一次接觸配置,真的找不到北,太多外掛,太多功能。作為新手,那需要怎麼個思路,我總結:按專案需求來配置。不要認為其他人配置的就適合自己專案,要不然給自己帶來各種麻煩。 摸索這個過程還挺長的:
A. 首先需求還是明確的:less編譯、jsx編譯、公共檔案單獨打包、html靜態模板中插入編譯後的檔案路徑、css提取。 上面這些對應配置:

const path = require('path');const webpack = require('webpack');// html中替換編譯後的jsconst HtmlwebpackPlugin = require('html-webpack-plugin');// css提取const ExtractTextPlugin = require('extract-text-webpack-plugin');const ROOT_PATH = path.resolve(__dirname);const APP_PATH = path.resolve(ROOT_PATH, '../src');const BUILD_PATH = path.resolve(ROOT_PATH, '../build');module.exports = {
  entry: {
    entry: path.resolve(APP_PATH, './entry.jsx'),
    vendor: ['react', 'react-dom', 'pace']
  },
  output: {
    filename: '[name].js',
    path: BUILD_PATH,
    chunkFilename: '[name].js',
    publicPath: '../'
  },
  devtool: 'eval-source-map',  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'babel-loader',
            query: {
              presets: ['es2015', 'react', 'stage-0']
            }
          }
        ]
      },
      {
        test: /\.(css|less)$/,
        use: ExtractTextPlugin.extract({
          fallback: 'style-loader',
          use: [            'css-loader',            'less-loader'
          ]
        }),
        exclude: /node_modules/
      },
      {
        test: /\.(jpg|jpeg|png|svg|gif|bmp)/i,
        use: [          'url-loader?limit=5000&name=img/[name].[sha512:hash:base64:8].[ext]'
        ]
      },
      {
        test: /\.(woff|woff2|ttf|eot)($|\?)/i,
        use: [          'url-loader?limit=5000&name=fonts/[name].[sha512:hash:base64:8].[ext]'
        ]
      }
    ]
  },
  plugins: [    new webpack.optimize.CommonsChunkPlugin({
      name: ['commons', 'vendor'],
      minChunks: 2
    }),    new ExtractTextPlugin('commons.css', {
      allChunks: true
    }),    new HtmlwebpackPlugin({
      template: path.resolve(ROOT_PATH, '../views/entry.html'),
      filename: path.resolve(ROOT_PATH, '../build/entry.html'),
      chunks: ['entry', 'vendor'],
      hash: false
    })
  ]
};

B. 配置到這步後,就能滿足基本開發了。試用之後,這時候對自己提出了幾個問題: 

  1. 命名css,開發的時候能不能不用擔心命名衝突的問題。

  2. css中引入圖片後,編譯失敗問題。

  3. 第三方外掛 載入效果pace元件,引入問題。

  4. 現在檔案過大,有根據路由按需載入需求。 

針對上面4個問題,重新配置:
第2個和3個解決方案一致:即宣告別名

  resolve: {
    extensions: ['.js', '.jsx', '.less', '.css', '.png', '.jpg', '.svg', '.gif', '.eot'],
    alias: {
      pace: path.resolve(ROOT_PATH, '../src/plugins/pace/index.js'),
      ImagesPath: path.resolve(ROOT_PATH, '../src/')
    }
  }

當中第3個問題,網上找了好多資料,都沒有結果,後來請教了前端群的同行,才解決該問題。
解決第1個問題過程中,我學習到了cssModule的概念,一開始菜鳥還不好理解,實踐了後,還真是個好東西。

      {
        test: /\.(css|less)$/,
        use: ExtractTextPlugin.extract({
          fallback: 'style-loader',
          use: [            'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]',            'less-loader'
          ]
        }),
        exclude: /node_modules/
      },

只要css-loader啟動modules就好了。為了支援 react,引入了 react-css-modules 依賴包。

網易雲免費體驗館,0成本體驗20+款雲產品

更多網易研發、產品、運營經驗分享請訪問網易雲社群