從零學腳手架(五)---react、browserslist


如果此篇對您有所幫助,在此求一個star。項目地址: OrcasTeam/my-cli

react

react介紹

目前,國內主流的前端應用框架具有兩個:vue.jsreact.js,關於vue和react的優劣性,網上眾說紛紜。在下就不在此引戰。

而是直接介紹React

🐋🐋🐋 vueReact這種都是快速應用開發工具,可能也會像曾經如日中天的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)

🐋🐋 JSXReact提供構建代碼方式的一種擴展語言,本質是一個語法糖。JSX定義的事件styleclassJSX自身語法,並不是原生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()虛擬DOMJSX語法支持等一系列核心內容。

但是此庫並不沒有提供與真實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

babelReact提供了一個preset@babel/preset-react

@babel/preset-react中封裝了所有處理Reactplugin

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 RouterRedux只是用於擴展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.jsBROWSERSLIST_ENV環境變量對應即可。

在此就不貼圖測試了,有興趣的朋友可以自行測試。

至於BROWSERSLIST_ENV 環境變量與 webpack中不同模式的關聯,在下一篇介紹。

browserslist支持的瀏覽器

browserslist支持設置當前基本上所有的瀏覽器,在Github上作者說明了可以設置的瀏覽器

可以看到,browserslist幾乎支持所有瀏覽器:PC、安卓、IOS 甚至還有國內瀏覽器。

🐋🐋 設置瀏覽器時名稱不區分大小寫

browserslist屬性

browserslist能得到社區的認可,也就在於browserslist提供了強大的屬性設置。

如前面使用的 指定 區間瀏覽器(chrome > 75) 也只是browserslist簡單的屬性配置

下面簡單列舉部分browserslist屬性配置,想了解更多的朋友請參考Github

  • defaultsbrowserslist設置的默認瀏覽器版本。屬性只相當 > 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
        }
      }
    ]
  ]
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM