React腳手架架構設計

1、前言

React如今已經有不少腳手架工具,如create-react-app,支持一鍵建立一個React應用項目結構,很方便,可是享受方便的同時,也失去了對項目架構及技術棧完整學習的機會,並且一般腳手架建立的應用技術架構並不能徹底知足咱們的業務需求,須要咱們本身修改,完善,因此若是但願對項目架構有更深掌控,最好仍是從0到1理解一個項目。css

2、項目結構與技術棧

文件目錄

此次應用架構設計不使用任何腳手架,須要本身建立每個文件,引入每個技術和三方庫,最終造成完整的應用,包括選擇的完整技術棧。 項目結構圖以下:1. src爲應用源代碼目錄; 2. webpack.config.js爲webpack配置入口文件; 3. package.json爲項目依賴管理文件; 4. .babelrc文件,babel的配置文件,使用babel編譯React和JavaScript代碼; 5.README.md爲項目說明文檔; 6.bird-config是gulp-bird轉發工具的配置文件;7.postcss.config.js是postcss的配置文件html

文件目錄
文件目錄

Featurenode

  • 能夠解析JSX語法
  • 能夠解析ES6語法新特性
  • 支持SCSS預處理器
  • 單獨分離CSS樣式文件
  • 支持文件MD5戳,解決文件緩存問題
  • 支持圖片、圖標字體等資源的編譯
  • 區分開發環境和生產環境
  • 分離業務功能代碼和公共依賴代碼

技術棧

對整個技術棧進行分析,要考慮哪幾個方面呢?react

  1. react和react-dom庫是項目前提;
  2. react路由;
  3. 應用狀態管理容器;
  4. 如何優雅地發送ajax請求;
  5. 打包上線發版,測試環境線上環境
  6. css預編譯及後處理,考慮兼容性寫法;
  7. 採用ES6寫碼,輔助工具,寫代碼更容易;
  8. 此外,每每後端開發好的接口會部署在測試環境,而不是本地,所以還須要考慮聯調時的代理轉發。

根據以上劃分決定選用如下第三方庫和工具構成項目的完整技術棧:jquery

  1. react,react-dom;
  2. react-router管理應用路由;
  3. mobx做爲JavaScript狀態容器,mobx-react將React應用與mobx鏈接,Mobx相關詳情請參見Mobx官方文檔
  4. axios庫發送ajax請求,解決接口調用問題,請參見Axios
  5. webpack配置;
  6. 安裝postcss的autoprefixer;
  7. 須要安裝babel,還可使用lodash等可選輔助類,工具類庫提高開發效率;
  8. 代理轉發工具選擇的是百度BEFE團隊開發的gulp-bird

組件劃分

React組件化開發原則是組件負責渲染UI,組件不一樣狀態對應不一樣UI,一般遵循如下組件設計思路:webpack

  1. 路由組件:負責項目路由,可嵌套;
  2. 佈局組件:僅僅涉及應用UI界面結構的組件,不涉及任何業務邏輯,數據請求及操做;
  3. 容器組件:負責獲取數據,處理業務邏輯,一般在render()函數內返回展現型組件;
  4. UI組件:指抽象出的可重用的UI獨立組件,一般是無狀態組件;

以本項目section的組件劃分爲例,index.js是入口文件,同時也承擔了路由組件的角色,layout是佈局組件,僅僅負責UI界面結構,homeindex是容器組件,是首頁的業務邏輯,islider則是輪播圖的獨立組件,可複用。 ios

組件劃分
組件劃分

3、項目搭建步驟

3.1準備工做

  • git新建項目
git init
touch README
git add README
git commit -m'first commit'
git remote add origin git@XXXXX.git
git push origin master

複製代碼
  • npm安裝react相關依賴包

採用npm install XXX -savenpm install XXX -save-dev配置安裝相關npm包,若是直接複製,能夠直接npm install安裝git

{
    "name": "zhiqiu",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "scripts": {
        "build": "./node_modules/.bin/webpack --config webpack.config.js --env.NODE_ENV=production",
        "dev": "./node_modules/.bin/webpack --config webpack.config.js --env.NODE_ENV=development",
        "qa": "./node_modules/.bin/webpack --config webpack.config.js --env.NODE_ENV=qa",
        "start": "./node_modules/.bin/webpack-dev-server --config webpack.config.js --env.NODE_ENV=local"
    },
    "proxy": {
        "/mansion": {
            "target": "http://localhost:8009"
        }
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "devDependencies": {
        "axios": "^0.18.0",
        "babel-cli": "^6.26.0",
        "babel-core": "^6.26.0",
        "babel-loader": "^7.1.4",
        "babel-plugin-transform-decorators-legacy": "^1.3.4",
        "babel-polyfill": "^6.26.0",
        "babel-preset-env": "^1.6.1",
        "babel-preset-es2015": "^6.24.1",
        "babel-preset-react": "^6.24.1",
        "babel-preset-stage-2": "^6.24.1",
        "classnames": "^2.2.5",
        "clean-webpack-plugin": "^0.1.19",
        "copy-webpack-plugin": "^4.5.1",
        "css-loader": "^0.28.11",
        "extract-text-webpack-plugin": "^3.0.2",
        "file-loader": "^1.1.11",
        "html-webpack-inline-source-plugin": "0.0.10",
        "html-webpack-plugin": "^3.2.0",
        "islider": "^0.1.0",
        "mobx": "^4.1.0",
        "mobx-react": "^5.0.0",
        "node-sass": "^4.8.3",
        "path": "^0.12.7",
        "react": "^16.2.0",
        "react-dom": "^16.2.0",
        "react-router": "^4.2.0",
        "react-router-dom": "^4.2.2",
        "sass-loader": "^6.0.7",
        "style-loader": "^0.20.3",
        "url-loader": "^1.0.1",
        "webpack": "^3.10.0",
        "webpack-bundle-analyzer": "^2.11.1",
        "webpack-dev-server": "^2.11.2"
    },
    "dependencies": {
        "autoprefixer": "^8.6.4",
        "babel-plugin-lodash": "^3.3.4",
        "gulp-bird": "^0.2.4",
        "hi-ui": "0.0.9",
        "islider.js": "^2.2.2",
        "jquery": "^3.3.1",
        "lodash": "^4.17.10",
        "postcss-loader": "^2.1.5",
        "vconsole": "^3.2.0"
    }
}
複製代碼

3.2 路由和數據狀態管理

路由安裝mobx-react,react-router模塊; React Router是完整的React路由解決方案,也是開發React應用最常使用的路由管理庫,它提供簡單的API,以聲明式方式實現強大的路由功能,諸如按需加載,動態路由等。 1.聲明式:語法簡潔,清晰; 2.按需加載:延遲加載,根據使用須要判斷是否須要加載; 3.動態路由:動態組合應用路由結構,更靈活,更符合組件化開發模式;github

/**
 * @file index 入口頁面
 * @author guoyueting
 */
import React from 'react';
import {render} from 'react-dom';
import {HashRouter, Route, Redirect} from 'react-router-dom';
import {Provider} from 'mobx-react';
import storeTree from './storeTree';
import 'section/common/scss/common.scss';
import Layout from 'section/layout/layout.js';

class App extends React.Component {
    render() {
        return (
            <Provider {...storeTree} >
                <HashRouter>
                    <Route path="/" component={Layout}/>
                </HashRouter>
            </Provider>
        );
    }
}

render(<App/>, document.getElementById('app'));
複製代碼

數據狀態管理,安裝mobx,肯定storetreeweb

/**
 * @file storeTree 整個app的狀態樹
 * @author guoyueting
 */
'use strict';

import {observable, action, extendObservable, runInAction} from 'mobx';
import NavState from 'section/nav/navStore';
import iSliderState from 'section/homeindex/component/islider/isliderStore';
import hotPointState from 'section/homeindex/component/hotpoint/hotpointStore';
import recommendState from 'section/homeindex/component/recommend/recommendStore';

import serviceState from 'section/service/serviceStore';
import repairState from 'section/myrepair/myrepairStore';


class AppState {
	// 全局store
	// ...
}

export default {
    AppState: new AppState(),
    NavState: new NavState(),
    iSliderState: new iSliderState(),
    hotPointState: new hotPointState(),
    recommendState: new recommendState(),
    serviceState: new serviceState(),
    repairState: new repairState()
}
複製代碼

@observer 函數/修飾器用於react組件。經過mobx-react依賴包來提供。它經過mobx.autorun來包裝了組件的render函數,以確保組件的render函數在任何數據的更改是強制從新渲染。

Autorun是用在一些你想要產生一個不用觀察者參與的被動調用函數裏面。當autorun被使用的時候,一旦依賴項發生變化,autorun提供的函數就會被執行。

action是任何改變狀態的事物。

/**
 * @file serviceStore
 * @author guoyueting
 */
'use strict';

import {observable, action, runInAction} from 'mobx';
import _ from 'lodash';
import * as model from 'src/itsm/model/model';
import {getSearchParam} from 'section/common/js/utils';

export default class serviceState {
    @observable serviceList = [];

    @action getServiceList() {
        let params = {
            mappedAppKey: getSearchParam('appKey')
        };
        model.getServiceListList(params).then(data => {
            runInAction(()=>{
                this.serviceList = _.get(data, 'data.data');
            });
        });
    }

    @action serviceClick(id) {
        let params = {
            mappedAppKey: getSearchParam('appKey'),
            id: id
        };
        model.serviceClick(params);
    }
}
複製代碼

3.3 Axios庫發送ajax請求

安裝axios,並經過get/post/put/delete等方式請求接口,對於json數據,通常在post請求進行數據格式轉換,並在請求頭部設置:

/**
 * @file  接口文件
 * @author guoyueting
 */
import axios from 'axios';
let axiosConfig = {
    headers: {
        'Content-Type': 'application/json;charset=UTF-8',
        'Access-Control-Allow-Origin': '*'
    }
};
// 獲取列表
export let getHotList = function (data) {
    return axios.post('/rdwtv2/api/hot', JSON.stringify(data), axiosConfig);
};
複製代碼

3.4 代理轉發gulp-bird

gulp-bird是由百度BEFE團隊開發的代理轉發工具,並非一個gulp插件,在bird基礎上進行了一些優化併發布到了npm,配置方法和bird同樣。 配置目標服務器host和port等,參考以下。

/**
 * @file  bird-config.js
 * @author guoyueting
 */
var bird = require('./node_modules/gulp-bird/index');
// 靜態服務器配置,可同時配置多個,域名需host到127.0.0.1
var server = {
    '8009': {
        // 靜態文件根目錄
        'basePath': './src/',
        // 是否開啓調試模式,true(表示server端不緩存),false(反之)
        'debug': true
        // 忽略的靜態文件請求,與此正則匹配的請求將直接走轉發規則(可選配置)
        // 'ignoreRegExp': /\/js\/urls\.js/g

    }
};
// 轉發規則——靜態服務器沒有響應的或者忽略的請求將根據一下規則轉發
var transpondRules = {
    '8009': {
        // 目標服務器的ip和端口,域名也可,但注意不要被host了
        targetServer: {
            'port': '8680',
            // 'port': '8080',
            'host': 'http://m1-ite-hidev04.m1.baidu.com',
            // 'host': 'cp01-ps-dev373-liuchao31.epc.baidu.com',
            // 當爲true時,若是cookie or header中有相同key,則替換
            'replaceHeaders': true,
            'headers': {
                    'cookie': ''}
            }
        // 特殊請求轉發,可選配置,內部的host、port和attachHeaders爲可選參數
        // regExpPath: {
            // '/oa-frontend-apply-1.0.0-SNAPSHOT': {
            //     'host': 'cp01-dev-heliping.epc.baidu.com/',
            //     'port': '8080',
            //     //'attachHeaders': {'app-id': 5},
            //     'path': '\/'
            // }
        // }
    },
    'ajaxOnly': false
};

var toolsConf = {
    weinre: {
        // 和移動調試工具條中的vconsole衝突, 當爲true時vconsole自動關閉
        open: false,
        port: 8009
    },
    // 移動端調試工具條,PC端開發可關閉
    showTools: false
};

bird.start(server, transpondRules, toolsConf);

複製代碼

3.5webpack配置打包

下面是一個較爲基礎的webpack文件配置。entry是配置模塊的入口,webpack執行構建的第一步將從入口開始搜尋及遞歸解析出全部入口依賴的模塊;output定義了打包後的輸出文件名、路徑;module配置模塊的讀取和解析規則,一般用來配置loader;resolve配置Webpack如何尋找模塊對應的文件;Plugins用於擴展webpack的功能,幾乎全部Webpack沒法直接實現的功能都能在社區找到開源的Plugin去解決。

const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const path = require('path');
const webpack = require('webpack');

module.exports = function(env) {
    console.log(env);
    return {
        entry: {
            'index': './src/itsm/index.js'
        },
        output: {
            filename: 'js/[name]-[chunkhash].js',
            path: path.resolve(__dirname, './build'),
            chunkFilename: '[name].[chunkhash:4].child.js'
        },
        module: {
            rules: [{
                test: /(\.jsx|\.js)$/i,
                use: [{
                    loader: 'babel-loader'
                }]
                // exclude: /node_modules/
            },
            {
                test: /(\.scss|\.sass)$/i,
                use: ExtractTextPlugin.extract({
                    fallback: 'style-loader',
                    use: [{
                        loader: 'css-loader',
                        options: {
                            module: false,
                            minimize: true
                        }
                    }, {
                        loader: 'postcss-loader',
                    }, {
                        loader: 'sass-loader',
                        options: {
                            sourceMap: false
                        }
                    }]
                })
            },
            {
                test: /(\.png|\.jpg|\.jpeg|\.gif)$/i,
                use: [{
                    loader: 'url-loader',
                    options: {
                        limit: 100
                    }
                }]
            }]
        },
        resolve: {
            alias: {
                'src': path.resolve(__dirname, 'src'),
                'section': path.resolve(__dirname, 'src/itsm/section'),
                'img': path.resolve(__dirname, 'src/itsm/img'),
                'modules': path.resolve(__dirname, 'node_modules/islider.js/build')
            }
        },
        plugins: [
            new CleanWebpackPlugin(['./build']),
            new webpack.DefinePlugin({
                'process.env.NODE_ENV': JSON.stringify(env.NODE_ENV || 'development'),
                'isDev': JSON.stringify(env.isDev || 'true')
            }),
            new ExtractTextPlugin('style.css'),
            new HtmlWebpackPlugin({
                template: './src/itsm/index.html',
                filename: './index.html',
                chunks: ['index']
            })
        ],
        devServer: {
            host: '0.0.0.0',
            port: 9822,
            proxy: {
                '/rdwtv2': 'http://localhost:8009'
            }
        }
    }
}
複製代碼

3.6打包指令配置

配置不一樣環境的變量值,生產環境,QA環境,開發環境某些變量可能須要根據環境進行配置,而後用webpack的definePlugin插件,定義全局變量,能夠保證在和環境有關的變量取值的正確性。

"scripts": {
    "build": "./node_modules/.bin/webpack --config webpack.config.js --env.NODE_ENV=production",
    "dev": "./node_modules/.bin/webpack --config webpack.config.js --env.NODE_ENV=development",
    "qa": "./node_modules/.bin/webpack --config webpack.config.js --env.NODE_ENV=qa",
    "start": "./node_modules/.bin/webpack-dev-server --config webpack.config.js --env.NODE_ENV=local"
}
複製代碼

4、將來改進

將來架構設計還須要完善的點:

  1. Immutable數據,考慮引入Immutable.js;
  2. 測試,考慮jest集成應用測試
  3. 函數庫,如ramda,underscore;
  4. 調試工具,reactotron,react-devtools

5、Demo地址

最後,附上demo地址:https://github.com/guoyueting/react-mobx-seed

原文地址:https://guoyueting.github.io/2018/07/01/React%E8%84%9A%E6%89%8B%E6%9E%B6/

相關文章
相關標籤/搜索