從零開發一套完整的react項目開發環境

不管是工作需要還是面試加分,除了Vue相關技術以外,React技術棧也已經成爲了前端開發工程師必備的技術點。接下來,我將從零開發一套完整的React全家桶項目開發環境,提供給需要的同行小夥伴觀看也方便自己以後複習。篇幅很長,請需要的小夥伴耐心閱讀完必有所收穫!

快上車

倉庫地址

項目源碼地址:https://github.com/tangmengcheng/my-react-project.git

目標

我們本次需要通過 webpack4.x完成那些目標:

  1. ES6/7/8/9等高級語法轉換成ES5
  2. stylus/less/scss等css預處理器轉換成css
  3. 解析字體font、圖片(jpg、png…)等靜態資源
  4. 壓縮js、css等文件
  5. 自動添加css各大瀏覽器廠商前綴
  6. 定義環境變量
  7. 抽離公共代碼
  8. 項目熱更新和懶加載
  9. 區別生產環境和開發環境
  10. 每次打包刪除上一次打包記錄
  11. 集成React、React-Router-Dom、Redux等周邊生態
  12. 添加express服務接口、通過axios打通前後端

後續有時間,樓主將 Antd UI 框架也集成進來,完完整整的從零一行行代碼開發一個完整的React全家桶項目。

項目初始化

  1. 檢查 node 環境配置

先本地全局安裝node環境,react的運行是依賴於nodenpm的管理工具來實現的,node下載地址。下載好node之後,打開cmd管理工具,輸入node -v,回車,查看node版本號,出現版本號則說明安裝成功

node -v  npm -v

效果圖

  1. 初始化項目目錄

在命令行依次輸入:

mkdir my-react-project 新建項目目錄
cd my-react-project/ 切換到項目目錄
npm init 生成項目的一些信息,最終會生成一個package.json文件。注意:可以輸入npm init -y可以不用按回車
  1. 安裝 webpack

webpack 是一個模塊打包機,自動分析項目依賴的模塊以及一些瀏覽器不能直接轉換的高級語法等轉換成瀏覽器可以解析的 jscss文件等。在項目根目錄本地安裝webpack, 本項目將使用webpack4.x版本

npm install webpack webpack-cli -D

效果圖

  1. 初始化項目目錄和文件

在項目根目錄新建一下文件:

src: 存放項目源碼的目錄
index.js: 需要被 webpack 編譯的文件
build:存放項目的 webpack 配置文件
webpack.config.js 項目的webpack核心配置文件
index.html: 項目打包後自動將打包的文件添加在該文件裏面

效果圖

添加webpack配置文件的基本信息

  1. mode: 模式, development開發環境、production生產環境
  2. entry: 項目的打包的入口文件
  3. output: 項目的打包後輸出文件
  4. module: 模塊, 在webpack中所有文件皆模塊, 解析css、js、圖片以及字體圖標等
  5. plugins: 插件, 用來擴展webpack功能

在package.json文件 scripts 屬性中添加 運行 npm run build 即可打包

"build": "webpack --config ./build/webpack.config.js"

在index.js中添加測試代碼驗證webpack打包是否正確

function sum(a, b) {
    return a + b;
}
var sum = sum(1, 2)
console.log(sum)

如果項目dist目錄生成了一個bundle.js文件,說明webpack打包正確.
效果圖

配置核心功能

ES6/7/8/9等高級語法轉換成ES5

在index.js中添加ES6/7/8等高級語法的代碼測試代碼驗證webpack是否能將其轉換爲ES5等讓瀏覽器能夠解析的低級語法

  1. 安裝相關依賴
    npm install babel-loader @babel/core @babel/preset-env -D
    
    babel-loader 是將ES6等高級語法轉換爲能讓瀏覽器能夠解析的低級語法
    @babel/core 是babel的核心模塊,編譯器。提供轉換的API
    @babel/preset-env 可以根據配置的目標瀏覽器或者運行環境來自動將ES2015+的代碼轉換爲es5
    @babel/preset-react 用於解析 JSX
  1. 在項目根目錄創建babel.config.js文件

如果不創建該文件的話,也可以在webpack配置文件對應的規則中添加options即可。

babel配置文件

  1. 修改核心配置文件webpack.config.js文件

    {
        // src目錄下面以.js結尾的文件都要使用babel解析
        // cacheDirectory是用來緩存的,下次編譯加速
        test: /\.js$/,
        use: ['babel-loader?cacheDirectory=true'],
        include: path.join(__dirname, '../src')
    },
    

    然後運行npm run build,就可以看到我們輸入的ES6+等高級語法被轉換爲ES5了。
    注意babel-loader只會將 ES6/7/8等高級語法轉換爲ES5語法,但是對新api並不會轉換。比如Promise、Iterator、Set、Proxy、Symbol等全局對象,以及一些定義在全局對象上的方法(比如Object.assign)都不會轉碼。此時,我們必須使用babel-polyfill,爲當前環境提供一個墊片。

    npm install @babel/polyfill -S
    
    babel-polyfill是解決babel-loader不能對新的api進行轉換爲當前環境添加一個墊片

重點:當我們執行打包後,打包的文件裏含有大量的重複代碼,那麼我們需要提供統一的模塊化的helper來減少這些helper函數的重複輸出。

    npm install @babel/runtime @babel/plugin-transform-runtime @babel/plugin-syntax-dynamic-import -D
    
    @babel/runtime 就是提供統一的模塊化的helper, 使用能大大減少打包編譯後的體積
    @babel/plugin-transform-runtime它會幫我自動動態require @babel/runtime中的內容
    注意:還有一些常見的babel:
    @babel/plugin-proposal-decorators將es6+中更高級的特性轉化---裝飾器
    @babel/plugin-proposal-class-properties將es6中更高級的API進行轉化---類

效果圖

  1. 在index.js中編寫ES6+等高級語法
    let fn = () => {
        console.log('箭頭函數')
    }
    fn()
    
    let promise = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(123)
        }, 1000)
    })
    promise.then(res => {
        console.log(res);
    })

stylus/less/scss等css預處理器轉換成css

以下就以less預處理器爲例,詳細介紹下其用法,其餘兩種類似:

  1. 安裝相關依賴
    npm install stylus stylus-loader less less-loader sass-loader node-sass css-loader style-loader -D
    
    css-loader主要的作用是解析css文件, 像@import等動態語法
    style-loader主要的作用是解析的css文件渲染到html的style標籤內
    stylus、less、sass是CSS的常見預處理器
    stylus-loader、less-loader、sass-loader主要是將其對應的語法轉換成css語法
  1. 修改核心配置文件webpack.config.js

    效果圖

  2. 添加index.less文件

    @color: red;
    #div1 {
        color: @color;
        font-size: 36px;
    }

效果圖
注意:CSS3 的許多特性來說,需要添加各種瀏覽器兼容前綴,開發過程中,這樣加太麻煩,postcss 幫你自動添加各種瀏覽器前綴

    npm install postcss-loader autoprefixer -D
    
    postcss-loader autoprefixer 處理瀏覽器兼容,自動爲CSS3的某些屬性添加前綴

效果圖

解析字體font、圖片(jpg、png…)等靜態資源

項目中通常會使用圖片、字體等靜態資源,不使用對應的loader項目會報錯

  1. 安裝相關依賴
    npm install file-loader url-loader -D
    
    file-loader可以用來幫助webpack打包處理一系列的圖片文件;比如:.png 、 .jpg 、.jepg等格式的圖片。打包的圖片會給每張圖片都生成一個隨機的hash值作爲圖片的名字
    url-loader封裝了file-loader,它的工作原理:1.文件大小小於limit參數,url-loader將會把文件轉爲Base64;2.文件大小大於limit,url-loader會調用file-loader進行處理,參數也會直接傳給file-loader
  1. 修改核心配置文件webpack.config.js

    效果圖

壓縮打包後的js、css等文件

由於項目打包後會生成很多js文件,代碼之間有很多空格、引號等,如果我們將其去掉,這樣會大大減少打包的體積

  1. 安裝相關依賴
    npm install mini-css-extract-plugin -D
    // or 或
    npm install extract-text-webpack-plugin@next -D // 不推薦使用
    npm install optimize-css-assets-webpack-plugin -D
    npm install uglifyjs-webpack-plugin -D
    // 擴展 消除未使用的css
    npm install purify-webpack purify-css -D

注意:在生產模式下,webpack自動將JS進行壓縮。MiniCssExtractPlugin 推薦只用於生產環境,因爲該插件在開發環境下會導致HMR功能缺失,所以日常開發中,還是用style-loader。

  1. 修改核心配置文件webpack.config.js

    修改配置

抽離公共代碼

在我們寫好代碼後打包,由於不管是css還是js都有很多公共的部分。如果不將其抽離的話會使打包的項目體積過大,影響頁面性能

注意:在webpack4以後的版本都使用內置的SplitChunksPlugin插件來進行公共部分的代碼提取。

// 核心配置
optimization: {
    splitChunks: {
        cacheGroups: {
            //打包公共模塊
            commons: {
                chunks: 'initial', //initial表示提取入口文件的公共部分
                minChunks: 2, //表示提取公共部分最少的文件數
                minSize: 0, //表示提取公共部分最小的大小
                name: 'commons' //提取出來的文件命名
            }
        }
    }
}

添加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')
    }
}

代碼熱更新

  1. 安裝依賴

    npm install webpack-dev-server -D
    npm install html-webpack-plugin -D
    html-webpack-plugin主要有兩個作用: 
    1. 爲html文件中引入的外部資源如script、link動態添加每次compile後的hash,防止引用緩存的外部文件問題       
    2. 可以生成創建html入口文件
    
  2. 修改核心配置文件webpack.config.js文件

    修改配置

  3. 在package.json添加:

    "dev": "webpack-dev-server --config ./build/webpack.config.js"
    

每次打包刪除上一次打包記錄

  1. 安裝相關依賴
    npm install clean-webpack-plugin -D
    
    clean-webpack-plugin是刪除webpack打包後的文件夾以及文件
  1. 修改核心配置文件webpack.config.js

    效果圖

添加該插件後,每次只需打包時都會刪除前一次的打包記錄。

區別生產環境和開發環境

在我們開發不管是Vue項目還是React項目,都會分爲開發環境和生產環境。這樣是因爲兩個環境會有一些不同:

一、開發環境:

  1. 模塊熱更新
  2. sourceMap
  3. 接口代理
  4. 代碼檢查規範

二、生產環境:

  1. 提取公共代碼
  2. 代碼壓縮
  3. 去除無用的代碼

在webpack中通過mode選項來區分開發環境(development)和生產環境(production)

優秀

集成react全家桶

集成react步驟

  1. 安裝依賴
npm install react react-dom -S
  1. 修改index.js,引入react
import React from 'react';
import ReactDom from 'react-dom';

ReactDom.render(
    <div>石小明</div>,
    document.getElementById('app')
)
  1. npm run dev 運行結果

運行結果

集成react-router-dom步驟

  1. 安裝依賴

    npm install react-router-dom -S
    

在項目根目錄添加一個page文件夾存放頁面,在新建兩個頁面作爲頁面跳轉後的文件

  1. 添加router.js文件
import React from 'react'
import { Route, Switch } from 'react-router-dom'

// 引入頁面
import Home from './pages/home/index'
import Page from './pages/page/index'

// 路由
const getRouter = () => {
    <Switch>
        <Route exact path="/" component={Home} />
        <Route exact path="/page" component={Page} />
    </Switch>
}

export default getRouter;
  1. 添加一個主文件
import React from 'react'
import { Link } from 'react-router-dom'

export default () => {
    return (
        <div>
            <ul>
                <li><Link to="/">首頁</Link></li>
                <li><Link to="/page">Page頁</Link></li>
            </ul>
        </div>
    )
}

最後,運行npm run dev就可以看到我們兩個頁面都可以相互點擊跳轉了。

集成redux步驟

  1. 安裝依賴

    npm install redux react-redux -S
    
    redux 是一個狀態管理器,類似於Vuex
    react-redux 是連接Redux 與 React兩個框架
    
  2. 新建核心配置文件store

第一、新建store核心配置文件

import {createStore, combineReducers} from 'redux'
import counter from 'reducers/counter'
import userInfo from 'reducers/userInfo'

let store = createStore(combineReducers({counter, userInfo}))

export default store

第二:將store集成在項目初始化的根目錄

import ReactDom from 'react-dom'
import { Provider } from 'react-redux'

import store from './redux/store'

ReactDom.render(
    <Provider store={store}>
        ...
    </Provider>,
    document.getElementById('app')
)

注意:通過將跟組件注入store,所有的文件都可以獲取的到store裏的內容

  1. 新建actions 和reducers文件夾

注意:actions文件夾裏存放是行爲,而reducers文件夾存放的是具體的操作

以計數例子爲例:

第一:在actions裏新建counter.js

在該文件中,我們定義了幾種type,分別代表加、減、清零。然後定義了幾個action創建函數

// 防止action的type值重複,給相應的前面加個prefix
export const INCREMENT = "counter/INCREMENT"
export const DECREMENT = "counter/DECREMENT"
export const RESET = "counter/RESET"

export function increment() {
    return {
        type: INCREMENT
    }
}
export function decrement() {
    return {
        type: DECREMENT
    }
}
export function reset() {
    return {
        type: RESET
    }
}

注意:在項目大的時候,我們需要將type類型的值單獨存放在一個文件裏,方便管理

第二:在reducers裏新建counter.js

在該文件中,我們初始化了最初的狀態,和定義了一個具體的處理邏輯的方法

import {INCREMENT, DECREMENT, RESET} from '../actions/counter';

// 初始化state
const initState = {
    count: 0
}

// reducer 接收兩個參數,state,action 返回新的state
export default function reducer(state = initState, action) {
    switch (action.type) {
        case INCREMENT:
            return {
                count: state.count + 1
            };
        case DECREMENT:
            return {
                count: state.count - 1
            }
        case RESET:
            return {
                count: 0
            }
        default:
            return state
    }
}

注意:reducer是一個純函數,它接收初始值和action,返回一個新的狀態。如果沒有找對對應的type,默認返回初始值

  1. 在項目中使用

我們在page目錄下新建一個counter目錄,用於存放測試redux功能的目錄

import React, { Component } from 'react'
import { connect } from 'react-redux'
import { increment,decrement, reset } from 'actions/counter'

class Counter extends Component{

    render() {
        return (
            <div>
                <span>當前計數值爲:{this.props.counter.count}</span>
                <button onClick={() => this.props.increment()}>增加</button>
                <button onClick={() => this.props.decrement()}>減少</button>
                <button onClick={() => this.props.reset()}>重置</button>
            </div>
        );
    }
}

export default connect(state => state, dispatch => ({
    increment: () => {
        dispatch(increment())
    },
    decrement: () => {
        dispatch(decrement())
    },
    reset: () => {
        dispatch(reset())
    }
}))(Counter)

注意:react-redux中提供了一個connect方法,用於連接react組件和redux。

通過該語法:connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])可知該方法各個參數的意思;

  1. mapStateToProps:意思是將Store中的狀態State,以屬性Props的方式傳給組件
  2. mapDispatchToProps:意思是將disptach方法,以屬性Props的方式傳個組件
  3. mergeProps:不常用
  4. options:不常用

最後就可以執行npm run dev, 在界面上操作數字加減的例子了。

運行效果圖

添加express服務接口、通過axios打通前後端

  1. 安裝依賴

    npm install express axios -S
    
    express Express 是一個基於 Node.js 平臺的極簡、靈活的 web 應用開發框架
    axios 是一個基於Promise的HTTP網絡請求庫
    
  2. 新建server文件夾

改文件用於存放編寫提供前臺調用的接口

const express = require('express')
const app = express()

app.get('/api/user', (req, res) => {
    res.header('Access-Control-Allow-Origin', '*')
    res.send({
        name: 'tmc',
        age: 24
    })
})

app.listen(3000, () => {
    console.log('app listen port 3000')
})
  1. 啓動服務

通過node來啓動這個本地服務,也可以在webpack配置一條命令來啓動。

執行:node server.js
在瀏覽器上就可以輸入 http://localhost: 3000/api/user 來調用接口
  1. 在組件中調用該接口

新建一個/home/test.js 組件用於測試調用接口。

// home/test.js
import React, { Component } from 'react'
import axios from 'axios'

export default class Home extends Component{
    componentDidMount() {
	axios.get('http://localhost:3000/api/user').then(res => {
            console.log(res);
        })
    }

    render() {
        return (
            <div>
                this is home page.
            </div>
        )
    }
}

注意:在使用組件進行接口調用時,一定要在webpack配置文件中進行跨域的處理

// webpack.dev.config.js

devServer: {
    contentBase: path.join(__dirname, '../dist'),
    compress: true, // gzip壓縮
    // host: '0.0.0.0', // 允許IP訪問
    hot: true, // 熱更新
    historyApiFallback: true, // 解決啓動後刷新404
    port: 8888,
    proxy: { // 配置服務器代理 http://localhost:3000/api/user
        '/api': {
            target: 'http://localhost:3000',
            pathRewrite: {
                '^/api': '/api'
            },
            changeOrigin: true // 讓target參數是域名
            // secure: false 設置支持https協議代理
        }
    }
}

最後重新運行npm run dev,打開瀏覽器,加載到剛纔添加的這個組件後就可以看到調用接口返回的信息

接口返回結果

總結

通過從零一行一行配置一個完整的React項目開發環境,其實最重要的就是對Webpack的運用。瞭解並熟知Webpack的各項常見配置,不管是對個人能力的提升還是面試跳槽都有極大的好處。對希望提升自己的小夥伴一定要認真閱讀,手動跟着上述文檔一步一步敲一遍,當完成時,必定會有極大的收穫!加油💪💪💪

加油

最後

如果本文對你有幫助得話,給本文點個贊❤️❤️❤️

歡迎大家加入,一起學習前端,共同進步!
cmd-markdown-logo
cmd-markdown-logo

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章