1. 程式人生 > >React+webpack 的開發環境配置步驟(三)

React+webpack 的開發環境配置步驟(三)

目錄
四、React 配置
1. React 元件
2. React router 模組
3. React 和Redux 配合

在前面的文章已經講解怎麼配置好了整個webpack環境。後面會有一篇文章講怎麼快速搭建webpack。

1. React 元件

先用node.js安裝react 和react-dom 模組:

npm install react react-dom --save

在webpack.dll.config.js 裡面的vendors 數組裡面新增 react-dom 第三庫

const vendors = [  
  'react',
  "react-dom"
];

在Powershell 窗口裡面敲下面指令,把第三方庫打包到dll裡面。

webpack -p --config webpack.dll.config.js --progress --profile --colors

指令完成後,會更新manifest.json 和vendor.xxxx.js。 這時需要手動重新整理html 模板裡面引進的vendor.xxxx.js檔案。

修改app.js裡面的內容,新增reactdom 的渲染語句:

require('./css/css');
require('./less/less.less');
require('./scss/scss.scss'
); var app=document.createElement("div"); app.innerHTML='<h1>Hello World!</h1>'; document.body.insertBefore(app,document.body.childNodes[0]);

//下面的是react的程式碼

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

class FirstComponent extends React.Component{
    render(){
      return
( <div> this is React code from Components. </div> ); } } var div=document.createElement("div"); div.setAttribute("id","root"); document.body.insertBefore(div,document.body.childNodes[1]); ReactDOM.render(<FirstComponent/>,document.getElementById('root'));

在powershell裡面敲npm start 啟動伺服器,在瀏覽器上敲localhost:8080 就可以顯示這個網頁的內容。

2.React router 模組

先安裝相應的模組:react-router-dom,指令:

npm install react-router-dom --save

這個模組同樣需要新增到webpack.dll.config.js 裡面的vendors 數組裡面新增 react-dom 第三庫

const vendors = [  
  'react',
  "react-dom"

];

在Powershell 窗口裡面敲下面指令,把第三方庫打包到dll裡面。

webpack -p --config webpack.dll.config.js --progress --profile --colors

指令完成後,會更新manifest.json 和vendor.xxxx.js。 這時需要手動重新整理html 模板裡面引進的vendor.xxxx.js檔案。

修改app.js裡面的內容:

import React from 'react';
import ReactDOM from 'react-dom';
import {NavLink,Route,BrowserRouter,HashRouter as Router, Swith,Redirect} from 'react-router-dom';
import RouteConfig from '../Config/Route.jsx';

var div=document.createElement("div");
div.setAttribute("id","root");
document.body.insertBefore(div,document.body.childNodes[1]);

ReactDOM.render(
    <Router>
        {RouteConfig}
    </Router>
    ,document.getElementById('root'));

現在的router 有兩個版本,用react-router 或者react-router-dom 。這裡用react-router-dom,這個模組有幾個介面:NavLink 、Route 、BrowserRouter、HashRouter、Swith、Redirect 等 。每個介面的作用我這不做說明。RouteConfig 是路由配置檔案,自己建立的。

現在需要建立幾個檔案,
1) 在根目錄建立Config資料夾,資料夾裡面建立Route.jsx。
Route.jsx 的內容:

import React from 'react';
import ReactDOM from 'react-dom';
import {NavLink,Route,BrowserRouter as Router,HashRouter,Switch,Redirect}  from 'react-router-dom';

import MainComponent from '../component/Main.jsx';//引進元件
import Topic from '../component/Topic.jsx';//引進元件
const routes =[
    {
        path:'/',
        exact:true,
        component: MainComponent
    },
    {
        path:'/topic',
        exact:false,
        component:Topic
    },
];
const RouteConfig = (
    <Switch>
    {
      routes.map((route,index)=>(
                  <Route
                   key ={index}
                   path={route.path}
                   exact={route.exact}
                   component={route.component}                
                  />
                ))
    }

    </Switch>
);

export default RouteConfig;

2)在根目錄下建立component 資料夾,資料夾下建立兩個檔案:
Main.jsx:

import React from 'react';
import ReactDOM from 'react-dom';
import {NavLink as Link} from 'react-router-dom';
class MainComponent extends React.Component{
    render(){
      return(
          <div>
             <h1>mainText</h1>    

             <Link to="/topic">jumpe to Topic</Link>         
          </div>
      );  
    }
}

export default MainComponent;

Topic.jsx

import React from 'react';
import ReactDOM from 'react-dom';
import {NavLink as Link} from 'react-router-dom';
class Topic extends React.Component{
    render(){
      return(
          <div>
              <h1>topicText:</h1>         
              <Link to="/">jumpe to Main</Link>         
          </div>
      );  
    }
}

export default Topic;

建完後,在powershell視窗下敲npm start,然後在瀏覽器位址列敲localhost:8080 就後出現下面頁面:
這裡寫圖片描述
點選 jumpe to Topic ,就會跳到 topic 頁面:
這裡寫圖片描述

這樣webpack react router 就這樣簡單的配置好了。

3.React 和Redux 配合

Redux 模組可以集中管理 react全部元件的state,這個技術很常用。
安裝Redux模組,指令:

 npm install redux react-redux react-router-redux redux-thunk --save

模組安裝好後,在需要在webpack.dll.config.js 裡面修改vendors的內容:

const vendors = [

  'react',
  "react-dom",
  "react-router-dom",
  "redux",
  "react-redux",
  "react-router-redux",
  "redux-thunk"


];

在Powershell 視窗下,敲:

webpack -p --config webpack.dll.config.js --progress --profile --colors

在build 路徑下會再次生成manifest.json 和 vendor.xxxxx.js檔案,把相應的vendor.xxxxx.js檔案引入到index.html模板檔案中。

要使用redux,需要先理解它的基本原理,這裡我簡單的說下,具體的可以百度瞭解下。
redux 主要分為三塊,store reducer action。
store 用來儲存component裡面需要集中管理的state;
action 是定義與state狀態改變相關的一系列方法(方法也稱為action creator);
reducer 初始化state及呼叫action的方法修改state的狀態,返回新state。
這三者之間的用什麼相互關聯的,下面步驟講解裡面會有提及。

store 一般會採用自動建立的方法。react元件可以通過函式直接上傳給store,上傳程式碼是直接寫在元件裡面,不需要新增一個元件就修改一次store的程式碼。
store 的中介軟體用來實現非同步呼叫,這裡用ReduxThunk。這個中介軟體的優缺點,暫時不涉及。
在src 目錄下建立一個Config 資料夾,在Config 裡面新建一個Store.jsx。
Store.jsx 的程式碼:

import {createStore,combineReducers,applyMiddleware} from 'redux';
import RootReducer from '../Reducer/index.jsx';//引入reduce
import ReduxThunk from 'redux-thunk';//中介軟體

var store = createStore(    //自動生成store的函式
    RootReducer, //reduce,修改state狀態的函式集合
    applyMiddleware(ReduxThunk) //中介軟體
);

export default store;

RootReducer是自己定義的reduce檔案。createStore applyMiddleware 來自redux 模組。 ReduxThunk 來自於redux-thunk。
store 和 reducer 是通過createStore 關聯起來的。

action
在src下面建立一個action資料夾,action資料夾下新建一個action.jsx檔案。
action程式碼:

const actions = {

 changeText:function(num){
     console.log("呼叫actions");
      switch(num){
      case 1:
      return {type:'AlterMain',payload:"mainContainer had been changed"};
      case 2:
       return {type:'AlterTopic',payload:"topicContainer had been changed"};
       default:
       return action;

   }
},

};
export default actions;

預先規劃設定state格式為:
const defaultState = { //在reducer 裡面定義state的初始的值
mainText:”mainContainer”,
topicText:”topicContainer”

};
action這裡定義了一個修改state狀態的函式。當reducer呼叫action時,action就會通過不同的情況返回不同的action值。

reducer:
在src資料夾下面建立一個Reducer資料夾,資料夾下面新建一個index.jsx檔案。
reducer的程式碼:

import {combineReducers} from 'redux';
import {routerReducer} from 'react-router-redux';

const defaultState = {//設定state的預設值
   mainText:"mainContainer",
   topicText:"topicContainer"
};
const reducer = (state = defaultState, action) => {     
    switch (action.type) {//通過action的返回值來選擇更新哪個state的狀態
        case 'AlterMain':
            return  Object.assign({},state,{ mainText:action.payload});
        case 'AlterTopic':
            return  Object.assign({},state,{ topicText:action.payload});
        default:
            return state;
    }
};
const RootReducer = combineReducers({//可以定義多個reducer,然後通過combineReducers來合併
    routing:routerReducer,//redux和router處理函式
    app:reducer      //app 需要與元件裡面上傳的state一致
});
export default RootReducer;

reducer 只看到 通過action返回值來修改state的狀態並沒有看到呼叫action。
在除錯移動端顯示的時候,發現object.assign 存在相容問題,在網上查了下資料,需要額外新增下面這段程式碼:

if (typeof Object.assign != 'function') {
    // Must be writable: true, enumerable: false, configurable: true
    Object.defineProperty(Object, "assign", {
      value: function assign(target, varArgs) { // .length of function is 2
        'use strict';
        if (target == null) { // TypeError if undefined or null
          throw new TypeError('Cannot convert undefined or null to object');
        }

        var to = Object(target);

        for (var index = 1; index < arguments.length; index++) {
          var nextSource = arguments[index];

          if (nextSource != null) { // Skip over if undefined or null
            for (var nextKey in nextSource) {
              // Avoid bugs when hasOwnProperty is shadowed
              if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
                to[nextKey] = nextSource[nextKey];
              }
            }
          }
        }
        return to;
      },
      writable: true,
      configurable: true
    });
  }

元件的定義:
修改component資料夾下的Main.jsx

import React from 'react';
import ReactDOM from 'react-dom';
import {NavLink as Link} from 'react-router-dom';

import {connect} from 'react-redux';
import P from 'prop-types';

import actions from '../src/action/action.jsx';//引入actions
//mapstoreStateToProps 這裡指定Main控制元件需要上傳的state
const mapStoreStateToProps = (state) =>(
    {
         mainText:state.app.mainText, //mainText是變數,值對應的state.app.mainText的儲存空間,其中app與reducers裡面定義的一致。

    }
);

//mapDispatchToProps 這裡上傳處理state函式,即action裡面定義的函式
const mapDispatchToProps = (dispatch,ownProps)=> ({
   fn:{
       changeText:(num)=> dispatch(actions.changeText(num))
   }
});

//這樣state一致上傳到store,需要取值用props取就ok
class MainComponent extends React.Component{
    render(){
      return(
          <div>
             <h1>mainText:{this.props.mainText}</h1>        
             <button onClick={()=>this.props.fn.changeText(1)}>修改mainText的值</button>

             <Link to="/topic">jumpe to Topic</Link>         
          </div>
      );  
    }
}
//最後呼叫connect函式,把元件和store連線起來
export default connect(mapStoreStateToProps,mapDispatchToProps)(MainComponent);

connect 函式能成功執行的前提是 元件是provider的子元件。所有需要修改app.js 。
app.js 程式碼:

import React from 'react';
import ReactDOM from 'react-dom';
import {NavLink,Route,BrowserRouter,HashRouter as Router, Swith,Redirect} from 'react-router-dom';
import RouteConfig from '../src/Config/Route.jsx';
import {Provider} from 'react-redux';
import store from '../src/Config/Store.jsx';


var div=document.createElement("div");
div.setAttribute("id","root");
document.body.insertBefore(div,document.body.childNodes[0]);

ReactDOM.render(
    <Provider store={store}> //Provider 並指定store的檔案
        <Router>
            {RouteConfig}
        </Router>
    </Provider>
    ,document.getElementById('root'));

Topic.jsx的程式碼:

import React from 'react';
import ReactDOM from 'react-dom';
import {NavLink as Link} from 'react-router-dom';

import {connect} from 'react-redux';



import actions from '../src/action/action.jsx';

const mapStoreStateToProps = (state) =>(
    {
         topicText:state.app.topicText,

    }
)

const mapDispatchToProps = (dispatch,ownProps)=> ({
   fn:{
       changeText:(num)=> dispatch(actions.changeText(num))
   }
});


class Topic extends React.Component{
    render(){
      return(
          <div>
              <h1>topicText:{this.props.topicText}</h1>
              <button onClick={()=>this.props.fn.changeText(2)}>修改topicText的值</button>       
              <Link to="/">jumpe to Main</Link>         
          </div>
      );  
    }
}

export default connect(mapStoreStateToProps,mapDispatchToProps)(Topic);

這樣整個redux就搭建好了。

provider 指定 store檔案,它下面的元件可以通過connect 把元件和store關聯。
store:通過createStore 把 store和reducer 關聯
reducer: 定義state的預設值,並定義state 和action的對應關係。combineReducers 合併reducer,指定reducer的介面,如果用到router時,要注意定義route的處理函式。
action:只是單獨定義一些修改state狀態的操作。
元件:通過connect 把需要集中管理的state即state對應的action 上傳到store,並繫結元件。state的值被修改,元件的view就會做相應的改變

這裡沒有涉及到redux的非同步通訊。
流程可以簡化理解為:
元件->action->dispath(action)->store->reducer ->store(修改state)->元件(view)

網頁的整體效果如下:
http://localhost:8080 就能看到下面的介面:
這裡寫圖片描述
點選修改mainText的值 的按鈕,mainText就會被更改如下:
這裡寫圖片描述
點選jumpe to Topic
這裡寫圖片描述

點選 修改topicText的值 的按鈕,topicText就會被更改如下:
這裡寫圖片描述