前言
自己搭的腳手架,坑都是一步一步踩完的;
技術棧: react@16.6.0
/ 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);
}
);
複製程式碼
問題七: Antd
及moment預設全域性中文注入
import React, {
StrictMode,
unstable_ConcurrentMode as ConcurrentMode
} from 'react';
import ReactDOM from 'react-dom';
import App from 'App';
// mobx 注入
import { Provider } from 'mobx-react'; // react mobx的耦合器
import RootStore from 'store/store';
// 全域性中文
import { LocaleProvider } from 'antd';
import zh_CN from 'antd/lib/locale-provider/zh_CN';
import 'moment/locale/zh-cn';
ReactDOM.render(
<Provider {...RootStore}>
<LocaleProvider locale={zh_CN}>
<App />
</LocaleProvider>
</Provider>,
document.getElementById('root')
);
複製程式碼
總結
寫完了整個後臺管理系統,發現mobx
並沒有想象中的好用;
看到阿里的umi
已經2.x
了(應該挺穩定了),準備用這個umi+dva
重寫整個專案.
不對之處,請留言,會及時修正,謝謝閱讀