ReactJS學習-使用webpack構建工程,使用materialUI構建前端,與hprose後端通訊
實現目標:
Material-UI 是一套用React寫成的,符合Google Material Design 的UI元件庫。
前端通過Material-UI構造介面,然後通過hprose-html5呼叫後端hprose服務取資料
hprose服務參考 上一篇hprose實踐,
環境配置:
必須先安裝nodejs與npm
新建工程目錄
mkdir react-workspace
cd react-workspace
npm init
安裝依賴
npm install --save react react-dom react-tap-event-plugin material-ui npm install --save-dev babel-core babel-loader npm install --save-dev babel-preset-es2015 babel-preset-react babel-preset-stage-1 npm install --save-dev webpack
第一行是生產用的 React 與 Material-UI 部分。
第二行是Babel轉換器的核心部分。
第三行是Babel轉換器的三個額外配置:ES2015(ES6),React,Stage1(ES7)。
第四行是Webpack的部分。
第二、三、四行的內容只在工程構建之前有用(用於開發:-dev)。
安裝完畢之後呢,可以先檢查一下 package.json
應該會看見如下內容
"dependencies": { "material-ui": "^0.15.0", "react": "^15.1.0", "react-dom": "^15.1.0", "react-tap-event-plugin": "^1.0.0" }, "devDependencies": { "babel-core": "^6.9.1", "babel-loader": "^6.2.4", "babel-preset-es2015": "^6.9.0", "babel-preset-react": "^6.5.0", "babel-preset-stage-1": "^6.5.0", "webpack": "^1.13.1" },
配置 Babel
在 package.json 中新增一個域”babel”,與之前的”dependencies” 同級。
"babel": {
"presets": [
"es2015",
"react",
"stage-1"
],
"plugins": []
}
配置 Webpack
在專案目錄新建一個webpack.config.js ,並寫入:
var path = require('path'); module.exports = { entry: './entry.js', output: { path: path.join(__dirname, '/dist'), filename: 'bundle.js' }, resolve: { extensions: ['', '.js', '.jsx'] }, module: { loaders: [ { test: /\.jsx?$/, loaders: ['babel'] } ] } }
這裡對Webpack的打包行為做了配置,主要分為幾個部分:
entry:指定打包的入口檔案,每有一個鍵值對,就是一個入口檔案
output:配置打包結果,path定義了輸出的資料夾,filename則定義了打包結果檔案的名稱
resolve:定義瞭解析模組路徑時的配置,常用的就是extensions,可以用來指定模組的字尾,這樣在引入模組時就不需要寫字尾了,會自動補全
module:定義了對模組的處理邏輯,這裡可以用loaders定義了一系列的載入器,以及一些正則。當需要載入的檔案匹配test的正則時,就會呼叫後面的loader對檔案進行處理,這正是webpack強大的原因。比如這裡定義了凡是.js結尾的檔案都是用babel-loader做處理,而.jsx結尾的檔案會先經過jsx-loader處理,然後經過babel-loader處理。當然這些loader也需要通過npm install安裝
plugins: 這裡定義了需要使用的外掛,比如commonsPlugin在打包多個入口檔案時會提取出公用的部分,生成common.js
當然Webpack還有很多其他的配置,具體可以參照它的配置文件
配置 npm 指令碼
現在我們還缺少一個構建指令碼,編輯package.json 中的 “scripts” 域:
"scripts": {
"build": "webpack",
"build-dev": "webpack -w -d"
}
接下來我們就可以在專案目錄下使用簡單的構建指令碼了:
$ npm run build
如果是開發中,可以使用監聽式的Webpack,差量打包,提升效率。
$ npm run build-dev
按照配置,打包生成的檔案為 dist/bundle.js 。
至此,基本的環境已經配置完畢,我們來嘗試一下呼叫Material-UI庫。
程式碼編寫
建立Web入口
在專案目錄下建立一個index.html,寫入:
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<script src="dist/bundle.js"></script>
</body>
</html>
編寫Webpack入口
編輯專案目錄下的 entry.js,寫入:
import React from 'react';
import ReactDOM from 'react-dom';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import AppBar from 'material-ui/AppBar';
const App = () => (
<MuiThemeProvider muiTheme={getMuiTheme()}>
<AppBar title="Hello, Material-UI!" />
</MuiThemeProvider>
);
let app = document.createElement('div');
ReactDOM.render(<App />, app);
document.body.appendChild(app);
執行構建指令碼:
npm run build
輸出
現在可以看到目錄下有了一個dist/bundle.js
開啟index.html
成功
瀏覽器自動重新整理
如果需要一直輸入 npm run build 確實是一件非常無聊的事情,幸運的是,我們可以把讓他安靜的執行,讓我們設定 webpack-dev-server。
npm install --save webpack-dev-server
修改package.json檔案的scripts:
"scripts": {
"build": "webpack",
"dev": "webpack-dev-server --devtool eval --progress --colors --hot --content-base build"
}
當你在命令列裡執行 npm run dev 的時候他會執行 dev 屬性裡的值。這是這些指令的意思:
webpack-dev-server - 在 localhost:8080 建立一個 Web 伺服器
–devtool eval - 為你的程式碼建立源地址。當有任何報錯的時候可以讓你更加精確地定位到檔案和行號
–progress - 顯示合併程式碼進度
–colors - Yay,命令列中顯示顏色!
–content-base build - 指向設定的輸出目錄
總的來說,當你執行 npm run dev 的時候,會啟動一個 Web 伺服器,然後監聽檔案修改,然後自動重新合併你的程式碼。真的非常簡潔!
訪問發現 “Cannot GET /” 錯誤
因為我們的index.html不是在build目錄下
調整專案結構為
- /app
- main.js
- component.js
- /build
- bundle.js (自動建立)
- index.html
- package.json
- webpack.config.js
修改index.html
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<script src="bundle.js"></script>
</body>
</html>
將entry.js移動到app/main.js
修改webpack.config.js,將入口檔案設定為app/main.js,output設為build目錄下的bundle.js,
並新增入口點,使得瀏覽器在檔案修改之後會自動重新整理。
var path = require('path');
module.exports = {
entry: [
'webpack/hot/dev-server',
'webpack-dev-server/client?http://localhost:8080',
path.resolve(__dirname, 'app/main.js')
],
output: {
path: path.join(__dirname, '/build'),
filename: 'bundle.js'
},
resolve: {
extensions: ['', '.js', '.jsx']
},
module: {
loaders: [
{ test: /\.jsx?$/, loaders: ['babel'] }
]
}
}
執行命令 npm run dev, 然後訪問http://localhost:8080/
看到之前同樣頁面,然後修改一下main.js
將
<AppBar title="Hello, Material-UI!" />
修改為
<AppBar title="Hello, World!" />
儲存一下,再回到瀏覽器,會發現自動重新整理了,內容也變成了Hello, World!
通過hprose-html5取資料
下面通過後端服務取資料,來替代Hello,World!
現已有hprose for php 構建的hprose服務,在遠端伺服器http://xx.xx.xx.xx:8080/
方法為getUserByID
修改index.html,引入hprose-html5.js,使用的是bootcss提供的cdn
<script type="text/javascript" src="http://cdn.bootcss.com/hprose-html5/2.0.8/hprose-html5.js"></script>
修改main.js
import React from 'react';
import ReactDOM from 'react-dom';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import AppBar from 'material-ui/AppBar';
const muiTheme = getMuiTheme();
class App extends React.Component{
constructor(props){
super(props);
this.state = {
data:"none",
};
this.componentDidMount = this.componentDidMount.bind(this);
}
componentDidMount() {
this.setState({data: "block"});
let self = this;
let client = hprose.Client.create("http://xx.xx.xx.xx:8080/", ["getUserByID"]);
client.getUserByID(2, function(result) {
console.log(result.player_name);
self.setState({data: result.player_name});
}, function(name, err) {
console.log(err);
});
}
render() {
return (
<MuiThemeProvider muiTheme={muiTheme}>
<AppBar title={this.state.data} />
</MuiThemeProvider>
);
}
}
let app = document.createElement('div');
ReactDOM.render(<App />, app);
document.body.appendChild(app);
檢視結果
從後端服務取到了資料“keyunqqq”
PS:React在ES6的實現中去掉了getInitialState這個hook函式,規定state在constructor中實現,如下:
Class App extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
…
}
Babel的Blog上還有一種實現方法,即直接使用賦值語句:
Class App extends React.Component {
constructor(props) {
super(props);
}
state = {}
…
}
ES6中this需要手動繫結:
this.componentDidMount = this.componentDidMount.bind(this);