React+Webpack+React-Router4 實例教程

本教程將帶大家一起學習使用react,首先我們會搭建開發環境(react+webpack),然后講解react相關的知識,再講到使用axios發起網絡請求,使用React-Router4作為前端路由,webpack打包生成app,最后使用nginx部署。


開發環境搭建

mkdir myreactapp
cd myreactapp
npm init

一路回車初始化package.json

安裝react react-dom
npm install react react-dom --save

安裝webpack 打包工具
npm install webpack webpack-cli --save-dev

安裝webpack-dev-server 調試服務器
npm install webpack-dev-server --save-dev

安裝babel插件 將ES6代碼轉換成ES5
npm install babel-core babel-loader babel-preset-es2015 babel-preset-react --save-dev

創建app文件夾,開發的代碼放在里面
創建build文件夾,打包后的文件放在里面
在app文件夾創建main.js,為app入口文件

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

ReactDOM.render(
    <h1>hello,first react app</h1>,
    document.getElementById('app')
);

在build文件夾創建index.html

<!DOCTYPE html>
<html>
<head lang="zh-CN">
    <meta charset="UTF-8">
    <title>my first react app</title>
</head>
<body>
<div id="app"></div>
<script src="/bundle.js"></script>
</body>
</html>

創建webpack.config.js并寫入內容


var path = require('path');
var webpack = require('webpack');
module.exports = {
    entry: ['webpack/hot/dev-server', path.resolve(__dirname, './app/main.js')],
    output: {
        path: path.resolve(__dirname, './build'),
        filename: 'bundle.js',
        publicPath: '/'
    },
    devServer: {
        inline: true,
        port: 3000
    },
    module: {
      rules: [
           {
               test: /\.js?$/,
               exclude: /(node_modules|bower_components)/,
               loader: 'babel-loader',
           }
        ],
    },
    plugins: [
        new webpack.HotModuleReplacementPlugin()
    ]
};

根目錄創建.babelrc文件 這個文件是用來設置轉碼的規則和插件的


{
  "presets": ["es2015", "react"],
  "env": {
    "development": {

        }
  }
}

修改package.json scripts節點
添加"start": "webpack-dev-server --devtool eval --progress --colors --content-base build"

輸入npm start,啟動服務器,瀏覽器輸入http://localhost:3000 ,驗證是否出現hello,first react app

點擊F12審查元素

確實是我們在main.js里面寫的h1標簽和文字,加載到了id為app的dom節點里面

接下來我們創建兩個組件,分別是index和index_section,在app文件夾下創建components文件夾,當然如果項目大的話可以按頁面存放,即再建一個文件夾index代表這是index頁面的組件,建一個public文件夾放置公共組件,這里只新建index.js和index_section.js演示,
請注意,組件取名必須以大寫字母開頭。

import React from 'react'
import IndexSection from './index_section'

export default class Index extends React.Component{
  constructor(props){
    super(props);
  }
  render(){
    return (
      <div>
          <h1>這是index.js</h1>
          <IndexSection/>
      </div>
    );
  }
}
import React from 'react'

export default class IndexSection extends React.Component{
  constructor(props){
    super(props);
  }
  render(){
    return (
      <section>
          <h1>這是index_section.js</h1>
      </section>
    );
  }
}

修改main.js

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

import Index from './components/index';

ReactDOM.render(
    <Index/>,
    document.getElementById('app')
);

點擊保存后,網頁自動刷新

當然,你也可以把index_section.js寫到index.js里面,注意,每個js文件都只能存在一個export default,所以新的index.js為

import React from 'react'

class IndexSection extends React.Component{
  constructor(props){
    super(props);
  }
  render(){
    return (
      <section>
          <h1>這是index.js里面的index_section組件</h1>
      </section>
    );
  }
}

export default class Index extends React.Component{
  constructor(props){
    super(props);
  }
  render(){
    return (
      <div>
          <h1>這是index.js</h1>
          <IndexSection/>
      </div>
    );
  }
}

props

react是單向是數據流,使用props實現父組件向子組件傳值
props被用作初始化狀態,當一個組件初始化之后,它的props是只讀的,只有通過父組件重新渲染才可以把新的props傳入組件中

上面的Index組件為父組件,IndexSection為子組件,我們向子組件傳遞一些字符串
在Index的render方法里面修改

  render(){
    return (
      <div>
          <h1>這是index.js</h1>
          <IndexSection name="子組件prop:name" age="子組件prop:age"/>
      </div>
    );
  }

在IndexSection的render方法log一下它收到的值,使用console.log(this.props)輸出,再在render方法的return里面輸出一下它的這兩個值

  render(){
    console.log(this.props);
    return (
      <section>
          <h1>這是index.js里面的index_section組件</h1>
          <h1>{this.props.name}</h1>
          <h1>{this.props.age}</h1>
      </section>
    );
  }


state

state里面保存組件自身的狀態,在組件初始化的時候可以設定組件默認的state,在IndexSection的constructor方法里面設置它的默認state,并在render方法輸出

  constructor(props){
    super(props);
    this.state = {name:"IndexSection自身的state:name",age:"IndexSection自身的state:age"};
  }
  render(){
    console.log(this.state);
    return (
      <section>
          <h1>這是index.js里面的index_section組件</h1>
          <h1>{this.state.name}</h1>
          <h1>{this.state.age}</h1>
      </section>
    );
  }


與props不同的是,組件的state是可以改變的,通過使用this.setState()方法來修改組件的state,傳入的第一個參數可以是一個json對象:this.setState({name:"shane",sex:"male"}); ,這里可以傳入初始state里面存在的屬性,也可以傳入一個不存在的屬性。在生命周期那里我們會驗證該方法。
第一個參數也可以是一個函數

setState((prevState, props) => {
      return {"..." :" ...",...};
});

prevState為更新前的state,props為當前的props,return的為修改后的state

setState({a:1},()=>...)還可以接受第二個參數,為一個函數,在setState()調用完成后調用,相當于生命周期的componentDidUpdate
setState()方法通常是異步的,并且連續多次調用 setState 來更新同一個字段時,只有最后一次setState()才會生效。如果setState()后需要立即獲取更新后的數據,可以使用setTimeout函數來延遲0秒或者componentDidUpdate方法來獲取。
但是請注意,在一些方法里面不可以使用this.setState(),錯誤使用將會造成循環調用,造成瀏覽器卡死崩潰,將在生命周期里面講到

生命周期

每一個組件都有若干個生命周期的方法(在進程中的你可以重寫在特殊的時刻),方法中帶Will前綴在某件事情發生之前被調用,帶Did的前綴的在某件事發生之后被調用。

mounting

以下方法將被調用當一個實例組件被創建并插入到dom中的時候

constructor(props) //使用super(props) 后構造函數里面才可以使用this.props ,生命周期中只執行一次
componentWillMount() //組件即將掛載,生命周期中只執行一次
render() //渲染,生命周期中可執行多次
componentDidMount()  //組件第一次掛載完成,生命周期中只執行一次
updating

屬性或者狀態的改變會觸發更新,以下方法會在組件正進行重新渲染的過程中觸發。

componentWillReceiveProps(nextprops ) //將收到新的props,nextprops為新的props,生命周期中可執行多次
shouldComponentUpdate(nextProps, nextState ) //比較新舊props和state返回true/false來控制是否更新,生命周期中可執行多次
componentWillUpdate(nextProps, nextState) //組件將更新,生命周期中可執行多次
render() //渲染,生命周期中可執行多次
componentDidUpdate(prevProps, prevState) //組件更新完成,生命周期中可執行多次

unmounting

該方法將在組件正被被移除dom的時候觸發

componentWillUnmount() //組件將卸載,生命周期中只執行一次
error handing

2017-09-27 React 16 新加入

該方法將被調用當渲染過程,生命周期方法,或者任何子組件構造出錯的時候

componentDidCatch(error,info) //捕捉組件的邊界錯誤,生命周期中只執行一次

更改我們的index.js來驗證部分生命周期

import React from 'react'

class IndexSection extends React.Component{
  constructor(props){
    super(props);
    this.state = {name:"IndexSection自身的state:name",age:"IndexSection自身的state:age"};
  }

  componentWillMount() {
    console.log("componentWillMount");
  }

  componentDidMount() {
    console.log("componentDidMount");
  }

  componentWillReceiveProps(nextProps) {
    console.log("componentWillReceiveProps,nextProps:"+nextProps.name);
  }

  shouldComponentUpdate() {
    console.log("shouldComponentUpdate");
    return true;        // 返回true/false
  }

  componentWillUpdate() {
    console.log("componentWillUpdate");
  }

  componentDidUpdate() {
    console.log("componentDidUpdate");
  }

  componentWillUnmount() {
    console.log("componentWillUnmount");
  }

  render(){
  //  console.log(this.state);
    return (
      <section>
          <h1>這是index.js里面的index_section組件</h1>
          <h1>{this.props.name}</h1>
          <h1>{this.state.age}</h1>
      </section>
    );
  }
}

生命周期.gif

在這些生命周期方法里面都可以使用this.state和this.props來獲取組件的state和props,但是在shouldComponentUpdate和componentWillUpdate方法里面不能直接使用this.setState()方法來設置組件的state。

當調用setState時,實際上會執行enqueueSetState方法,并對partialState以及pending-StateQueue更新隊列進行合并操作,最終通過enqueueUpdate執行state更新
而performUpdateIfNecessary方法會獲取pendingElement, pendingStateQueue,
pending-ForceUpdate,并調用receiveComponent和updateComponent方法進行組件更新
如果在shouldComponentUpdate或者componentWillUpdate方法中調用setState,此時this._pending-StateQueue != null,就會造成循環調用,使得瀏覽器內存占滿后崩潰。

事件系統

對于一些控件我們需要對其綁定事件,最常用的是點擊事件,在原生html里面我們對一個a標簽寫onclick標簽來綁定事件,在react里面使用onClick來綁定,當然還有其他的事件比如input框的onFocus,onBlur,onChange事件等,然后使用event.target來獲取事件對象的dom元素。下面的表格列出了部分事件

事件 事件名稱
鼠標事件 onClick,onContextMenu,onDoubleClick,onDrag,onDragEnd,onDragEnter
... onDragExit,onDragLeave,onDragOver,onDragStart,onDrop,onMouseDown,
... onMouseEnter,onMouseLeave,onMouseMove,onMouseOut,onMouseOver,onMouseUp
鍵盤事件 onKeyDown,onKeyPress,onKeyUp
剪貼板事件 onCopy,onCut,onPaste
焦點事件 onFocus,onBlur
表單事件 onChange,onInput,onSubmit
觸控事件 onTouchCancel,onTouchEnd onTouchMove,onTouchStart
UI事件 onScroll
滾輪事件 onWheel

在index.js增加一個button,對其綁定點擊事件;增加一個select,對其綁定onChange事件
增加一個input事件,對其綁定onBlur事件。首先需要在constructor里面給添加的事件綁定this對象,再實現事件,最后在需要它的dom節點上綁定事件,當然,其他的方法中也可以使用this.函數名()來調用事件,如this.handleSubmit(data);


export default class Index extends React.Component{
  constructor(props){
    super(props);
    this.handleClick = this.handleClick.bind(this);
    this.handleBlur = this.handleBlur.bind(this);
    this.handleChange = this.handleChange.bind(this);
  }
  handleClick(e){
    console.log(e.target);
    console.dir(e.target);
    console.log("------------");
  }
  handleBlur(e){
    console.log(e.target);
    console.dir(e.target);
    console.log(e.target.value);
    console.log("------------");
  }
  handleChange(e){
    console.log(e.target);
    console.dir(e.target);
    console.log(e.target.value);
    console.log("------------");
  }
  render(){
    return (
      <div>
          <h1>這是index.js</h1>
          <button onClick={this.handleClick}>測試button點擊事件</button>
          <div>
            測試input框的onblur事件
            <input type="text"  onBlur={this.handleBlur} />
          </div>
          <div>
            測試select的onchange事件
            <select onChange={this.handleChange}>
              <option value="opt1">選項1</option>
              <option value="opt2">選項2</option>
              <option value="opt3">選項3</option>
            </select>
          </div>
      </div>
    );
  }
}

事件系統.gif

refs

refs是react中的一種屬性,可以在render方法中的虛擬dom節點添加ref屬性,refs的用途是獲取實例,可返回真實dom節點,修改index.js的代碼

export default class Index extends React.Component{
  constructor(props){
    super(props);
  }
  componentDidMount(){
    console.log(this.refs.mybutton);
    console.log(this.refs.mysection);
  }
  render(){
    return (
      <div>
          <h1>這是index.js</h1>
          <button ref="mybutton">測試button點擊事件</button>
          <IndexSection  ref="mysection"/>
      </div>
    );
  }
}
refs.gif

可見,如果我們直接在html節點上添加ref屬性,通過this.refs.ref名即可獲得真實的dom節點;如果是在我們自己的組件上添加ref屬性,通過this.refs.ref名獲得的是虛擬dom節點,這樣就可以調用組件里面的方法。修改index.js

import React from 'react'

class IndexSection extends React.Component{
  constructor(props){
    super(props);
    this.state = {name:"IndexSection自身的state:name",age:"IndexSection自身的state:age"};
    this.testlog = this.testlog.bind(this);
  }
  testlog(){
    console.log("使用ref調用子組件的方法");
  }
  ...
}

export default class Index extends React.Component{
  constructor(props){
    super(props);
  }
  componentDidMount(){
    console.log(this.refs.mybutton);
    this.refs.mysection.testlog();
  }
  ...
}

refs調用.gif

父子通信

父->子

父組件傳遞props給子組件 這里就不測試了

子->父

父組件使用props傳遞回調函數給子組件
修改index.js

import React from 'react'

class IndexSection extends React.Component{
  constructor(props){
    super(props);
    this.state = {name:"IndexSection自身的state:name",age:"IndexSection自身的state:age"};
  }
  componentDidMount(){
    this.props.test("子組件掛載完成");
  }
  ...
}

export default class Index extends React.Component{
  constructor(props){
    super(props);
    this.mymethod = this.mymethod.bind(this);
  }
  mymethod(e){
    console.log("子組件調用了父組件的方法,傳遞參數值:"+e);
  }
  render(){
    return (
      <div>
          <h1>這是index.js</h1>
          <button ref="mybutton">測試button點擊事件</button>
          <IndexSection  test={this.mymethod}/>
      </div>
    );
  }
}
props回調.gif

此處注意constructor的綁定this對象和父組件給子組件設置回調函數props的花括號和子組件調用函數的this.props調用

子->子

父組件做通信橋梁使子組件能互相調用對方的回調函數

我們測試點擊IndexSection中的button修改IndexSectionA中button的文字,在index.js創建IndexSectionA組件,并創建setButtonText方法,父組件寫一個中間函數passtext,父組件給IndexSection組件傳遞中間函數passtext,給IndexSectionA設置ref屬性,并在passtext里面調用IndexSectionA的setButtonText方法。

index.js代碼如下

import React from 'react'

class IndexSection extends React.Component{
  constructor(props){
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick(){
    this.props.settext("你的button文字被我修改了");
  }
  render(){
    return (
      <section>
          <button onClick={this.handleClick}>IndexSection的按鈕,點擊我修改IndexSectionA的button文字</button>
      </section>
    );
  }
}

class IndexSectionA extends React.Component{
  constructor(props){
    super(props);
    this.state = {buttontext:"IndexSectionA的按鈕文字"}
  }
  setButtonText(text){
    this.setState({buttontext:text});
  }
  render(){
    return (
      <section>
          <button>{this.state.buttontext}</button>
      </section>
    );
  }
}

export default class Index extends React.Component{
  constructor(props){
    super(props);
    this.passmethod = this.passmethod.bind(this);
  }
  passmethod(e){
    console.log("父組件充當通信橋梁,收到參數為:"+e);
    this.refs.sectiona.setButtonText(e);
  }
  render(){
    return (
      <div>
          <h1>這是index.js</h1>
          <IndexSection  settext={this.passmethod}/>
          <hr/>
          <IndexSectionA  ref="sectiona"/>
          <hr/>
      </div>
    );
  }
}

子子通信.gif

網絡請求

作為一個網頁應用肯定需要請求網絡數據,本教程使用axios請求網絡,使用quertstring將對象序列化為字符串傳輸,首先使用npm安裝axios和querystring

npm install axios querystring -save

在app目錄下新建util文件夾,新建axios工具axios.js

import axios from 'axios'
import querystring from 'querystring';

export function axiosPost(url, params , callback ,errorcallback) {
  let config = {
   'headers':{'Content-Type': 'application/x-www-form-urlencoded'},
  }
    axios.post(url, querystring.stringify(params))
      .then(result=>callback(result))
      .catch(e => {console.log("Oops, error", e);if(errorcallback != null){errorcallback(e);}});
}

export function axiosGet(url, callback ,errorcallback){
  let data = {
    'headers':{'Content-Type': 'application/x-www-form-urlencoded'}
  }
  axios.get(url, data)
    .then(result=>callback(result))
    .catch(e => {console.log("Oops, error", e);if(errorcallback != null){errorcallback(e);}});
}

axios.js封裝了兩個函數,axiosGet和axiosPost,callback為請求成功回調,errorcallbak為請求失敗回調(可不傳)。

webpack-dev-server代理

在調試的時候我們有時需要使用網絡接口,使用axios請求數據則會遇到跨域問題,這時可在webpack-dev-server配置代理,修改webpack.config.js,這里我們配置中國天氣網接口的代理,順便測試使用上面的axios工具,北京天氣接口地址為:http://www.weather.com.cn/data/cityinfo/101010100.html 則找到webpack.config.js里面的devServer節點,添加proxy規則

proxy: {
    '/data/*': {
        target: 'http://www.weather.com.cn/',
        secure: false,
        changeOrigin: true
    }
}

第2行的"/data/*"代表 localhost:3000/data/ 的請求會代理到target配置的網址,保存后重啟項目。在index.js引入axios工具,并在Index組件的componentDidMount函數下面測試使用axios請求北京天氣

import {axiosGet} from '../util/axios'

componentDidMount(){
    axiosGet("/data/cityinfo/101010100.html",function(result){
      console.log(result);
    });
}

瀏覽器打開app首頁,F12進入調試工具,查看輸出的結果。


網絡請求.gif

這里axios請求成功的回調帶回result參數,里面的data為接口返回的結果,status為http請求狀態碼

網絡請求一般在組件componentDidMount函數里面發起,獲取后設置為組件的state

react-router4路由

對react-router4的詳細講解請看我的另一篇文章React-Router v4簡單入門教程

我們之前都在學習react的基本使用,只在一個頁面也就是index上面練習,但是我們的應用肯定不只是一個頁面的,接下來我們使用react-router4作為前端路由編寫真正的SPA應用,建議開始之前大概瀏覽一下上面提到的講解文章。

首先安裝react-router-dom

npm install react-router-dom -save

在app文件夾創建router文件夾,在里面新建router.js,編寫路由配置

import React from 'react';
import {BrowserRouter,Route,Switch } from 'react-router-dom'

import Index from '../components/index.js'

export default class AppRouter extends React.Component {
    constructor(props) {
        super(props);
    }
    render() {
        return (
            <BrowserRouter>
                <Switch>
                    <Route path="/" component={Index}></Route>
                </Switch>
            </BrowserRouter>
        )
    }
}

改造main.js,引入路由配置

import React from 'react';
import ReactDOM from 'react-dom';
import AppRouter from './router/router'; //路由配置

ReactDOM.render(
    <AppRouter/>,
    document.getElementById('app')
);

重啟項目,瀏覽器刷新后,index是不是又出來了?
接下來編寫一個新的頁面,在components文件夾下新建subpage.js

import React from 'react'

export default class SubPage extends React.Component{
  constructor(props){
    super(props);
  }
  render(){
    return (
      <div>
          <h1>這是subpage.js。為subpage頁面</h1>
      </div>
    );
  }
}

修改路由配置router.js

import React from 'react';
import {BrowserRouter,Route,Switch } from 'react-router-dom'

import Index from '../components/index.js'
import SubPage from '../components/subpage.js'

export default class AppRouter extends React.Component {
    constructor(props) {
        super(props);
    }
    render() {
        return (
            <BrowserRouter>
                <Switch>
                    <Route exact path="/" component={Index}></Route>
                    <Route path="/subpage" component={SubPage}></Route>
                </Switch>
            </BrowserRouter>
        )
    }
}

刷新瀏覽器,在3000端口后面輸入"/subpage",觀察現象


react-router4_historyapi.gif

出404錯誤?這是怎么回事?
我們的router配置確實是正確的,這時候就要考慮是否是webpack-dev-server的問題,果然,是因為webpack配置有問題。原文鏈接
果斷修改webpak.config.js,在devServer中添加historyApiFallback: true

devServer: {
        inline: true,
        historyApiFallback: true,
        port: 3000
    },

重啟項目再刷新瀏覽器


react-router4_subpage.gif

問題解決

我們想實現路徑參數避免search(a.html?param=..)出現,比如 /article?id=100 -> /article/100
更改我們的路由配置router.js

import React from 'react';
import {BrowserRouter,Route,Switch } from 'react-router-dom'

import Index from '../components/index.js'
import SubPage from '../components/subpage.js'

const Article = () =>(
  <Switch>
      <Route exact path="/article" component={SubPage}></Route>
      <Route path="/article/:id" component={SubPage}></Route>
  </Switch>
)

export default class AppRouter extends React.Component {
    constructor(props) {
        super(props);
    }
    render() {
        return (
            <BrowserRouter>
                <Switch>
                    <Route exact path="/" component={Index}></Route>
                    <Route path="/article" component={Article}></Route>
                </Switch>
            </BrowserRouter>
        )
    }
}

修改subpage.js驗證參數是否傳到

render(){
    return (
      <div>
          <h1>
            這是subpage.js。為article頁面
            {this.props.match.params.id != null ? ",id為"+this.props.match.params.id:null}
          </h1>
      </div>
    );
  }
react-router4_路徑參數.gif

這里BrowserRouter里面的<Route path="/article" component={Article}></Route>匹配到的是所有以 /article 開頭的路徑,即/article和/article/:id ,渲染Article組件;在Article組件中配置了兩個Route,第一條是/article,但是前面有exact參數,說明只有 /article 路徑才能匹配到,第二條是/article/:id,/article/... 才能匹配到,SubPage組件里面使用this.props.match.params獲取params 對象,就可以拿到傳過來的id值。

引入css文件

由于我們使用webpack打包,css資源最終也會被打包嵌入到生成的js中,這時需要css的loader去幫我們處理css文件,一般處理css需要style-loader和css-loader,如果你打算使用less或sass的話,你可能需要安裝其他的loader來處理。首先我們安裝這兩個loader:npm install style-loader css-loader --save-dev,然后修改webpack.config.js,找到module節點下的rule節點,添加如下代碼

{
    test: /\.css$/,
        use: [
            'style-loader',
            'css-loader',
        ],
}

test后面是一個正則表達式,代表匹配的文件為以.css結尾的文件使用style-loader和css-loader
之后我們在app目錄下創建文件夾style,用于存放css文件,并新建main.css

h1{
  color:red;
  font-size: 30px;
  text-decoration: underline;
}

在index.js頭部引入該css:import '../style/main.css',查看瀏覽器頁面變化

圖片1.png

這里需要說明的是,當在使用class屬性設置樣式的時候,在react里面,標簽不可以使用class來設置樣式,而是應該使用className來代替,如<div className="myclass">className來代替class</div>

使用webpack打包

看到這里,如果你一直跟著敲代碼到這里的話,你的app應該是有一個index頁面,一個article頁面,并且是引入了css樣式的,你可以接著編寫出其他的頁面,也可以跟著我學習如何進行打包。之前的代碼都是以dev模式運行在webpack-dev-server上,調試信息都會輸出在瀏覽器的控制臺,并且代碼沒有壓縮,整個js很大,我們需要對app進行打包。

首先在根目錄創建webpack.config.dist.js,可是我們已經有webpack.config.js了,為什么還需要創建一個呢?我們可以在webpack.config.dist.js里面配置發布版本的配置,而原來的webpack.config.js為調試版本的配置,所以不妨把webpack.config.js改名為webpack.config.dev.js,這樣還需要修改package.json文件下的scripts節點,添加start和build指令

"start": "webpack-dev-server --devtool eval --progress --colors --content-base build --config ./webpack.config.dev.js"
"build": "webpack -p --config ./webpack.config.dev.js"

新建webpack.config.dev.js

var path = require('path');
var webpack = require('webpack');
module.exports = {
    entry: [path.resolve(__dirname, './app/main.js')],
    output: {
        path: path.resolve(__dirname, './build'),
        filename: 'bundle.js',
        publicPath:'/'
    },
    module: {
      rules: [
           {
               test: /\.js?$/,
               exclude: /(node_modules|bower_components)/,
               loader: 'babel-loader',
           },
            {
                test: /\.css$/,
                use: [
                    'style-loader',
                    'css-loader',
                ],
            }
        ],
    },
    plugins: [
          //移除打包后的警告
          new webpack.DefinePlugin({
              "process.env": {
                  NODE_ENV: JSON.stringify("production")
              }
          }),
          new webpack.optimize.ModuleConcatenationPlugin(),
    ]
};

保存之后,在終端輸入npm run build,打包好的js文件就放在build文件夾下了,現在生成的js從原來的600多kb壓縮為了170多kb,當然還可以壓縮的更小,比如提取公共模塊和按需加載。

nginx上部署

生成好后,我們就可以將build文件夾的index.html和bundle.js部署到服務器上了,這里我在centos虛擬機部署,這里部署的app當做靜態資源文件,我們先在centos虛擬機安裝nginx,由于是centos7 64位,先下載rpm包
wget http://nginx.org/packages/centos/7/x86_64/RPMS/nginx-1.12.2-1.el7_4.ngx.x86_64.rpm
然后安裝nginxsudo yum install nginx-1.12.2-1.el7_4.ngx.x86_64.rpm
啟動nginx服務 sudo service nginx start
打開瀏覽器輸入localhost,welcome to nginx!
之后配置nginx.conf,nginx配置文件夾在/etc/nginx下面,修改nginx.conf

user  root;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';
    access_log  /var/log/nginx/access.log  main;
    sendfile        on;
    #tcp_nopush     on;
    keepalive_timeout  65;
    #gzip  on;
    server {
        listen       80;
        server_name  localhost;

        location / {
            try_files $uri $uri/ /index.html;
            root   /home/shane/develope/static;
            index  index.html index.htm;
        }
    }
}

然后把build文件夾里面的index.html和bundle.js拷貝到虛擬機目錄的對應位置,即nginx配置的 location下的root對應的目錄,之后重啟nginx或reload,重啟吧...sudo service nginx restart
重要的一步。。。如果你也是centos7的話請先關閉SELinux,否則nginx沒有權限讀取文件,直接sudo setenforce 0暫時關閉SELinux,然后訪問localhost:80 查看是否出現頁面

部署nginx.gif

參考資料

  1. react官方文檔
  2. npm install 時--save-dev和--save的區別 - 學習會讓你青春永駐 - 博客園
  3. webpack4.0.1安裝問題和webpack.config.js的配置變化 - CSDN博客
  4. React.Component 生命周期 - 爬蟲一只 - 博客園
  5. 玩轉 React(五)- 組件的內部狀態和生命周期
  6. setState詳解 - CSDN博客
  7. 事件系統 - React 中文版 - 極客學院Wiki
  8. react-router browserHistory刷新頁面404問題解決 – 熊建剛的博客
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,431評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,637評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,555評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,900評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,629評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,976評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,976評論 3 448
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,139評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,686評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,411評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,641評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,129評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,820評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,233評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,567評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,362評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,604評論 2 380

推薦閱讀更多精彩內容