寫 React 已經有一段時間了,從開始到現在都是一路摸爬滾打過來的,踩過不少坑。雖然學習 React 已經很久了,但是和真正寫還是有很大區別的,今天就從頭開始記錄下使用過程的點點滴滴,權且當作學習筆記吧,也希望能給將要入坑的小伙伴一點點啟示吧!
前言
習慣了使用Vue
開發的小伙伴,如果突然切換到React
技術棧,剛開始肯定會有些手足無措,因為相比Vue
的腳手架Vue-cli
,React
官方提供的腳手架工具create-react-app
則顯得沒有那么友好。在Vue-cli
中,如果想要擴展webpack
配置,只需要在項目的根目錄新建一個vue.config.js
文件,在該文件中進行配置即可。但是create-react-app
則沒有那么簡單,它把所有的webpack
配置都集成在了react-scripts
的依賴包中,我們無法直接修改的,官方提供了一個npm run eject
命令可以執行腳本將react-scripts
配置釋放到本地,來讓我們進行配置,但這種方式也會有缺陷,具體后面會詳細說明,同時,社區也提供了一種方案,可以使用react-app-rewired
包來重寫webpack
配置。接下來我們就詳細來說明這兩種方式,幫你一舉掃清環境配置的各種障礙。
使用create-react-app
初始化項目
- 創建項目
npx create-react-app react-demo
- 啟動項目
cd react-demo
npm run start
至此,一個簡單的react
項目就創建和啟動完畢了。
扒一扒create-react-app
背后的東西
通過項目創建大概有小伙伴會說這比vue-cli
創建vue
項目簡單,但是兩個命令背后發生了什么應該也會有疑問,接下來我們就一點點扒開它的真實面目。
兩個疑問
1、為什么使用npx
創建項目?
在使用create-react-app
創建項目時,通常是使用如下命令:
// 全局安裝create-react-app
npm install create-react-app -g
// 使用create-react-app創建項目
create-react-app react-demo
但是我們這里為什么使用了 npx 就可以直接創建項目呢 ?
那就要從認識npx
說起了。
npx
是npm
提供的一個命令,它可以不用全局安裝包文件,就可以執行命令,在使用npx
時它會先從下載一個臨時包文件,等使用完成之后就會自動刪除,以避免額外的占用磁盤空間。
2、執行npm run start
后發生了什么?
相比很多的小伙伴大概也會跟我一樣,對于執行一個命令就啟動起來項目了,這背后到底發生了什么很好奇?那么現在我們就來一起一探究竟。
首先,查看項目根目錄的package.json
文件:
{
"name": "react-demo",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-scripts": "4.0.1",
"web-vitals": "^0.2.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": ["react-app", "react-app/jest"]
},
"browserslist": {
"production": [">0.2%", "not dead", "not op_mini all"],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
查看package.json
中的scripts
配置,我們知道了,執行npm run start
命令其實是執行了react-scripts start
,那么react-scripts
又是何方圣神呢?
其實npm
執行的命令默認都會去項目的node_modules
下面.bin
目錄下找可執行腳本。
其中react-scripts.cmd
是windows
系統下的可執行命令,react-script
是linux
環境下的命令。扒開文件內容我們可以看到:
@IF EXIST "%~dp0\node.exe" (
"%~dp0\node.exe" "%~dp0\..\react-scripts\bin\react-scripts.js" %*
) ELSE (
@SETLOCAL
@SET PATHEXT=%PATHEXT:;.JS;=;%
node "%~dp0\..\react-scripts\bin\react-scripts.js" %*
)
通過上面命令我們就知道了腳手架的webpack
配置入口文件是react-scripts
包中bin
目錄下的react-scripts.js
文件。
然后在入口文件中又根據執行命令(參數)不同加載scripts
目錄下不同的文件。
由入口文件可知,如果執行的是npm run start
就會執行scripts/start.js
文件。
在start.js
文件中引入了config
目錄下webpack.config.js
文件,那么這個文件就是項目腳手架中核心的webpack
配置文件了。
扒到這里,大概大家就明白了,服務是如何啟動的了,以及腳手架背后的webpack
是如何配置的了。
那么,接下來我們就來說一說在日常項目中我們通常都怎樣在腳手架的基礎上自定義webpack
配置的。
自定義webpack
配置
通常,腳手架生成的默認配置是很難滿足我們項目需要的,因此自定義配置項目的webpack
是我們無法避免的。接下來我就介紹兩種自定義webpack
的方式。以及日常開發中常用到的配置。
一、使用eject
,釋放默認webpack
配置
在package.json
文件的scripts
命令中,有一個npm run eject
的命令,這個是create-react-app
官方提供了一個可以釋放默認webpack
的命令。
執行npm run eject
命令會將react-scripts
釋放到本地項目中,可以通過修改對應的文件完成配置。同時會刪除react-scripts
依賴包,修改package.json
中命令。
命令執行完畢以后,我們就會看到本地項目中多出了scripts
和config
兩個目錄文件。同時,package.json
文件命令也被修改了。
"scripts": {
"start": "node scripts/start.js",
"build": "node scripts/build.js",
"test": "node scripts/test.js"
}
此時,如果在執行啟動或者打包命令,就是直接執行的scripts
目錄下對應的文件了。我們可以很方便的根據需要修改對應的文件配置即可。
但值得注意的是,npm run eject
命令是不可逆的,即執行之后不可恢復,這就造成了如果后續我們想通過腳手架的react-scripts
包增加新的特性,比如PWA
支持,是不可行的,因此,請根據需要謹慎釋放。
說完如何釋放腳手架的webpack
配置,接下來我們就來說一下如何通過釋放默認配置的方式進行一些常用的自定義配置。
添加less
支持
- 安裝
less
和less-loader
npm install less less-loader
- 在
config/webpack.config.js
文件中做如下配置:
1、添加匹配.less
和.module.less
結尾的文件的正則
2、在module
的rules
中配置less-loader
規則
至此,對less
文件的支持已經配置好了,在項目中創建less
樣式文件并引入,就可以生效了。
-
拓展
看了上面的配置,可能有些人會有疑問:
為什么要在配置中匹配
.module.less
文件,并進行單獨的解析?這是因為在
create-react-app
創建的項目中,組件中的樣式沒有作用域隔離的概念,不像是vue
中給style
添加scoped
屬性,樣式文件只針對當前組件有效,不會影響到子組件。所以在react
項目中如果不進行處理很容易造成全局樣式污染的問題。針對此,通常有兩種解決方案:
1、每個組件在最外層標簽上都定義一個唯一的類名,所有的樣式寫在這個類下面。但是該方式當項目大了,參入人員多了就很難把控。
2、定義組件樣式文件為
組件名.module.less
的形式,并以模塊的方式引入。
style.module.png
解析到頁面的時候就會給對應標簽添加一個唯一標志的類名,這樣就可以有效的避免樣式污染問題。
按需引入 Antd 組件
antd
組件是使用React
技術棧使用最為普遍的 UI 組件庫,如果使用css
書寫樣式,那么按照官方文檔配置使用即可:
但是,如果我們使用less
作為樣式書寫方式,使用上面的方式,antd
的樣式文件會不生效,如果將在App.less
頂部引入的antd
樣式文件改為antd.less
則會報錯,因為在antd
的dist
目錄下根本就找不到antd.less
文件。
那么,我們就需要借助babel-plugin-import
這個加插件了,使用這個插件在按需引入組件的時候會自動引入.js
和.css|.less
文件。
接下來,就需要安裝和配置babel-plugin-import
了
- 安裝
npm install babel-plugin-import
- 配置
在package.json
文件中配置babel
:
"babel": {
"presets": [
"react-app"
],
"plugins": [["import",{
"libraryName": "antd",
"libraryDirectory": "es",
"style": true
}]]
}
或者在根目錄創建.babelrl
文件,配置:
// .babelrl
{
"babel": {
"presets": [
"react-app"
],
"plugins": [
["import", {
"libraryName": "antd",
"libraryDirectory": "es",
// css
// "style": "css"
//less
"style": true
}]
]
}
以上兩種配置babel
的方式取一種即可。
另外需要提及一下的是,以上配置style
取值不同,加載的樣式文件類型也不一樣。
-
踩坑
配置
style: true
時,如果使用less
和less-loader
較高版本時會報錯,在一番嘗試后,將版本降級即可。我這里使用如下版本:
"less": "2.7.3",
"less-loader": "5.0.0",
當然,經過探索,使用高版本的less-loader
也是可以的,但是less
的版本要匹配,我這里使用了less-loader
最新版本7.1.0
,通過查看less-loader
配置文件,發現依賴的less
版本是3.5.0
,因此我將less
版本降為了對應版本??晒膺@樣還是不夠,還需要對webpack
的less-loader
進行對應的配置。
自定義 Antd 主題
搞定上面的less-loader
配置以后,自定義主題就非常容易了。antd
組件樣式是使用less
開發的,我們只需要使用less
提供的 modifyVars
的方式進行覆蓋變量即可。
配置跨域
跨域是前后端分離的開發方式繞不過去的問題,因此也是我們進行項目開發時首先要考慮的,React
開發的跨域配置有別于Vue
,下面我們就來詳細的看看。
通過閱讀create-react-app
官方文檔可知,有兩種方式可以配置跨域:
1、在package.json
中,proxy
字段進行配置:
"proxy": "http://192.168.0.0/api"
這時,如果訪問后端接口本地的localhost
就會被代理到http://192.168.0.0/api
上。
但是,這種方式存在一個缺陷,如果你想同時訪問多臺服務器這種方式就沒辦法滿足了,類似如下配置:
// 偽代碼
"proxy": {
"/api": {
"target": "http://192.168.0.0",
},
"/abc": {
"target": "http://192.168.0.1",
}
}
此時,啟動就會報錯,提示proxy
字段只接收字符串,不接受對象。
2、針對第一種的缺陷,官方比較推薦使用 http-proxy-middleware
插件的方式
具體使用如下:
- 安裝
http-proxy-middleware
yarn add http-proxy-middleware
- 在項目的
src
目錄下創建setupProxy.js
,并進行配置
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function (app) {
app.use(
'/api',
createProxyMiddleware({
target: 'http://172.16.0.0:8082',
changeOrigin: true,
})
),
app.use(
'/abc',
createProxyMiddleware({
target: 'http://172.16.0.1:8082',
changeOrigin: true,
})
),
}
然后重啟項目,就可以了。
設置路徑別名
在config
目錄下找到webpack.config.js
文件,做如下配置即可:
關閉生產環境 sourcemap
通常在開發的時候,我們會配置開發環境生成 sourcemap 文件,方便調試,但是在生產環境都要關閉,避免代碼泄露和打包多余文件,體積太大。通過查看config/webpack.config.js
文件的devtool
配置如下:
因此,可以通過cross-env
插件在打包命令中添加全局變量。
- 安裝
cross-env
yarn add cross-env
- 在打包命令中配置
"scripts": {
"build": "cross-env GENERATE_SOURCEMAP=false node scripts/build.js",
},
打包開啟 gzip 壓縮
前端代碼打包開啟gzip
壓縮是前端性能優化重要的策略之一,因此這里也說下eject
后如何配置打包開啟gzip
壓縮。開啟打包代碼壓縮需要使用到插件compression-webpack-plugin
。
- 安裝
yarn add compression-webpack-plugin
- 配置
在config/webpack.config.js
文件下做如下配置:
const CompressionPlugin = require("compression-webpack-plugin");
// 在命令中添加一個GENERATE_GZIP環境變量控制是否開啟打包壓縮
const isGzip = process.env.GENERATE_GZIP === "true";
plugins: [
// ...
isEnvProduction &&
isGzip &&
new CompressionPlugin({
// 匹配開啟gzip壓縮的文件
test: /\.(js|css|html|png|jpg)$/,
// 壓縮后靜態資源文件名
filename: "[path][base].gz",
// 使用的壓縮算法
algorithm: "gzip",
// 只處理10M以上的資源壓縮
threshold: 10240,
// 只處理壓縮率小于0.8的資源
minRatio: 0.8,
// 壓縮后是否刪除源文件
deleteOriginalAssets: true,
}),
];
- 在命令腳本設置是否開啟壓縮打包變量
"scripts": {
"test": "cross-env GENERATE_GZIP=true node scripts/build.js",
"prod": "cross-env GENERATE_GZIP=true node scripts/build.js"
},
配置全局環境變量
在create-react-app
官方文檔中提供了通過.env
文件來配置全局環境變量的方式,但是只提供了開發環境和生產環境兩種,但是我們日常開發中,必定會有測試環境,甚至是體驗環境,因此僅僅使用官方提供的無法滿足我們的需求,因此需要做些小的修改。
需求:
打包后的文件名通過.env.xx
環境變量REACT_APP_MODE
來決定。
步驟:
- 在項目根目錄創建三個文件:
.env
、.env.test
、.env.prod
。內容如下:
// .env 開發環境環境變量
NODE_ENV = development;
REACT_APP_MODE = dev;
// .env.test 測試環境環境變量
NODE_ENV = production;
REACT_APP_MODE = test;
// .env.prod 生產環境環境變量
NODE_ENV = production;
REACT_APP_MODE = prod;
- 修改
package.json
文件命令腳本
"scripts": {
"start": "node scripts/start.js",
"test": "cross-env MODE_ENV=test node scripts/build.js",
"prod": "cross-env MODE_ENV=prod node scripts/build.js"
},
-
修改 config/env.js 文件
mode_env.png
- 修改
config/paths.js
文件
fileName.png
幾點說明
- 在命令腳本設置環境變量使用了
cross-env
插件,因此需要先安裝。 - 在命令腳本加入
MODE_ENV
環境變量是為了根據命令加載不同的.env.xx
環境變量文件。 -
.env.xx
配置的環境變量必須以REACT_APP_xx
開頭。 - 在
.env.xx
配置的環境變量在全局范圍內通過process.env.變量名
獲取到。
二、不使用eject
,重寫項目默認webpack
配置
相比于eject
釋放配置后,需要進行復雜的webpack
配置不同,不使用eject
則簡單很多,添加less支持
、Antd組件按需引入
、自定義主題
、跨域配置
、設置路徑別名
、打包開啟 gzip 壓縮
、生產環境關閉 sourceMap
等都可以通過插件react-app-rewired、customize-cra
在一個配置文件內完成,同時還支持自定義擴展,根據需求配置(添加或修改)webpack
的plugins
和loader
。具體操作如下:
安裝依賴
yarn add react-app-rewired customize-cra babel-plugin-import less less-loader antd
修改package.json
命令配置
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build"
}
在項目根目錄創建config-overrides.js
文件,并進行如下配置:
const {
override,
overrideDevServer,
fixBabelImports,
addLessLoader,
addWebpackAlias,
} = require("customize-cra");
const path = require("path");
const CompressionPlugin = require("compression-webpack-plugin");
const devServerConfig = () => (config) => {
return {
...config,
// 跨域配置
proxy: {
"/api": {
target: "http://172.16.15.50:8082/api",
secure: false,
changeOrigin: true,
pathRewrite: {
"^/api": "",
},
},
"/abc": {
target: "http://172.16.0.1:9003",
secure: false,
changeOrigin: true,
pathRewrite: {
"^/abc": "",
},
},
},
};
};
// 自定義插件配置
const selfPlugins = [
new CompressionPlugin({
// 匹配開啟gzip壓縮的文件
test: /\.(js|css|html|png|jpg)$/,
// 壓縮后靜態資源文件名
filename: "[path][base].gz",
// 使用的壓縮算法
algorithm: "gzip",
// 只處理10M以上的資源壓縮
threshold: 10240,
// 只處理壓縮率小于0.8的資源
minRatio: 0.8,
// 壓縮后是否刪除源文件
deleteOriginalAssets: true,
}),
// 更多自定義插件配置.....
];
module.exports = {
// webpack配置
webpack: override(
// 按需引入antd組件
fixBabelImports("import", {
libraryName: "antd",
libraryDirectory: "es",
style: true,
}),
// 添加less支持和自定義主題
addLessLoader({
lessOptions: {
modifyVars: { "@primary-color": "#1DA57A" },
javascriptEnabled: true,
},
}),
// 設置路徑別名
addWebpackAlias({
"@": path.resolve(__dirname, "./src"),
}),
/**暴露webpack的配置
* 當插件內置方法不足以滿足我們的要求時,可以接收一個函數來暴露webpack所有配置,
* 進行添加或修改loader,plugins,如此就可以滿足所有的需求了
*/
(config) => {
console.log("config", config);
// 開發環境開啟source map,生產環境去掉map文件
config.devtool =
config.mode === "development" ? "cheap-module-source-map" : false;
/**
* 添加或修改plugins配置
*/
if (process.env.NODE_ENV === "production") {
// 生產環境開啟gzip壓縮
config.plugins = [...config.plugins, ...selfPlugins];
}
/**
* 添加和修改loader配置
*/
// 獲取所有的loader信息:
let loaders = config.module.rules.find((rule) =>
Array.isArray(rule.oneOf)
).oneOf;
// 獲取到所有的loader之后,根據需求添加或修改具體的某一項loader.....
return config;
}
),
// 本地服務devServer配置
devServer: overrideDevServer(devServerConfig()),
};
幾點說明:
- 添加
less
支持會根據less
和less-loader
版本不同會有所差異,我這里使用的版本是:"less": "3.5.0"
、"less-loader": "^7.1.0"
。 - 這種配置
less
支持的方式,不管是直接使用.less
文件書寫樣式,還是通過.module.less
文件的方式都可以支持。 - 跨域代理配置這并不是唯一的方法,也可以使用前面介紹到的
http-proxy-middleware
插件。 - 使用
compression-webpack-plugin
開啟gzip
壓縮時,如果使用最新版本打包時會報錯,版本降為6.0.0
后正常。
gzip-error.png
- 這種方式,除了使用插件內置的方法配置外,還支持自定義擴展配置,通過暴露
webpack
的配置來完成自定義添加或修改plugins
和loader
,這樣就可以最大限度來滿足項目的多樣化需求了。
環境變量配置差異
相較于釋放webpack
配置配置環境變量,重寫webpack
配置設置環境變量則有所不同,我們無法在config-overrides.js
中配置根據環境引入不同的.env.xx
文件。因此只能借助工具來完成,通過dotenv
插件我們可以在命令腳本配置引入哪個.env.xx
文件。我們用同樣的需求演示。
需求:
打包后的文件名通過.env.xx
環境變量REACT_APP_MODE
來決定。
步驟:
- 安裝
dotenv-cli
yarn add dotenv-cli
- 在項目根目錄下創建
.env
、.env.test
、.env.prod
文件,并進行如下配置:
// .env 開發環境環境變量
REACT_APP_MODE = dev;
// .env.test 測試環境環境變量
REACT_APP_MODE = test;
// .env.prod 生產環境環境變量
REACT_APP_MODE = prod;
- 修改
package.json
文件命令腳本
"scripts": {
"start": "react-app-rewired start",
"test": "dotenv -e .env.test react-app-rewired build",
"prod": "dotenv -e .env.prod react-app-rewired build"
}
- 在
config-overrides.js
中修改默認打包后的文件名
override_path.png
完整配置文件
const {
override,
overrideDevServer,
fixBabelImports,
addLessLoader,
addWebpackAlias,
} = require("customize-cra");
const path = require("path");
const CompressionPlugin = require("compression-webpack-plugin");
const devServerConfig = () => (config) => {
return {
...config,
// 跨域配置
proxy: {
"/api": {
target: "http://172.16.15.50:8082/api",
secure: false,
changeOrigin: true,
pathRewrite: {
"^/api": "",
},
},
"/abc": {
target: "http://172.16.0.1:9003",
secure: false,
changeOrigin: true,
pathRewrite: {
"^/abc": "",
},
},
},
};
};
// 自定義插件配置
const selfPlugins = [
new CompressionPlugin({
// 匹配開啟gzip壓縮的文件
test: /\.(js|css|html|png|jpg)$/,
// 壓縮后靜態資源文件名
filename: "[path][base].gz",
// 使用的壓縮算法
algorithm: "gzip",
// 只處理10M以上的資源壓縮
threshold: 10240,
// 只處理壓縮率小于0.8的資源
minRatio: 0.8,
// 壓縮后是否刪除源文件
deleteOriginalAssets: true,
}),
// 更多自定義插件配置.....
];
module.exports = {
/**
* 路徑信息配置
* 要修改什么路徑,打印出來paths,更改對應的位置即可
*/
paths: function (paths) {
// 打包后的文件名
paths.appBuild = path.join(__dirname, `./${process.env.REACT_APP_MODE}`);
console.log(paths);
return paths;
},
// webpack配置
webpack: override(
// 按需引入antd組件
fixBabelImports("import", {
libraryName: "antd",
libraryDirectory: "es",
style: true,
}),
// 添加less支持和自定義主題
addLessLoader({
lessOptions: {
modifyVars: { "@primary-color": "#1DA57A" },
javascriptEnabled: true,
},
}),
// 設置路徑別名
addWebpackAlias({
"@": path.resolve(__dirname, "./src"),
}),
/**暴露webpack的配置
* 當插件內置方法不足以滿足我們的要求時,可以接收一個函數來暴露webpack所有配置,
* 進行添加或修改loader,plugins,如此就可以滿足所有的需求了
*/
(config) => {
// console.log('config', config)
// 開發環境開啟source map,生產環境去掉map文件
config.devtool =
config.mode === "development" ? "cheap-module-source-map" : false;
/**
* 添加或修改plugins配置
*/
if (process.env.NODE_ENV === "production") {
// 生產環境開啟gzip壓縮
config.plugins = [...config.plugins, ...selfPlugins];
}
/**
* 添加和修改loader配置
*/
// 獲取所有的loader信息,
let loaders = config.module.rules.find((rule) =>
Array.isArray(rule.oneOf)
).oneOf;
// 獲取到所有的loader之后,根據需求添加或修改具體的某一項loader.....
return config;
}
),
// 本地服務devServer配置
devServer: overrideDevServer(devServerConfig()),
};
至此,項目開發中的常用配置就已經分享完了,搭建好環境后就可以愉快的擼碼了。
碼字不易,且行且珍惜!?。?!