React 折騰記 - (5) 記錄用React開發專案過程遇到的問題(Webpack4/React16/antd等)
版權宣告:版權所有:CRPER([email protected]); 掘金|Github:CRPER; https://blog.csdn.net/bomess/article/details/83511171
自己搭的腳手架,坑都是一步一步踩完的;
技術棧: [email protected]
/ react-router-dom@v4
/ webpack^4.23.1(babel7+)
閒話不多說,直入主題,有興趣的可以瞧瞧,沒興趣的止步,節約您的時間.
問題列表
問題一: history
模式下,介面和請求衝突的問題
就是反向對映介面和請求的根路徑重疊,如下:
proxy: { '/': { target: 'http://192.168.31.100/api/web', changeOrigin: true, secure: false, } },
這樣對映會造成路由定址不到…
這個問題我遇到的時候,浪費的挺多時間,最後發現還是有解的;
網上大多數人的寫法就是,加個 prefix
(聚合一個字首),然後用 pathRewrite
重寫請求路徑
proxy: { '/api': { target: 'http://192.168.31.100/api/web', changeOrigin: true, secure: false, pathRewrite: { '^/api': '/' }, } }, historyApiFallback: true
可這法子,不大適合我這邊…能不能重疊又不影響,
翻了一些 Stack Overflow
上的問答和文件,發現還是有的.
下面的寫法就是先判斷是 html
請求還是其他請求,若是請求 html
則不反向代理
proxy: { '/': { target: 'http://192.168.31.100/api/web', changeOrigin: true, secure: false, // pathRewrite: { '^/api': '/' }, bypass: function(req, res, proxyOptions) { if (req.headers.accept.indexOf('html') !== -1) { console.log('Skipping proxy for browser request.'); return '/index.html'; } } } }, historyApiFallback: true
問題二: 如何非 ts
下支援裝飾器 , 以及常規的語法解析
因為用了 mobx
,實在不想用高階函式的寫法…一堆括號…
我是直接配置 babelrc
的. 跟隨最新 babel 7
,裝上這個依賴即可支援
- @babel/plugin-proposal-decorators – 裝飾器支援
- @babel/plugin-syntax-dynamic-import – 動態引入相關程式碼,適用於程式碼分離
- babel/plugin-proposal-object-rest-spread –
...
的支援 - @babel/plugin-proposal-class-properties –
class
支援 - babel-plugin-import – 阿里出品的
css
按需載入 - react-hot-loader/babel – 配置
react-hot-loader
會用到
{ "presets": [ [ "@babel/preset-env", { "targets": { "browsers": [ "last 3 versions", "safari >= 7" ] }, "modules": false, "debug": false, "useBuiltIns": "usage" } ], "@babel/preset-react" ], "plugins": [ [ "@babel/plugin-proposal-decorators", { "legacy": true } ], [ "@babel/plugin-proposal-class-properties", { "loose": true } ], "@babel/plugin-proposal-object-rest-spread", [ "import", { "libraryName": "antd", "libraryDirectory": "es", "style": "css" } ], "@babel/plugin-syntax-dynamic-import", "react-hot-loader/babel" ] }
問題三: mobx
實現路由基礎鑑權
- model
import { observable, action, computed, toJS } from 'mobx'; import API from 'services';// axios的封裝 class AuthModel { constructor() {} // 登入請求 @action requestLogin = async values => { // 登入介面 const data = await API.post('/admin/login', values); const AuthUserData = JSON.stringify(data); window.localStorage.setItem('AuthUserData', AuthUserData); window.location.href = '/'; }; // 退出登入 @action requestLogout = async values => { this.UserData = {}; // 重置為空物件 this.isPermission = false; window.localStorage.removeItem('AuthUserData'); window.location.href = '/entrance/login'; }; @computed get isAuthenticated() { if (window.localStorage.getItem('AuthUserData')) { if (JSON.parse(window.localStorage.getItem('AuthUserData')).token) { return true; } return false; } else { return false; } } } const Auth = new AuthModel(); export default Auth;
- 在對應的入口引入,結合
react-route-dom
的switch
跳轉
import React, { Component } from 'react'; import { hot } from 'react-hot-loader'; import DevTools from 'mobx-react-devtools'; import { BrowserRouter, Route, Switch, Redirect } from 'react-router-dom'; import { observer, inject } from 'mobx-react'; import './App.css'; import ErrorBoundary from 'components/ErrorBoundary/ErrorBoundary'; import asyncComponent from 'components/asyncComponent/asyncComponent'; // 登入註冊找回密碼 const Entrance = asyncComponent(() => import('pages/Entrance/Entrance')); // 管理後臺 import AdminLayout from 'pages/Layout/AdminLayout'; @inject('auth') @observer export class App extends Component { constructor(props) { super(props); } componentDidMount = () => {}; render() { const { isAuthenticated } = this.props.auth; return ( <ErrorBoundary> <BrowserRouter> <div> <Switch> <Route path="/entrance" render={() => isAuthenticated ? ( <Redirect exact to="/" /> ) : ( <Entrance /> ) } /> <Route path="/" render={() => isAuthenticated ? ( <AdminLayout /> ) : ( <Redirect exact to="/entrance" /> ) } /> </Switch> {/**這裡是開啟了開發模式下顯示mobx的devtool*/} {process.env.NODE_ENV === 'development' ? ( <DevTools /> ) : null} </div> </BrowserRouter> </ErrorBoundary> ); } } // react-hot-loader v4的寫法 export default hot(module)(App);
問題四: 加快開發模式下的編譯,以及常規的美化輸出
用了 happypack
來加快了 js
, css
的便以速度(多程序);
給 css
也開啟了 tree shaking
我這個專案沒有引入 less
或 sass
,用 styled-components@4
來寫樣式
- webpack.base.config.js
const webpack = require('webpack'); const path = require('path'); // 終端輸出進度條 const WebpackBar = require('webpackbar'); // html模板 const HtmlWebpackPlugin = require('html-webpack-plugin'); // css 抽離 const MiniCssExtractPlugin = require('mini-css-extract-plugin'); // 多程序編譯 const HappyPack = require('happypack'); const os = require('os'); const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length }); // 給指令碼預新增資訊 const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin'); // css tree shaking const glob = require('glob'); const PurifyCSSPlugin = require('purifycss-webpack'); // 顯示編譯時間 const ProgressBarPlugin = require('progress-bar-webpack-plugin'); const chalk = require('chalk'); const config = { entry: [path.resolve(__dirname, '../src')], resolve: { extensions: ['.js', '.jsx'], modules: [path.resolve(__dirname, '../src'), 'node_modules'], alias: { store: path.resolve(__dirname, '..', 'src/store'), transition: path.resolve(__dirname, '..', 'src/transition'), components: path.resolve(__dirname, '..', 'src/components'), utils: path.resolve(__dirname, '..', 'src/utils'), pages: path.resolve(__dirname, '..', 'src/pages'), views: path.resolve(__dirname, '..', 'src/views'), services: path.resolve(__dirname, '..', 'src/services'), assets: path.resolve(__dirname, '..', 'src/assets'), router: path.resolve(__dirname, '..', 'src/router') } }, performance: { hints: false }, plugins: [ // 顯示打包時間 new ProgressBarPlugin({ format: 'build [:bar] ' + chalk.green.bold(':percent') + ' (:elapsed seconds)' }), // css tree shaking new PurifyCSSPlugin({ // 路勁掃描 nodejs內建 路勁檢查 paths: glob.sync(path.join(__dirname, 'pages/*/*.html')) }), // 進度條 new WebpackBar(), // 定製全域性變數 new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV) }), // 生成引用一個或多個出口檔案的html,需要生成多少個 html 就 new 多少此該外掛 new HtmlWebpackPlugin({ // 沒有引入模板時的預設title,favicon也一樣,但filename除外 title: 'index', favicon: path.resolve(__dirname, '../public/favicon.png'), // 定義插入到文件哪個節點,預設在body倒數位置 inject: 'body', filename: 'index.html', template: path.resolve(__dirname, '../public/index.html'), // 壓縮html檔案 // 詳細的配置 https://github.com/kangax/html-minifier#options-quick-reference minify: process.env.NODE_ENV === 'production' ? { removeComments: true, collapseWhitespace: true, removeRedundantAttributes: true, useShortDoctype: true, removeEmptyAttributes: true, removeStyleLinkTypeAttributes: true, keepClosingSlash: true, minifyJS: true, minifyCSS: true, minifyURLs: true } : {}, // 在js檔案後面加上一段hash碼,預設為false hash: true }), new HappyPack({ id: 'js', threadPool: happyThreadPool, loaders: ['babel-loader?cacheDirectory=true'] }), new HappyPack({ id: 'css', threadPool: happyThreadPool, loaders: [ { loader: 'css-loader', options: { importLoaders: 1 // 0 => 無 loader(預設); 1 => postcss-loader; 2 => postcss-loader, sass-loader } }, 'postcss-loader' ] }), new MiniCssExtractPlugin({ // Options similar to the same options in webpackOptions.output // both options are optional filename: process.env.NODE_ENV !== 'production' ? 'static/css/[name].css' : 'static/css/[name].[hash].css', chunkFilename: process.env.NODE_ENV !== 'production' ? 'static/css/[id].css' : 'static/css/[id].[hash].css' }) ], module: { rules: [ { test: /\.js$/, include: [path.resolve(__dirname, '../src')], exclude: /node_modules/, use: 'happypack/loader?id=js' }, { test: /\.css$/, loaders: [ 'style-loader', // { //loader: 'css-loader', //options: { //importLoaders: 1 // 0 => 無 loader(預設); 1 => postcss-loader; 2 => postcss-loader, sass-loader //} // }, // 'postcss-loader' 'happypack/loader?id=css' ] }, { test: /\.json$/, loader: 'file-loader', options: { name: 'json/[name].[ext]', outputPath: 'static' } }, { test: /\.(jpe?g|png|gif)(\?.*)?$/, include: [path.resolve(__dirname, '../src/assets/')], exclude: /node_modules/, use: [ { loader: 'file-loader', options: { name: '/images/[name].[ext]?[hash]', outputPath: 'static' } } ] }, { test: /\.(eot|svg|ttf|woff|woff2)$/, use: [ { loader: 'file-loader', options: { name: 'fonts/[name].[ext]', outputPath: 'static' } } ] } ] } }; module.exports = config;
- 效果圖如下
問題五: 用新的 getDerivedStateFromProps
取代 componentWillReceiveProps
?
新的寫法是組合寫法,若是隻用這個靜態方法有時候會造成無限迴圈渲染,導致堆疊溢位
一旦用了 static getDerivedStateFromProps(nextProps, prevState)
,必須返回一個值,
若是不更新 state
,那就返回null;
有時候在這裡返回新的 state
不夠嚴謹,這時候就要結合 componentDidUpdate
來變得更可控
componentDidUpdate = (prevProps, prevState, snapshot)
這個生命週期的第三個引數
是用來捕獲更新前的 state
(其實就是 getDerivedStateFromProps
返回的)
antd
上傳元件結合 axios
上傳失敗
這個問題,挺坑的… antd
官方文件說了可以設定 header
,
header
為 form-data
就掛了(預設就是這個提交格式)
最終 axios
裡面還要過濾下,在請求攔截器裡面
// 產生一個基於 axios 的新例項 const api = axios.create({ baseURL: process.env.NODE_ENV === 'development' ? isDev : isProd, // 介面根路徑 timeout: 5000, // 超時時間 withCredentials: true, // 是否跨站點訪問控制請求,攜帶 cookie responseType: 'json', // 響應資料格式 headers: { // 設定請求頭cd 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8' } }); // http請求攔截器 api.interceptors.request.use( config => { // 請求開始,藍色過渡滾動條開始出現 NProgress.start(); if (window.localStorage.getItem('AuthUserData')) { let token = '不給你看 ' + JSON.parse(window.localStorage.getItem('AuthUserData')).token; config.headers.Authorization = token; } if ( config.method === 'post' || config.method === 'put' || config.method === 'patch' ) { //這段就是問答的解決所在,識別為該格式的資料,不用`qs`編碼,直接提交 if (config.headers['Content-Type'] === 'multipart/form-data') { return config; } // 若是需要對介面的欄位進行序列化則可以使用一個迷你庫 // 在最頂部引入`qs`,序列化提交的資料 config.data = qs.stringify(config.data); } return config; }, error => { message.error(error); Promise.reject(error); } );
寫完了整個後臺管理系統,發現 mobx
並沒有想象中的好用;
看到阿里的 umi
已經 2.x
了(應該挺穩定了),準備用這個 umi+dva
重寫整個專案.
不對之處,請留言,會及時修正,謝謝閱讀