天天看點

01【React再造之旅】從0搭建一個完整的React項目(初稿)

概述

平時工作中一直在用React提供的腳手架工具搭建React項目,一行指令全都搞定,自己隻管做需求開發即可,從來沒仔細研究過各個子產品代碼怎麼去配置,互相之間怎麼去進行互動。這周正好有時間,是以決定仔細研究下React項目中的各個功能子產品,是以我們來講解下如何從零搭建一個完整的React項目。

本文篇幅較長,請你耐心閱讀~

目錄

​​概述​​

​​目錄​​

​​操作步驟​​

​​一、項目初始化​​

​​1、安裝NodeJS環境和初始化項目​​

​​2、安裝webpack​​

​​3、建立項目目錄和檔案​​

​​二、核心配置​​

​​1、ES6、ES7、ES8、ES9等進階文法轉換成ES5​​

​​2、less/sass等css預處理器代碼轉換為css​​

​​3、解析字型、圖檔等靜态資源​​

​​4、壓縮打包後的JS、CSS檔案​​

​​5、抽離公共代碼​​

​​6、添加resolve選項​​

​​8、删除上一次的打包結果及記錄​​

​​三、內建React全家桶​​

​​1、內建react​​

​​2、內建react-router-dom​​

​​3、內建redux(後續更新)​​

​​4、內建Ant Design​​

​​5、添加express服務接口,用axios打通前後端​​

​​總結​​

操作步驟

一、項目初始化

1、安裝NodeJS環境和初始化項目

在開始之前,我們本機首先要安裝部署Node環境。Node環境安裝部署其實很簡單,隻需要去官網(https://nodejs.org/zh-cn/)下載下傳安裝包,然後輕按兩下安裝即可,中間并沒有太大的坑,安裝過程中會自動将node安裝路徑添加至作業系統的環境變量中,是以安裝完成後我們直接可以在指令行中使用ndoe、npm這些指令,不像java一樣還要自己手動去配環境變量。

安裝完成後,我們打開指令行工具或者win10的powershell視窗,輸入以下指令,如果成功輸入資訊,即表明Node環境安裝部署成功:

node -v
npm -v      
01【React再造之旅】從0搭建一個完整的React項目(初稿)

Node環境部署成功後,我們在he'sh合适的目錄檔案夾中,打開powershell視窗,輸入以下指令來建立我們的項目檔案夾,并且進入到這個建立的檔案夾中,最後通過"npm init"來初始化一個最基礎的項目架構:

mkdir myreactproject
 cd .\myreactproject\
 npm init      
01【React再造之旅】從0搭建一個完整的React項目(初稿)

當我們進入到建立的項目檔案夾的時候,通過"npm init"來初始化一個基礎的項目架構時,指令行中會出現有一些問答資訊,這些資訊是在詢問我們關于初始化的這個項目的一些基礎資訊,包括項目名稱、項目描述、項目版本、入口檔案、測試指令、git庫位址、關鍵字、項目作者、項目遵循的版權資訊等,在這裡我們直接按回車即可,它會将預設資訊填入相應的屬性中,同樣的,我們也可以輸入自定義的一些資訊,看自己喜好吧。

到此時呢,我們的一個基礎項目架構已經完成了,打開這個項目檔案夾,大家可以看到,在此檔案夾下生成了一個"package.json"檔案,裡面就是我們剛才建立項目時候指定的一些基礎資訊,如下:

01【React再造之旅】從0搭建一個完整的React項目(初稿)
01【React再造之旅】從0搭建一個完整的React項目(初稿)

2、安裝webpack

webpack是一個子產品打包工具,它會自動分析我們項目中的依賴以及項目編碼中所用的進階文法這些東西,然後将它們打包編譯成浏覽器可以解析運作的js和css等檔案。我們可以将webpack的API和CLI配合使用,API不用過多解釋,這是webpack提供給我們調用和配置的接口,CLI是webpack提供的一個類似于腳手架的東西,它允許我們在指令行中可以使用webpack指令、運作webpack,是以在此處我們安裝兩個東西。

在項目根目錄下運作指令行或powershell工具,然後通過以下指令安裝webpack和webpack-cli工具:

npm install webpack webpack-cli --save-dev      
01【React再造之旅】從0搭建一個完整的React項目(初稿)

安裝完成之後可以在項目根目錄下看到,多了一個"node_modules"檔案夾和一個"package-lock.json"檔案,同時在我們的"package.json"檔案中也多了些資訊。

其中"node_modules"檔案夾中存放的是我們整個項目代碼中安裝的一些插件子產品的代碼;"package-lock.json"檔案是我們執行"npm install"指令後生成的,它鎖定所有子產品的版本号,包括主子產品和所有依賴子子產品。當我們執行npm install的時候,node從package.json檔案讀取子產品名稱,從package-lock.json檔案中擷取版本号,然後進行下載下傳或者更新。是以,正因為有了package-lock.json檔案鎖定版本号,是以當我們執行npm install的時候,node不會自動更新package.json檔案中的子產品,必須用npm install packagename(自動更新小版本号)或者npm install [email protected](指定版本号)來進行安裝才會更新,package-lock.json檔案中的版本号也會随着更新。當package.json與package-lock.json都不存在,執行"npm install"時,node會重新生成package-lock.json檔案,然後把node_modules中的子產品資訊全部記入package-lock.json檔案,但不會生成package.json檔案,此時,我們可以通過"npm init --yes"或者"npm init"來初始化生成package.json檔案;"package.json"檔案中多出來的資訊就是我們剛才安裝的webpack的描述資訊,它裡面記錄了安裝的webpack的版本号和webpack-cli的版本号,如下:

01【React再造之旅】從0搭建一個完整的React項目(初稿)

3、建立項目目錄和檔案

項目根目錄下建立"src"檔案夾,用來存放後期的項目源碼,然後裡面建立一個“index.js”檔案作為被webpack編譯的檔案,同時也是webpack配置的入口檔案;項目根目錄下再建立一個“build”檔案夾,存放項目的webpack配置檔案,然後在檔案夾中建立"webpack.config.js"檔案,用于編寫webpack的核心配置代碼;在項目根目錄建立一個"index.html"檔案,是後期我們的項目打包運作的首頁面,同時項目打包後自動将打包的檔案添加在該檔案中。

檔案目錄建立完成後是如下所示的結構:

01【React再造之旅】從0搭建一個完整的React項目(初稿)

檔案目錄和檔案都建立成功後,我們分别為各個檔案添加上基礎代碼,最後測試一下搭建的基礎環境是否成功。

首先是webpack.config.js檔案,在此檔案中我們根據官網文檔,在裡面添加如下代碼,完成webpack的基礎配置:

const path = require('path');

module.exports = {
    mode: 'development',            //此行表示我們webpack打包環境是開發環境
    entry: './src/index.js',        //項目的入口檔案,是我們建立的index.js檔案,它的路徑是相對于項目根路徑的,是以此處我們寫的是“./src”,而不是“../src”
    output: {                       //配置輸出資訊
        filename: 'bundle.js',      //打包輸出的檔案名稱,這裡是一個寫死的名稱,後期可以改成按一定規則動态生成
        path: path.resolve(__dirname, '../dist')     //輸出的路徑,這裡的路徑針對的是目前目錄,是以我們寫成了"../dist",而不是"./dist"
    }
};      

接下來是建立的index.js檔案,在此檔案我們添加以下測試代碼即可,僅僅是用來測試打包是否成功:

function sum (a, b) {
    return a + b;
}
var result = sum (12, 23);
console.log(result);      

最後将package.json檔案中的script屬性修改一下,使它能夠讓在我們指令行中輸入啟動指令後自動完成打包過程,如下:

"build": "webpack --config ./build/webpack.config.js"      
01【React再造之旅】從0搭建一個完整的React項目(初稿)

到此為止呢,我們編輯和修改代碼已經完成了,index.html檔案中并沒有增加任何代碼,此時它隻是一個空檔案,我們後期再增加。在項目根目錄下運作指令行或powershell工具,然後通過“npm run build”來啟動我們的項目,此處其實并不叫啟動,因為我們沒有為項目配置調試伺服器這些插件,準确點應該說是通過這個指令來進行項目打包,如下:

01【React再造之旅】從0搭建一個完整的React項目(初稿)

由上圖可看到我們的項目已經順利打包,這時候在我們項目根目錄自動建立了“dist”檔案夾,并且裡面生成了結果檔案bundle.js,如下:

01【React再造之旅】從0搭建一個完整的React項目(初稿)

打開bundle.js檔案,我們可以看到打包後的代碼,如下所示:

01【React再造之旅】從0搭建一個完整的React項目(初稿)

此時說明我們項目安裝和配置的webpack是正确的。

二、核心配置

1、ES6、ES7、ES8、ES9等進階文法轉換成ES5

ES6、ES7、ES8、ES9等這些進階文法在浏覽器中是無法直接編譯運作的,是以需要我們在編譯運作之前對這些進階文法進行轉換,将它們轉換成ES5代碼,以便在浏覽器中進行編譯運作。這個事情是babel-loader來做的,它主要是将ES6等進階文法轉換成浏覽器能解析運作的低級文法,是以我們要在項目根目錄中安裝這些插件:

npm install babel-loader @babel/core @babel/preset-env @babel/preset-react --save-dev      

以上插件中babel-loader主要用于進階文法轉換成低級文法,它是webpack的一個loader,用來預處理檔案;@babel/core是babel的核心子產品,提供轉換的API;@babel/preset-env可以根據配置的目标浏覽器或者運作環境将ES5+代碼自動轉換為ES5代碼,也是babel的一個子產品;@babel/preset-react用來解析React中的JSX文法,同樣也是babel的子產品。以上的子產品寫法中,前面加有"@"符号的寫法是babel 7.0+版本的寫法,具體的可以檢視babel官網。

01【React再造之旅】從0搭建一個完整的React項目(初稿)

相關的babel依賴安裝完成後,我們在項目根目錄建立“babel.config.js”檔案,用來配置babel。如果不建立此檔案的話babel的配置資訊我們直接寫到webpack配置檔案中的對應規則下的options屬性中即可,在此處我們用babel.config.js配置檔案的方式。在配置檔案中我們加入如下代碼:

const babelConfig = {
    presets: [                      //它的功能相當于是下面plugins的一個集合,即插件集。有了它我們不用在plugins中一個一個的配置插件了
        ["@babel/preset-env", {
            useBuiltIns: "entry",    //如果用了@babel/polyfill的話,配置這個屬性可以将@babel/polyfill按需引入
            corejs: 2
        }], "@babel/preset-react"
    ],
    plugins: []
};

module.exports = babelConfig;      

然後在webpack.config.js配置檔案中,我們配置一下babel-loader,代碼如下:

const path = require('path');

module.exports = {
    mode: 'development',
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, '../dist')
    },
    module: {       //通過module屬性配置babel-loader
        rules: [
            {
                test: /\.js/,
                use: ['babel-loader?cacheDirectory=true'],
                include: path.join(__dirname, '../src')
            }
        ]
    }
};      

配置完成之後,我們修改下index.js中的代碼,來測試下是否可以成功打包我們的ES5+的代碼檔案:

let xbeichen = () => {
    console.log('測試箭頭函數');
}

xbeichen();      

通過在指令行運作啟動指令後發現,我們的項目打包成功,如下:

01【React再造之旅】從0搭建一個完整的React項目(初稿)
01【React再造之旅】從0搭建一個完整的React項目(初稿)

以上的配置還存在兩個問題,第一個首先是雖然我們打包成功了項目,這也表示着ES5+的代碼我們可以順利打包,但是我們在代碼中用Promise、Set、Symbol等全局對象或者一些定義在全局對象上的方法時它都不會轉換,這是因為我們的babel-loader隻能轉換進階文法,并不會轉換新的API,是以這時候我們就需要用@babel/polyfill來為目前環境提供一個墊片;還有第二個問題是,當我們執行打包後,打包的檔案裡會有大量的重複代碼,那我們這時候就需要提供統一的子產品化的helper來減少這些helper函數的重複輸出。是以就需要我們安裝以下插件子產品:

npm install @babel/polyfill --save-dev      
01【React再造之旅】從0搭建一個完整的React項目(初稿)

@babel/polyfill安裝完之後我們不再需要進行額外的配置,因為在上面babel的配置檔案中我們已經指定了@babel/polyfill是按需引入。

npm install @babel/runtime @babel/plugin-transform-runtime @babel/plugin-syntax-dynamic-import --save-dev      
01【React再造之旅】從0搭建一個完整的React項目(初稿)

以上三個插件安裝完成之後,我們需要在babel的配置檔案中進行相應的配置,如下:

const babelConfig = {
    presets: [
        ["@babel/preset-env", {
            useBuiltIns: "entry",
            corejs: 2
        }], "@babel/preset-react"
    ],
    plugins: ["@babel/plugin-syntax-dynamic-import", ["@babel/plugin-transform-runtime"]]   //就是在此處添加了兩個@babel/runtime中的插件
};

module.exports = babelConfig;      

最後我們在index.js檔案中添加Promise相關的代碼來檢視打包結果,如下:

let xbeichen = () => {
    console.log('測試箭頭函數');
}

xbeichen();

let promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(123);
    }, 1000);
});

promise.then(res => {
    console.log(res);
});      

運作啟動指令後打包結果如下所示:

01【React再造之旅】從0搭建一個完整的React項目(初稿)

2、less/sass等css預處理器代碼轉換為css

在項目中如果我們使用了css預處理器,那就需要在打包的時候将less、sass等預處理器編寫的代碼轉換成浏覽器可以執行的css代碼,這就需要我們安裝以下插件,此處介紹less預處理器代碼的轉換配置:

npm install stylus stylus-loader less less-loader sass-loader node-sass css-loader style-loader --save-dev      
01【React再造之旅】從0搭建一個完整的React項目(初稿)

以上安裝的依賴插件中:css-loader主要的作用是解析css檔案, 像@import等動态文法;style-loader主要的作用是解析的css檔案渲染到html的style标簽内;stylus、less、sass是CSS的常見預處理器;stylus-loader、less-loader、sass-loader主要是将其對應的文法轉換成css文法。

然後我們修改webpack的配置檔案,代碼如下:

const path = require('path');

module.exports = {
    mode: 'development',
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, '../dist')
    },
    module: {
        rules: [
            {
                test: /\.js/,
                use: ['babel-loader?cacheDirectory=true'],
                include: path.join(__dirname, '../src')
            }, {                                                 //此處再添加一條rules,用于配置css預處理器資訊
                test: /\.less$/,
                use: [
                    {
                        loader: 'style-loader'
                    }, {
                        loader: 'css-loader'
                    }, {
                        loader: 'less-loader'
                    }
                ]
            }
        ]
    }
};      

在src目錄下建立"index.less"檔案,添加如下代碼:

@color: red;

#div1 {
    color: @color;
    font-size: 23px;
}      
01【React再造之旅】從0搭建一個完整的React項目(初稿)

然後在index.js檔案中我們引入建立的index.less檔案,運作啟動指令來執行打包,結果如下:

01【React再造之旅】從0搭建一個完整的React項目(初稿)
01【React再造之旅】從0搭建一個完整的React項目(初稿)

但是如果我們使用CSS3的一些新特性時,需要為不同的浏覽器在CSS代碼中添加不同的字首,在開發中手動添加太麻煩,是以我們可以通過postcss來自動添加各種浏覽器字首。首先我們先要安裝以下依賴插件:

npm install postcss-loader autoprefixer --save-dev      
01【React再造之旅】從0搭建一個完整的React項目(初稿)

然後在webpack配置檔案中添加postcss的配置資訊即可,如下:

const path = require('path');

module.exports = {
    mode: 'development',
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, '../dist')
    },
    module: {
        rules: [
            {
                test: /\.js/,
                use: ['babel-loader?cacheDirectory=true'],
                include: path.join(__dirname, '../src')
            }, {
                test: /\.less$/,
                use: [
                    {
                        loader: 'style-loader'
                    }, {
                        loader: 'css-loader'
                    }, {     //添加這段配置資訊即可
                        loader: 'postcss-loader',
                        options: {
                            plugins: [
                                require('autoprefixer')
                            ]
                        }
                    }, {
                        loader: 'less-loader'
                    }
                ]
            }
        ]
    }
};      

3、解析字型、圖檔等靜态資源

在我們的項目中會使用到圖檔等靜态資源,在此處我們來添加配置。首先是安裝相關依賴,如下:

npm install file-loader url-loader --save-dev      
01【React再造之旅】從0搭建一個完整的React項目(初稿)

以上依賴中:file-loader可以用來幫助webpack打包處理一系列的圖檔檔案,比如:.png 、 .jpg 、.jepg等格式的圖檔。打包的圖檔會給每張圖檔都生成一個随機的hash值作為圖檔的名字;url-loader封裝了file-loader,它的工作原理:1、檔案大小小于limit參數,url-loader将會把檔案轉為Base64;2、檔案大小大于limit,url-loader會調用file-loader進行處理,參數也會直接傳給file-loader。

安裝完依賴後,我們進行webpack的配置,代碼如下:

const path = require('path');

module.exports = {
    mode: 'development',
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, '../dist')
    },
    module: {
        rules: [
            {
                test: /\.js/,
                use: ['babel-loader?cacheDirectory=true'],
                include: path.join(__dirname, '../src')
            }, {
                test: /\.less$/,
                use: [
                    {
                        loader: 'style-loader'
                    }, {
                        loader: 'css-loader'
                    }, {
                        loader: 'postcss-loader',
                        options: {
                            plugins: [
                                require('autoprefixer')
                            ]
                        }
                    }, {
                        loader: 'less-loader'
                    }
                ]
            }, {                                       //配置圖檔靜态資源的打包資訊
                test: /\.(jpg|png|jpeg|gif)$/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 1024,
                            fallback: {
                                loader: 'file-loader',
                                options: {
                                    name: 'img/[name].[hash:8].[ext]'
                                }
                            }
                        }
                    }
                ]
            }, {                                       //配置多媒體資源的打包資訊
                test: /\.(mp4|webm|ogg|mp3|wav)$/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 1024,
                            fallback: {
                                loader: 'file-loader',
                                options: {
                                    name: 'media/[name].[hash:8].[ext]'
                                }
                            }
                        }
                    }
                ]
            }
        ]
    }
};      

然後我們在項目src目錄下建立"assets"檔案夾,裡面放置兩張圖檔,在index.js中引入這兩張圖檔,運作啟動指令來打包項目代碼,最後檢視結果:

01【React再造之旅】從0搭建一個完整的React項目(初稿)
01【React再造之旅】從0搭建一個完整的React項目(初稿)
01【React再造之旅】從0搭建一個完整的React項目(初稿)

除此之外,在我們的dist目錄下建立了一個img檔案夾,裡面存放的是兩張打包後的圖檔檔案,如下:

01【React再造之旅】從0搭建一個完整的React項目(初稿)

以上是我們對靜态資源打包的配置資訊。

4、壓縮打包後的JS、CSS檔案

我們打包後的JS和CSS檔案中存在大量的空格和引号等,這些會嚴重影像我們打包後的檔案體積,是以接下來我們通過安裝配置相應的依賴插件來壓縮我們打包後的代碼檔案。首先安裝如下依賴:

npm install mini-css-extract-plugin --save-dev      
01【React再造之旅】從0搭建一個完整的React項目(初稿)

然後在webpack配置檔案中增加我們依賴插件的配置,如下:

const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');       //首先引入我們新安裝的依賴插件

module.exports = {
    mode: 'development',
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, '../dist')
    },
    plugins: [                             //然後建立一個plugins屬性來執行個體化這個依賴插件
        new MiniCssExtractPlugin({
            filename: '[name].[contenthash].css',
            chunkFilename: '[id].[contenthash].css'
        })
    ],
    module: {
        rules: [
            {
                test: /\.js/,
                use: ['babel-loader?cacheDirectory=true'],
                include: path.join(__dirname, '../src')
            }, {                                             //最後添加這個依賴插件的配置資訊
                test: /\.css$/,
                use: [
                    {
                        loader: MiniCssExtractPlugin.loader,
                        options: {
                            publicPath: '../'
                        }
                    },
                    'css-loader',
                ]
            }, {
                test: /\.less$/,
                use: [
                    {
                        loader: 'style-loader'
                    }, {
                        loader: 'css-loader'
                    }, {
                        loader: 'postcss-loader',
                        options: {
                            plugins: [
                                require('autoprefixer')
                            ]
                        }
                    }, {
                        loader: 'less-loader'
                    }
                ]
            }, {
                test: /\.(jpg|png|jpeg|gif)$/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 1024,
                            fallback: {
                                loader: 'file-loader',
                                options: {
                                    name: 'img/[name].[hash:8].[ext]'
                                }
                            }
                        }
                    }
                ]
            }, {
                test: /\.(mp4|webm|ogg|mp3|wav)$/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 1024,
                            fallback: {
                                loader: 'file-loader',
                                options: {
                                    name: 'media/[name].[hash:8].[ext]'
                                }
                            }
                        }
                    }
                ]
            }
        ]
    }
};      

然後我們在src目錄下建立"index02.css"檔案,在此檔案中添加css測試代碼,如下:

#divview {
    position: relative;
    color: blue;
    width: 200px;
}      

接下來在index.js檔案中引入建立的這個index02.css檔案和我們之前建立的index.less檔案,最後運作啟動指令來進行打包,最後結果如下:

01【React再造之旅】從0搭建一個完整的React項目(初稿)
01【React再造之旅】從0搭建一個完整的React項目(初稿)
01【React再造之旅】從0搭建一個完整的React項目(初稿)

由結果可以看到,最後css檔案被打包,重新在dist目錄下生成了一個打包後的檔案,但是我們的less檔案依然是直接打包在了bundle.js的檔案中,這是因為mini-css-extract-plugin這個依賴插件隻針對于css檔案的。

5、抽離公共代碼

我們在打包後的JS或者CSS檔案中會有很多公共代碼,如果不将這些代碼進行抽離,我們最後的打封包件會變得很大,是以抽離公共代碼這件事情是由SplitChunksPlugin插件來做,我們隻需要在webpack配置檔案中加入如下代碼即可:

const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
    mode: 'development',
    optimization: {        //添加抽離公共代碼插件的配置
        splitChunks: {
            cacheGroups: {
                //打包公共子產品
                commons: {
                    chunks: 'initial', //initial表示提取入口檔案的公共部分
                    minChunks: 2, //表示提取公共部分最少的檔案數
                    minSize: 0, //表示提取公共部分最小的大小
                    name: 'commons' //提取出來的檔案命名
                }
            }
        },
    },
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, '../dist')
    },
    plugins: [
        new MiniCssExtractPlugin({
            filename: '[name].[contenthash].css',
            chunkFilename: '[id].[contenthash].css'
        })
    ],
    module: {
        rules: [
            {
                test: /\.js/,
                use: ['babel-loader?cacheDirectory=true'],
                include: path.join(__dirname, '../src')
            }, {
                test: /\.css$/,
                use: [
                    {
                        loader: MiniCssExtractPlugin.loader,
                        options: {
                            publicPath: '../'
                        }
                    },
                    'css-loader',
                ]
            }, {
                test: /\.less$/,
                use: [
                    {
                        loader: 'style-loader'
                    }, {
                        loader: 'css-loader'
                    }, {
                        loader: 'postcss-loader',
                        options: {
                            plugins: [
                                require('autoprefixer')
                            ]
                        }
                    }, {
                        loader: 'less-loader'
                    }
                ]
            }, {
                test: /\.(jpg|png|jpeg|gif)$/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 1024,
                            fallback: {
                                loader: 'file-loader',
                                options: {
                                    name: 'img/[name].[hash:8].[ext]'
                                }
                            }
                        }
                    }
                ]
            }, {
                test: /\.(mp4|webm|ogg|mp3|wav)$/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 1024,
                            fallback: {
                                loader: 'file-loader',
                                options: {
                                    name: 'media/[name].[hash:8].[ext]'
                                }
                            }
                        }
                    }
                ]
            }
        ]
    }
};      

6、添加resolve選項

這個選項的作用是為了友善我們開發者,比如檔案的别名、檔案的擴充名等,具體的配置如下:

const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
    mode: 'development',
    optimization: {
        splitChunks: {
            cacheGroups: {
                //打包公共子產品
                commons: {
                    chunks: 'initial', //initial表示提取入口檔案的公共部分
                    minChunks: 2, //表示提取公共部分最少的檔案數
                    minSize: 0, //表示提取公共部分最小的大小
                    name: 'commons' //提取出來的檔案命名
                }
            }
        },
    },
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, '../dist')
    },
    plugins: [
        new MiniCssExtractPlugin({
            filename: '[name].[contenthash].css',
            chunkFilename: '[id].[contenthash].css'
        })
    ],
    module: {
        rules: [
            {
                test: /\.js/,
                use: ['babel-loader?cacheDirectory=true'],
                include: path.join(__dirname, '../src')
            }, {
                test: /\.css$/,
                use: [
                    {
                        loader: MiniCssExtractPlugin.loader,
                        options: {
                            publicPath: '../'
                        }
                    },
                    'css-loader',
                ]
            }, {
                test: /\.less$/,
                use: [
                    {
                        loader: 'style-loader'
                    }, {
                        loader: 'css-loader'
                    }, {
                        loader: 'postcss-loader',
                        options: {
                            plugins: [
                                require('autoprefixer')
                            ]
                        }
                    }, {
                        loader: 'less-loader'
                    }
                ]
            }, {
                test: /\.(jpg|png|jpeg|gif)$/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 1024,
                            fallback: {
                                loader: 'file-loader',
                                options: {
                                    name: 'img/[name].[hash:8].[ext]'
                                }
                            }
                        }
                    }
                ]
            }, {
                test: /\.(mp4|webm|ogg|mp3|wav)$/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 1024,
                            fallback: {
                                loader: 'file-loader',
                                options: {
                                    name: 'media/[name].[hash:8].[ext]'
                                }
                            }
                        }
                    }
                ]
            }
        ]
    },
    resolve: {                                    //resolve核心配置
        extensions: ['.js', '.jsx', '.json'],
        alias: {
            pages: path.join(__dirname, '../src/pages'),
            components: path.join(__dirname, '../src/components'),
            actions: path.join(__dirname, '../src/redux/actions'),
            reducers: path.join(__dirname, '../src/redux/reducers'),
            images: path.join(__dirname, '../src/images')
        }
    },
};      

7、代碼熱更新

代碼熱更新需要安裝以下兩個依賴子產品,如下:

npm install webpack-dev-server --save-dev
npm install html-webpack-plugin --save-dev      
01【React再造之旅】從0搭建一個完整的React項目(初稿)
01【React再造之旅】從0搭建一個完整的React項目(初稿)

其中第一個依賴插件是熱更新插件,第二個是我們的html-webpack-plugin插件,這個插件的作用是它可以每次動态的将我們打包後的js、css檔案加入到index.html頁面中。其中webpack的配置資訊如下:

const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');      //引入html模闆插件

module.exports = {
    mode: 'development',
    optimization: {
        splitChunks: {
            cacheGroups: {
                //打包公共子產品
                commons: {
                    chunks: 'initial', //initial表示提取入口檔案的公共部分
                    minChunks: 2, //表示提取公共部分最少的檔案數
                    minSize: 0, //表示提取公共部分最小的大小
                    name: 'commons' //提取出來的檔案命名
                }
            }
        },
    },
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, '../dist')
    },
    plugins: [
        new MiniCssExtractPlugin({
            filename: '[name].[contenthash].css',
            chunkFilename: '[id].[contenthash].css'
        }),
        new HtmlWebpackPlugin({             //執行個體化Html模闆子產品
            template: path.resolve(__dirname, '../index.html')
        })
    ],
    module: {
        rules: [
            {
                test: /\.js/,
                use: ['babel-loader?cacheDirectory=true'],
                include: path.join(__dirname, '../src')
            }, {
                test: /\.css$/,
                use: [
                    {
                        loader: MiniCssExtractPlugin.loader,
                        options: {
                            publicPath: '../'
                        }
                    },
                    'css-loader',
                ]
            }, {
                test: /\.less$/,
                use: [
                    {
                        loader: 'style-loader'
                    }, {
                        loader: 'css-loader'
                    }, {
                        loader: 'postcss-loader',
                        options: {
                            plugins: [
                                require('autoprefixer')
                            ]
                        }
                    }, {
                        loader: 'less-loader'
                    }
                ]
            }, {
                test: /\.(jpg|png|jpeg|gif)$/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 1024,
                            fallback: {
                                loader: 'file-loader',
                                options: {
                                    name: 'img/[name].[hash:8].[ext]'
                                }
                            }
                        }
                    }
                ]
            }, {
                test: /\.(mp4|webm|ogg|mp3|wav)$/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 1024,
                            fallback: {
                                loader: 'file-loader',
                                options: {
                                    name: 'media/[name].[hash:8].[ext]'
                                }
                            }
                        }
                    }
                ]
            }
        ]
    },
    resolve: {
        extensions: ['.js', '.jsx', '.json'],
        alias: {
            pages: path.join(__dirname, '../src/pages'),
            components: path.join(__dirname, '../src/components'),
            actions: path.join(__dirname, '../src/redux/actions'),
            reducers: path.join(__dirname, '../src/redux/reducers'),
            images: path.join(__dirname, '../src/images')
        }
    },
    devServer: {            //配置熱更新子產品
        hot: true,
        open: true,
        port: 3500,
        contentBase: '../dist'
    }
};      

webpack配置好之後,我們在package.json中添加啟動指令,這次的啟動指令是真正意義的啟動指令,如下所示:

"dev": "webpack-dev-server --config ./build/webpack.config.js"      
01【React再造之旅】從0搭建一個完整的React項目(初稿)

在指令行中輸入"npm run dev"來啟動項目,這時候浏覽器會自動打開,我們打開浏覽器控制台可以看到,輸出了我們在index.js中編寫的代碼,此時也說明它是将我們的js、css檔案自動加載到index.html頁面中了,如下:

01【React再造之旅】從0搭建一個完整的React項目(初稿)

此處我們index.js中的代碼如下:

import './index.less';
import './index02.css';

let sun = () => {
    console.log('xbeichen');
};

sun();      

同樣的,這時候我們運作打包指令,可以看到dist目錄下的打包結果,如下:

01【React再造之旅】從0搭建一個完整的React項目(初稿)

由上圖可看到,它打包生成了三個檔案,并且在html檔案中,自動的将js和css檔案引入進去了。

8、删除上一次的打包結果及記錄

我們每次運作打包指令之前都要手動删除dist檔案夾,不然的話它每次打包都會在dist檔案夾中加入新的打包内容,上一次的打包内容還存留着,是以我們要安裝clean-webpack-plugin插件來将我們上一次的打包記錄及結果删除,安裝配置如下:

npm install clean-webpack-plugin --save-dev      
01【React再造之旅】從0搭建一個完整的React項目(初稿)

webpack中的配置資訊如下:

const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');     //引入clean-webpack-plugin插件

module.exports = {
    mode: 'development',
    optimization: {
        splitChunks: {
            cacheGroups: {
                //打包公共子產品
                commons: {
                    chunks: 'initial', //initial表示提取入口檔案的公共部分
                    minChunks: 2, //表示提取公共部分最少的檔案數
                    minSize: 0, //表示提取公共部分最小的大小
                    name: 'commons' //提取出來的檔案命名
                }
            }
        },
    },
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, '../dist')
    },
    plugins: [
        new CleanWebpackPlugin(),          //執行個體化clean-webpack-plugin插件
        new MiniCssExtractPlugin({
            filename: '[name].[contenthash].css',
            chunkFilename: '[id].[contenthash].css'
        }),
        new HtmlWebpackPlugin({
            template: path.resolve(__dirname, '../index.html')
        })
    ],
    module: {
        rules: [
            {
                test: /\.js/,
                use: ['babel-loader?cacheDirectory=true'],
                include: path.join(__dirname, '../src')
            }, {
                test: /\.css$/,
                use: [
                    {
                        loader: MiniCssExtractPlugin.loader,
                        options: {
                            publicPath: '../'
                        }
                    },
                    'css-loader',
                ]
            }, {
                test: /\.less$/,
                use: [
                    {
                        loader: 'style-loader'
                    }, {
                        loader: 'css-loader'
                    }, {
                        loader: 'postcss-loader',
                        options: {
                            plugins: [
                                require('autoprefixer')
                            ]
                        }
                    }, {
                        loader: 'less-loader'
                    }
                ]
            }, {
                test: /\.(jpg|png|jpeg|gif)$/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 1024,
                            fallback: {
                                loader: 'file-loader',
                                options: {
                                    name: 'img/[name].[hash:8].[ext]'
                                }
                            }
                        }
                    }
                ]
            }, {
                test: /\.(mp4|webm|ogg|mp3|wav)$/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 1024,
                            fallback: {
                                loader: 'file-loader',
                                options: {
                                    name: 'media/[name].[hash:8].[ext]'
                                }
                            }
                        }
                    }
                ]
            }
        ]
    },
    resolve: {
        extensions: ['.js', '.jsx', '.json'],
        alias: {
            pages: path.join(__dirname, '../src/pages'),
            components: path.join(__dirname, '../src/components'),
            actions: path.join(__dirname, '../src/redux/actions'),
            reducers: path.join(__dirname, '../src/redux/reducers'),
            images: path.join(__dirname, '../src/images')
        }
    },
    devServer: {
        hot: true,
        open: true,
        port: 3500,
        contentBase: '../dist'
    }
};      

此時我們再次運作打包指令,結果如下:

01【React再造之旅】從0搭建一個完整的React項目(初稿)

它會删除掉上一次打包後的檔案,并進行重新打包。

以上兩部分的介紹是關于如何建立一個基于webpack的基礎項目架構,并對webpack基礎配置的介紹,到此為止我們的基礎架構已經搭建完成了,在其中我們配置了webpack的相關基礎配置資訊,同時也指定了項目的打包指令和啟動指令,接下來我們在項目中引入React,打造React項目架構。

三、內建React全家桶

1、內建react

內建react無非就是在項目架構中引入react和react-dom兩個依賴插件,首先我們來進行安裝,如下:

npm install react react-dom --save-dev      
01【React再造之旅】從0搭建一個完整的React項目(初稿)

安裝完成之後,我們在index.js中編寫React代碼,就是獨具特色的JSX文法,因為在剛開始時我們已經配置了相應的loader,是以後面項目啟動的時候,它是可以将我們裡面的JSX代碼編譯成ES5代碼的,如下:

import React from 'react';
import ReactDom from 'react-dom';

ReactDom.render(
    <div>X北辰北</div>,
    document.getElementById('xbeichen')
)      

然後在項目根目錄下的index.html檔案中添加如下代碼:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="xbeichen"></div>
</body>
</html>      

現在我們通過"npm run dev"來啟動項目,可以發現,此時我們将“X北辰北”字樣渲染在了我們的前端頁面,如下:

01【React再造之旅】從0搭建一個完整的React項目(初稿)

2、內建react-router-dom

配置好了基礎的React環境之後,我們接下來配置路由。首先是安裝依賴子產品,如下:

npm install react-router-dom --save-dev      
01【React再造之旅】從0搭建一個完整的React項目(初稿)

子產品安裝完成之後,我們在src目錄下建立一個pages檔案夾,然後在此檔案夾下建立兩個react元件,名稱分别為ComponentOne和ComponentTwo,分别添加如下代碼:

01【React再造之旅】從0搭建一個完整的React項目(初稿)
//元件一代碼
import React,{ Component } from 'react';

class ComponentOne extends Component {

    render () {
        return (
            <div>
                <h3>元件1</h3>
            </div>
        )
    }
}

export default ComponentOne;      
//元件二代碼
import React,{ Component } from 'react';

class ComponentTwo extends Component {

    render () {
        return (
            <div>
                <h3>元件2</h3>
            </div>
        )
    }
}

export default ComponentTwo;      

元件建立完成之後,我們在src目錄下建立一個router檔案夾,裡面建立一個routers.js檔案,用來進行路由子產品化配置,然後在此檔案中添加如下代碼:

01【React再造之旅】從0搭建一個完整的React項目(初稿)
import ComponentOne from '../pages/ComponentOne';
import ComponentTwo from '../pages/ComponentTwo';

let routes = [
    {
        path: '/',
        component: ComponentOne,
        exact: true
    }, {
        path: '/two',
        component: ComponentTwo
    }
];

export default routes;      

然後我們修改index.js檔案,引入react-router-dom子產品,進行路由配置,代碼如下:

import React from 'react';
import ReactDom from 'react-dom';
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
import routes from './router/routers';

ReactDom.render(
    <Router>
        <Link to="/">元件一</Link>
        <br />
        <Link to="/two">元件二</Link>

        {
            routes.map((value, key) => {
                if(value.exact) {
                    return <Route exact path={value.path} component={value.component} key={key} />
                }else {
                    return <Route path={value.path} component={value.component} key={key} />
                }
            })
        }
    </Router>,
    document.getElementById('xbeichen')
)      

此時我們已經完成了路由的配置,并且在此處還做了路由的子產品化配置,最後我們啟動項目,就可以看到下面的結果:

01【React再造之旅】從0搭建一個完整的React項目(初稿)

3、內建redux(後續更新)

4、內建Ant Design

此時Antd最新版本是4.0.1,那在此處我們就直接內建最新版。首先是安裝這個插件:

npm install antd --save-dev      
01【React再造之旅】從0搭建一個完整的React項目(初稿)

安裝完成之後,我們在index.js中引入它的css檔案,然後按照官網的元件執行個體代碼來将我們之前配置的路由點選元素換成Antd的按鈕元件,如下:

import React from 'react';
import ReactDom from 'react-dom';
import { Button } from 'antd';
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
import routes from './router/routers';
import 'antd/dist/antd.css';

ReactDom.render(
    <Router>
        <Link to="/">
            <Button type="primary">元件一</Button>
        </Link>
        <br /><br />
        <Link to="/two">
            <Button type="primary">元件二</Button>
        </Link>

        {
            routes.map((value, key) => {
                if(value.exact) {
                    return <Route exact path={value.path} component={value.component} key={key} />
                }else {
                    return <Route path={value.path} component={value.component} key={key} />
                }
            })
        }
    </Router>,
    document.getElementById('xbeichen')
)      

最後啟動項目,得到如下所示的結果:

01【React再造之旅】從0搭建一個完整的React項目(初稿)

這樣我們就簡單地完成了Antd的內建,具體的樣式優化大家可以後面慢慢調。

5、添加express服務接口,用axios打通前後端

在項目根目錄安裝express和axios,如下:

npm install express axios --save-dev      
01【React再造之旅】從0搭建一個完整的React項目(初稿)

其中Express是一個基于NodeJS的輕量級背景伺服器架構,axios是一個基于Promise的HTTP網絡請求庫。安裝完成之後,我們在項目根目錄下建立server檔案夾,然後在裡面建立一個server.js檔案,并添加如下代碼,用來初始化我們的背景伺服器:

01【React再造之旅】從0搭建一個完整的React項目(初稿)
const express = require('express');
const app = express();

app.get('/api/xuqw', (req, res) => {
    res.header('Access-Control-Allow-Origin', '\*');
    res.send({
        name: 'xbeichen',
        comurl: 'geov.online'
    });
});

app.listen(3000, () => {
    console.log('app listen 3000 port');
});      

然後進入server這個檔案夾,在這個檔案夾目錄下打開指令行工具,通過指令"node server.js"來啟動背景伺服器,然後在浏覽器中通過接口位址來通路測試,如下:

01【React再造之旅】從0搭建一個完整的React項目(初稿)
01【React再造之旅】從0搭建一個完整的React項目(初稿)

以上可看到,我們的背景是運作成功的。接下來我們在ComponentTwo.js中添加一個生命周期函數,在這個函數裡編寫axios代碼,讓這個元件完成加載時去請求我們的背景擷取資料,代碼如下:

import React,{ Component } from 'react';
import axios from 'axios';

class ComponentTwo extends Component {

    componentDidMount () {
        axios.get('http://localhost:3000/api/xuqw').then(res => {
            console.log(res);
        })
    }

    render () {
        return (
            <div>
                <h3>元件2</h3>
            </div>
        )
    }
}

export default ComponentTwo;      

然後我們啟動前端項目系統,并且啟動背景項目系統,點選元件二按鈕時路由會跳轉加載元件2,當它完成挂載後我們的生命周期函數就會自動執行,這時候axios就通過我們的背景接口去擷取資料,最後将擷取到的資料在浏覽器控制台列印,如下:

01【React再造之旅】從0搭建一個完整的React項目(初稿)

在請求過程中如果我們遇到跨域的問題,那就要在webpack配置檔案中進行配置跨域了,配置參考資訊如下:

01【React再造之旅】從0搭建一個完整的React項目(初稿)

以上就是在項目中建立系統背景,然後用axios打通前背景資料互動的全過程了。

至此呢,本篇文章也就結束了。

總結