如果此篇對您有所幫助,在此求一個star。項目地址: OrcasTeam/my-cli
react
react介紹
目前,國內主流的前端應用框架具有兩個:vue.js和react.js,關於vue和react的優劣性,網上眾說紛紜。在下就不在此引戰。
而是直接介紹React
🐋🐋🐋 vue和React這種都是快速應用開發工具,可能也會像曾經如日中天的JQuery被市場淘汰,所以個人建議不要盲目只追求快速工具的使用,而是花時間去學習原點。例如設計思想和數據結構。快速應用框架(或語言)只不過是應用工具而已。
🐋 以前都說是“三大框架”,還有一個Google開發的Angular,但是國內Angular使用份額越來越少。
個人感覺Angular主要問題是上手成本。Angular比較偏向於后端,很多概念對於前端開發人員都是噩夢。不過對於前端工程化,個人認為Angular是集大成之作。個人建議,對於有經驗的朋友,可以稍微學習下Angular中的思想。
React是一個用於構建用戶界面的 JavaScript 庫,
React本身是一個特別簡單的庫:將元素抽象為虛擬DOM,更新DOM時對比虛擬DOM,然后只更新那些真正需要更新的元素。
React.createElement()
使用Document構建DOM時,都是使用 document.createElement() 來構建標簽
const li = document.createElement('li');
document.body.appendChild(li)
在React中, 也提供了這樣一個自定義函數來React組件。
React.createElement() 返回的是一個React自定義的元素類型:ReactElement
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
React提供的React.createElement()和ReactElement提供了很好平台隔離性。
使用同一套代碼編寫的元素組件只需要對接不同平台的APi,就可以實現跨平台。
React能夠跨平台的原因也在於此。
在日常開發中,也會經常寫無關業務的通用封裝,其思想與此類似。
虛擬DOM
在直接使用Document更新DOM元素時,很多時候會因為某些原因 對不必更新DOM進行更新 從而產生了性能浪費
解決這個問題一般想到的做法就是做一個DOM緩存。創建DOM時將DOM信息緩存,更新時對比新舊DOM。排除掉不必要的更新DOM。
這種緩存DOM數據的方案就叫虛擬DOM(Virtual DOM), 而排除算法叫做diff算法
React也使用了這種方案提升性能
虛擬DOM(Virtual DOM)和diff算法 是對數據結構和算法的考驗。每一個人都可以模擬出簡單的方案,但不是每一個人都可以寫出優秀的解決方案。
在下愚鈍,對於數據結構和算法掌握的不好。所以對虛擬DOM(Virtual DOM)和diff算法只有淺薄的認知。有興趣的朋友可以看一下這篇文章:深度剖析:如何實現一個 Virtual DOM 算法
JSX
React是通過JS構建元素的,
我們都知道使用JS編寫頁面痛苦是沒有結構性。
使用HTML兩個標簽能搞定的事,使用JS就能寫一大堆代碼。
React為了解決這個問題,提供了一個模板語言---JSX
JSX是一種JS擴展語言。允許在JS中以標簽形式構建元素。並且JSX開發工具中還可以具有各種提示和快捷鍵。
能夠極大的提高開發效率
const element = (
<h1 className="greeting">
Hello, world!
</h1>
);
但JSX編寫的組件只是React.createElement()語法糖,打包編譯過程中會將JSX語法轉換為React.createElement()
🐋🐋🐋 JSX編寫的組件本質是 React.createElement() 語法糖。所以React還支持使用 React.createElement() 創建虛擬DOM(Virtual DOM)。
🐋🐋 JSX是React提供構建代碼方式的一種擴展語言,本質是一個語法糖。JSX定義的事件、style、class是JSX自身語法,並不是原生DOM。所以有些屬性名稱不一致。
🐋🐋 JSX轉換React.createElement()操作使用的是babel提供的一個plugin,在下面再介紹
🐋 JSX目前被社區認可。Vue@3.X也支持JSX
添加 React
安裝 react
React目前最新版本為17.0.1,在這里就直接引用此版本來介紹,對React有興趣的朋友在從老版本循循漸進的學習。
yarn add react@17.0.1
react庫是React的核心庫,具有 React.createElement() 、虛擬DOM、JSX語法支持等一系列核心內容。
但是此庫並不沒有提供與真實DOM交互。與真實DOM交互的代碼則由react-dom提供,
yarn add react-dom@17.0.1
react類似一個通用庫,沒有與任何平台具有相關性,只負責組織數據結構。
就像寫React Native時,使用了react-native來做平台交互。
使用 react
接下來就仿照react-cli來組織代碼。
根節點
第一步就是在HTML頁面中創建一個元素作為React承載的根節點。

🐋 vue-cli也具有這么一個根節點用來承載vue,只不過元素ID名稱不一樣,有興趣的朋友可以自行查看。
接下來處理JS,在之前打包測試中都是使用 /src/index.js 文件作為源文件。
也是使用此文件作為源文件。
🐋🐋 React只是承載在打包器中的一個應用框架。經過打包器打包將JSX轉換為可運行的代碼。
import React from 'react';
import ReactDOM from 'react-dom';
const root = (
<h1 className="greeting">
Hello, world!
</h1>
);
ReactDOM.render(root, document.getElementById('root'));
在 /src/index.js 文件中使用了JSX創建元素,然后使用
react-dom中的 ReactDOM.render() 添加到根節點中。
🐋 vue-cli也同樣如此,有興趣的朋友可以自行查看
@babel/preset-react
不過如果此時執行yarn build
操作,會直接報錯。

這是因為JSX無法被識別的問題。前面說過,JSX只是React提供的一種模板語言。本質上並不屬於JS模塊。
所以需要將JSX轉換為 React.createElement() 形式
提供這個轉換操作的是babel中提供的一個plugin:@Babel/plugin-syntax-jsx
不過不需要直接安裝這個plugin
babel為React提供了一個preset:@babel/preset-react。
@babel/preset-react中封裝了所有處理React的plugin

yarn add -D @babel/preset-react@7.12.13
🐋 Babel官網提供了JSX轉換為 React.createElement() 的測試,有興趣的朋友可以測試測試
然后配置在 .babelrc 文件中

此時執行yarn build
便可以執行成功,並且查看生成代碼可以看到JSX已經轉換為了React.createElement()

在瀏覽器也可以正常運行代碼

.jsx文件
app.jsx
React代碼已經運行成功,接下來就組織React代碼。
剛才,直接在 /src/index.js 文件中編寫了JSX代碼進行測試
但是真正開發中,需要將JSX代碼編寫在 .jsx 文件中,通過模塊導入導入方式提供給 /src/index.js 文件。
將JSX提取到 /src/app.jsx 文件,在 /src/index.js 導入。


🐋🐋 app.jsx作為React框架的根節點。用在承載React組件。
/src/app.jsx 文件中組件作為React的根節點。React也是以樹的組織方式管理,/src/app.jsx 文件中組件就是樹根。React框架代碼就像 托管 在了 /src/app.jsx 之中
🐋 🐋
- React組件分為 函數組件 和 類組件 , 函數組件 方便,再加上 Hooks 的助力,在編寫顆粒度較小組件時使用 函數組件 是個非常好的選擇。類組件 封裝性強,內部提供完善的鈎子函數和一系列功能,再加上繼承特性。比較適合使用在業務代碼主干中。
- /src/app.jsx 中返回的 <></> 代表 空標簽 ,React組件只允許返回一個元素,但有時候組件需要返回元素數組,可以在外部包一層空標簽。與Vue中的template標簽功能一致。
- React 組件名稱約定為大寫形式。
webpack配置
.jsx作為一種新的文件格式,需要在webpack進行配置使用babel
const modules = {
module:{
rules:[
{
// 所有的.js或者.jsx文件都走babel-loader
test: /\.js(x?)$/,
include: path.join(config.root,'src'),
loader: "babel-loader"
}
]
}
}
並且可以提供引用時忽略后綴名稱。
resolve:{
// 可被忽略的后綴
extensions:['.jsx', '.js', '.json'],
}
此時就算成功將React使用在腳手架中了。
而對於React Router、Redux只是用於擴展React的開發庫。在此就不再添加。
🐋 vue-cli搭建方式與react-cli基本一致,只是各自框架暴露的API不同
browserslist
browserslist是什么
在介紹babel時使用過package.json文件中browserslist屬性設置瀏覽器版本,那么browserslist屬性到底是怎么回事呢?
前面介紹過,前端的運行環境(瀏覽器)版本是由用戶決定的,不同的項目對於瀏覽器版本要求不一樣。
而在打包過程中。需要指定支持的瀏覽器版本,以這些版本對開發代碼做出適配。(CSS、JS都需要適配)。
browserslist屬性就是提供指定瀏覽器版本功能。是由browserslist庫提供的。
而這個簡單的功能browserslist卻做出了強大的效果,得到了社區的高度認可。很多庫都直接依賴browserslist
browserslist配置方式
browserslist提供了兩種配置方式。
一種就是配置在package.json文件中的browserslist屬性。browserslist執行時會默認讀取此屬性。

另一種是使用約定文件。可以在項目根目錄(package.json所在目錄)創建一個約定文件 .browserslistrc.json ,將屬性配置在此。.browserslistrc.json文件名稱一般會省略后綴:.browserslistrc

兩種方式不可同時設置,否則會直接報錯。

個人推薦直接配置在package.json文件中,沒必要創建一個文件了。在此也就直接使用此方案。
browserslist環境變量
browserslist可以使用不用屬性來靈活的控制瀏覽器版本。
如下所示。可以設置在不同環境下設置不同瀏覽器版本。
"browserslist": {
"development": [
"chrome > 75"
],
"production": [
"ie 9"
]
}
屬性值取自Node.js中環境變量。環境變量名稱為BROWSERSLIST_ENV。所以需要設置環境變量。

注意:在此雖然設置在webpack.config.js文件中,但設置的是Node.js中的環境變量, 並不是webpack提供的環境變量。
browserslist屬性值名稱可以隨意命名。只要與Node.js中BROWSERSLIST_ENV環境變量對應即可。
在此就不貼圖測試了,有興趣的朋友可以自行測試。
至於BROWSERSLIST_ENV 環境變量與 webpack中不同模式的關聯,在下一篇介紹。
browserslist支持的瀏覽器
browserslist支持設置當前基本上所有的瀏覽器,在Github上作者說明了可以設置的瀏覽器

可以看到,browserslist幾乎支持所有瀏覽器:PC、安卓、IOS 甚至還有國內瀏覽器。
🐋🐋 設置瀏覽器時名稱不區分大小寫
browserslist屬性
browserslist能得到社區的認可,也就在於browserslist提供了強大的屬性設置。
如前面使用的 指定 區間瀏覽器(chrome > 75) 也只是browserslist簡單的屬性配置
下面簡單列舉部分browserslist屬性配置,想了解更多的朋友請參考Github
-
defaults:browserslist設置的默認瀏覽器版本。屬性只相當 > 0.5%, last 2 versions, Firefox ESR, not dead
-
指定版本號: 支持直接指定某個瀏覽器版本號。
IE 11:設置IE11瀏覽器
-
范圍版本:支持設置某個瀏覽器指定范圍版本。
Chrome > 75: 設置大於Chrome75版本的瀏覽器
並且支持
>=
、<
、<=
語法設置 -
24個月內未更新版本:支持設置24個月內未更新的版本
dead
-
瀏覽器使用率:支持設置指定瀏覽器使用率版本
>5%:全球超過5%人使用的瀏覽器版本
> 5% in US:美國超過5%使用的瀏覽器版本
> 5% in alt-AS:亞洲超過5%使用的瀏覽器版本
也自定義設置地區,具體參考Github文檔
並且支持
>=
、<
、<=
語法設置 -
最新瀏覽器版本:支持設置最新的幾個版本瀏覽器。
last 2 versions:設置所有瀏覽器最新的兩個版本。
last 2 Chrome versions:設置Chrome瀏覽器最新的兩個版本
-
排除瀏覽器:browserslist支持排除指定瀏覽器,
not ie < 11:排除IE11以下的瀏覽器
-
條件組合:browserslist強大的功能之一是支持多個條件做一個,這也是browserslist靈活所在。
例如
"browserslist": [ "ie 9", "Chrome > 75" ],
這就是一個並且(and)組合設置。兩者都必須滿足
browserslist同樣支持 或者(or)組合:> .5% or last 2 versions
一般只需要簡單的設置即可。
總結
🐋🐋🐋
React是一個快速構建高性能網站的開發框架
React使用了虛擬DOM(Virtual DOM)和diff 算法優化了DOM操作
React利用自定義DOM類型解耦平台限制,以此實現了跨平台
JSX只是一個JS擴展語法。React使用JSX作為構建元素的模板語言
browserslist是一個強大的設置瀏覽器版本庫。
本文參考
本文依賴
package.json
{
"name": "my-cli",
"version": "1.0.0",
"main": "index.js",
"author": "mowenjinzhao<yanzhangshuai@126.com>",
"license": "MIT",
"devDependencies": {
"@babel/core": "7.13.1",
"@babel/plugin-transform-runtime": "7.13.7",
"@babel/preset-env": "7.13.5",
"@babel/preset-react": "7.12.13",
"@babel/runtime-corejs3": "7.13.7",
"babel-loader": "8.2.2",
"clean-webpack-plugin": "3.0.0",
"html-webpack-plugin": "5.2.0",
"webpack": "5.24.0",
"webpack-cli": "4.5.0"
},
"dependencies": {
"jquery": "3.5.1",
"react": "17.0.1",
"react-dom": "17.0.1"
},
"scripts": {
"start": "webpack --mode=development --config webpack.config.js",
"build": "webpack --mode=production --config webpack.config.js"
},
"browserslist": [
"ie 9",
"Chrome > 75"
]
}
webpack.config.js
const path = require('path')
const webpack = require("webpack");
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')
// browserslist環境變量
process.env.BROWSERSLIST_ENV = 'development'
const config = {
root: path.join(__dirname, './'),
}
const modules = {
// 入口文件
// 字符串形式
entry: path.join(config.root, 'src/index.js'),
// 對象形式
// entry:{
// 'index': path.join(config.root, 'src/index.js'),
// },
// 輸出文件
// 字符串形式
// output:path.join(config.root, './dist/[name].js')
//對象形式
output: {
// 輸出文件的目錄地址
path: path.join(config.root, 'dist'),
// 輸出文件名稱,contenthash代表一種緩存,只有文件更改才會更新hash值,重新打包
filename: '[name]_[contenthash].js'
},
//devtool:false, //'eval'
module:{
rules:[
{
// 所有的.js(x?)文件都走babel-loader
test: /\.js(x?)$/,
include: path.join(config.root,'src'),
loader: "babel-loader"
}
]
},
optimization: {
minimize: false,
minimizer: [
new TerserPlugin({
// 指定壓縮的文件
include: /\.js(\?.*)?$/i,
// 排除壓縮的文件
// exclude:/\.js(\?.*)?$/i,
// 是否啟用多線程運行,默認為true,開啟,默認並發數量為os.cpus()-1
// 可以設置為false(不使用多線程)或者數值(並發數量)
parallel: true,
// 可以設置一個function,使用其它壓縮插件覆蓋默認的壓縮插件,默認為undefined,
minify: undefined,
// 是否將代碼注釋提取到一個單獨的文件。
// 屬性值:Boolean | String | RegExp | Function<(node, comment) -> Boolean|Object> | Object
// 默認為true, 只提取/^\**!|@preserve|@license|@cc_on/i注釋
// 感覺沒什么特殊情況直接設置為false即可
extractComments: false,
// 壓縮時的選項設置
terserOptions: {
// 是否保留原始函數名稱,true代表保留,false即保留
// 此屬性對使用Function.prototype.name
// 默認為false
keep_fnames: false,
// 是否保留原始類名稱
keep_classnames: false,
// format和output是同一個屬性值,,名稱不一致,output不建議使用了,被放棄
// 指定壓縮格式。例如是否保留*注釋*,是否始終為*if*、*for*等設置大括號。
format: {
comments: false,
},
output: undefined,
// 是否支持IE8,默認不支持
ie8: false,
compress: {
// 是否使用默認配置項,這個屬性當只啟用指定某些選項時可以設置為false
defaults: false,
// 是否移除無法訪問的代碼
dead_code: false,
// 是否優化只使用一次的變量
collapse_vars: true,
warnings: true,
// 是否刪除所有 console.*語句,默認為false,這個可以在線上設置為true
drop_console: false,
// 是否刪除所有debugger語句,默認為true
drop_debugger: true,
// 移除指定func,這個屬性假定函數沒有任何副作用,可以使用此屬性移除所有指定func
// pure_funcs: ['console.log'], //移除console
},
},
})
]
},
plugins: [
new HtmlWebpackPlugin({
// HTML的標題,
// template的title優先級大於當前數據
title: 'my-cli',
// 輸出的html文件名稱
filename: 'index.html',
// 本地HTML模板文件地址
template: path.join(config.root, 'src/index.html'),
// 引用JS文件的目錄路徑
publicPath: './',
// 引用JS文件的位置
// true或者body將打包后的js腳本放入body元素下,head則將腳本放到中
// 默認為true
inject: 'body',
// 加載js方式,值為defer/blocking
// 默認為blocking, 如果設置了defer,則在js引用標簽上加上此屬性,進行異步加載
scriptLoading: 'blocking',
// 是否進行緩存,默認為true,在開發環境可以設置成false
cache: false,
// 添加mate屬性
meta: {}
}),
new CleanWebpackPlugin({
// 是否假裝刪除文件
// 如果為false則代表真實刪除,如果為true,則代表不刪除
dry: false,
// 是否將刪除日志打印到控制台 默認為false
verbose: true,
// 允許保留本次打包的文件
// true為允許,false為不允許,保留本次打包結果,也就是會刪除本次打包的文件
// 默認為true
protectWebpackAssets: true,
// 每次打包之前刪除匹配的文件
cleanOnceBeforeBuildPatterns: ['**/*'],
// 每次打包之后刪除匹配的文件
cleanAfterEveryBuildPatterns:["*.js"],
}),
new webpack.DefinePlugin({ "global_a": JSON.stringify("我是一個打包配置的全局變量") }),
],
resolve: {
alias:{
// 設置路徑別名
'@': path.join(config.root, 'src') ,
'~': path.join(config.root, './src/assets') ,
},
// 可互忽略的后綴
extensions:['.JSX', '.js', '.json'],
// 默認讀取的文件名
mainFiles:['index', 'main'],
}
}
// 使用node.js的導出,將配置進行導出
module.exports = modules
.babelrc
{
"presets": [
"@babel/preset-react",
[
"@babel/preset-env",
{
"modules":false
// 移除useBuiltIns設置
// "targets": "chrome > 75",
// "useBuiltIns": "usage",
// "corejs": {
// "version": 3,
// "proposals":true
// }
}
]
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": {
"version": 3,
"proposals": true
}
}
]
]
}