輕鬆入門React和Webpack

最近在學習React.js,以前都是直接用最原生的方式去寫React代碼,發現組織起來特別麻煩,以前聽人說用Webpack組織React組件駕輕就熟,就花了點時間學習了一下,收穫頗豐。
說說React
一個組件,有本身的結構,有本身的邏輯,有本身的樣式,會依賴一些資源,會依賴某些其餘組件。好比平常寫一個組件,比較常規的方式:
- 經過前端模板引擎定義結構
- JS文件中寫本身的邏輯
- CSS中寫組件的樣式
- 經過RequireJS、SeaJS這樣的庫來解決模塊之間的相互依賴,
那麼在React中是什麼樣子呢?
結構和邏輯
在React的世界裏,結構和邏輯交由JSX文件組織,React將模板內嵌到邏輯內部,實現了一個JS代碼和HTML混合的JSX。
結構
在JSX文件中,能夠直接經過React.createClass來定義組件:
var CustomComponent = React.creatClass({ 
    render: function(){ 
        return (); 
    } 
}); 
經過這種方式能夠很方便的定義一個組件,組件的結構定義在render函數中,但這並非簡單的模板引擎,咱們能夠經過js方便、直觀的操控組件結構,好比我想給組件增長几個節點:

經過這種方式,React使得組件擁有靈活的結構。那麼React又是如何處理邏輯的呢?
邏輯
寫過前端組件的人都知道,組件一般首先須要相應自身DOM事件,作一些處理。必要時候還須要暴露一些外部接口,那麼React組件要怎麼作到這兩點呢?
事件響應
好比我有個按鈕組件,點擊以後須要作一些處理邏輯,那麼React組件大體上長這樣:
var ButtonComponent = React.createClass({ 
    render: function(){ 
        return (屠龍寶刀,點擊就送); 
    } 
}); 
點擊按鈕應當觸發相應地邏輯,一種比較直觀的方式就是給button綁定一個onclick事件,裏面就是須要執行的邏輯了:
function getDragonKillingSword() { 
    //送寶刀 

var ButtonComponent = React.createClass({ 
    render: function(){ 
        return (屠龍寶刀,點擊就送); 
    } 
}); 
這樣就實現內部事件的響應了,那若是須要暴露接口怎麼辦呢?
暴露接口
事實上如今getDragonKilling
<br> 
Sword已是一個接口了,若是有一個父組件,想要調用這個接口怎麼辦呢?
父組件大概長這樣:
var ImDaddyComponent = React.createClass({ 
    render: function(){ 
        return ( 
                            //其餘組件 
                                //其餘組件 
                    ); 
    } 
}); 
那麼若是想手動調用組件的方法,首先在ButtonComponent上設置一個ref=""屬性來標記一下,好比這裏把子組件設置成,那麼在父組件的邏輯裏,就能夠在父組件本身的方法中經過這種方式來調用接口方法:
this.refs.getSwordButton.getDragonKillingSword(); 
看起來屌屌噠~那麼問題又來了,父組件但願本身可以按鈕點擊時調用的方法,那該怎麼辦呢?
配置參數
父組件能夠直接將須要執行的函數傳遞給子組件:
而後在子組件中調用父組件方法:
var ButtonComponent = React.createClass({ 
    render: function(){ 
        return (屠龍寶刀,點擊就送); 
    } 
}); 
子組件經過this.props可以獲取在父組件建立子組件時傳入的任何參數,所以this.props也常被當作配置參數來使用。
屠龍寶刀每一個人只能領取一把,按鈕點擊一下就應該灰掉,應當在子組件中增長一個是否點擊過的狀態,這又應當處理呢?
組件狀態css

在React中,每一個組件都有本身的狀態,能夠在自身的方法中經過this.state取到,而初始狀態則經過getInitialState()方法來定義,好比這個屠龍寶刀按鈕組件,它的初始狀態應該是沒有點擊過,因此getInitialState方法裏面應當定義初始狀態clicked: false。而在點擊執行的方法中,應當修改這個狀態值爲click: true:
var ButtonComponent = React.createClass({ 
    getInitialState: function(){ 
        //肯定初始狀態 
        return { 
            clicked: false 
        }; 
    }, 
    getDragonKillingSword: function(){ 
        //送寶刀 
        //修改點擊狀態 
        this.setState({ 
            clicked: true 
        }); 
    }, 
    render: function(){ 
        return (屠龍寶刀,點擊就送); 
    } 
}); 
這樣點擊狀態的維護就完成了,那麼render函數中也應當根據狀態來維護節點的樣式,好比這裏將按鈕設置爲disabled,那麼render函數就要添加相應的判斷邏輯:
render: function(){ 
    var clicked = this.state.clicked; 
    if(clicked) 
        return (屠龍寶刀,點擊就送); 
    else  
        return (屠龍寶刀,點擊就送); 

小節
這裏簡單介紹了經過JSX來管理組件的結構和邏輯,事實上React給組件還定義了不少方法,以及組件自身的生命週期,這些都使得組件的邏輯處理更增強大
資源加載
CSS文件定義了組件的樣式,如今的模塊加載器一般都可以加載CSS文件,若是不能通常也提供了相應的插件。事實上CSS、圖片能夠看作是一種資源,由於加載過來後通常不須要作什麼處理。
React對這一方面並無作特別的處理,雖然它提供了Inline Style的方式把CSS寫在JSX裏面,但估計沒有多少人會去嘗試,畢竟如今CSS樣式已經再也不只是簡單的CSS文件了,一般都會去用Less、Sass等預處理,而後再用像postcss、myth、autoprefixer、cssmin等等後處理。資源加載通常也就簡單粗暴地使用模塊加載器完成了
組件依賴
組件依賴的處理通常分爲兩個部分:組件加載和組件使用。
組件加載
React沒有提供相關的組件加載方法,依舊須要經過<script>標籤引入,或者使用模塊加載器加載組件的JSX和資源文件。
組件使用
若是細心,就會發現其實以前已經有使用的例子了,要想在一個組件中使用另一個組件,好比在ParentComponent中使用ChildComponent,就只須要在ParentComponent的render()方法中寫上<ChildComponent />就好了,必要的時候還能夠傳些參數。
疑問
到這裏就會發現一個問題,React除了只處理告終構和邏輯,資源也無論,依賴也無論。是的,React將近兩萬行代碼,連個模塊加載器都沒有提供,更與Angularjs,jQuery等不一樣的是,他還不帶啥腳手架...沒有Ajax庫,沒有Promise庫,要啥啥沒有...
虛擬DOM
那它爲啥這麼大?由於它實現了一個虛擬DOM(Virtual DOM)。虛擬DOM是幹什麼的?這就要從瀏覽器自己講起
如咱們所知,在瀏覽器渲染網頁的過程當中,加載到HTML文檔後,會將文檔解析並構建DOM樹,而後將其與解析CSS生成的CSSOM樹一塊兒結合產生愛的結晶——RenderObject樹,而後將RenderObject樹渲染成頁面(固然中間可能會有一些優化,好比RenderLayer樹)。這些過程都存在與渲染引擎之中,渲染引擎在瀏覽器中是於JavaScript引擎(JavaScriptCore也好V8也好)分離開的,但爲了方便JS操做DOM結構,渲染引擎會暴露一些接口供JavaScript調用。因爲這兩塊相互分離,通訊是須要付出代價的,所以JavaScript調用DOM提供的接口性能不咋地。各類性能優化的最佳實踐也都在儘量的減小DOM操做次數。
而虛擬DOM幹了什麼?它直接用JavaScript實現了DOM樹(大體上)。組件的HTML結構並不會直接生成DOM,而是映射生成虛擬的JavaScript DOM結構,React又經過在這個虛擬DOM上實現了一個 diff 算法找出最小變動,再把這些變動寫入實際的DOM中。這個虛擬DOM以JS結構的形式存在,計算性能會比較好,並且因爲減小了實際DOM操做次數,性能會有較大提高
道理我都懂,但是爲何咱們沒有模塊加載器?
因此就須要Webpack了
說說Webpack
什麼是Webpack?
事實上它是一個打包工具,而不是像RequireJS或SeaJS這樣的模塊加載器,經過使用Webpack,可以像Node.js同樣處理依賴關係,而後解析出模塊之間的依賴,將代碼打包
安裝Webpack
首先得有Node.js
而後經過npm install -g webpack安裝webpack,固然也能夠經過gulp來處理webpack任務,若是使用gulp的話就npm install --save-dev gulp-webpack
配置Webpack
Webpack的構建過程須要一個配置文件,一個典型的配置文件大概就是這樣
var webpack = require('webpack'); 
var commonsPlugin = new webpack.optimize.CommonsChunkPlugin('common.js'); 
 
module.exports = { 
    entry: { 
        entry1: './entry/entry1.js', 
        entry2: './entry/entry2.js' 
    }, 
    output: { 
        path: __dirname, 
        filename: '[name].entry.js' 
    }, 
    resolve: { 
        extensions: ['', '.js', '.jsx'] 
    }, 
    module: { 
        loaders: [{ 
            test: /\.js$/, 
            loader: 'babel-loader' 
        }, { 
            test: /\.jsx$/, 
            loader: 'babel-loader!jsx-loader?harmony' 
        }] 
    }, 
    plugins: [commonsPlugin] 
}; 
這裏對Webpack的打包行爲作了配置,主要分爲幾個部分:
entry:指定打包的入口文件,每有一個鍵值對,就是一個入口文件
output:配置打包結果,path定義了輸出的文件夾,filename則定義了打包結果文件的名稱,filename裏面的[name]會由entry中的鍵(這裏是entry1和entry2)替換
resolve:定義瞭解析模塊路徑時的配置,經常使用的就是extensions,能夠用來指定模塊的後綴,這樣在引入模塊時就不須要寫後綴了,會自動補全
module:定義了對模塊的處理邏輯,這裏能夠用loaders定義了一系列的加載器,以及一些正則。當須要加載的文件匹配test的正則時,就會調用後面的loader對文件進行處理,這正是webpack強大的緣由。好比這裏定義了凡是.js結尾的文件都是用babel-loader作處理,而.jsx結尾的文件會先通過jsx-loader處理,而後通過babel-loader處理。固然這些loader也須要經過npm install安裝
plugins: 這裏定義了須要使用的插件,好比commonsPlugin在打包多個入口文件時會提取出公用的部分,生成common.js
固然Webpack還有不少其餘的配置,具體能夠參照它的配置文檔
執行打包
若是經過npm install -g webpack方式安裝webpack的話,能夠經過命令行直接執行打包命令,好比這樣:
$webpack --config webpack.config.js 
這樣就會讀取當前目錄下的webpack.config.js做爲配置文件執行打包操做
若是是經過gulp插件gulp-webpack,則能夠在gulpfile中寫上gulp任務:
var gulp = require('gulp'); 
var webpack = require('gulp-webpack'); 
var webpackConfig = require('./webpack.config'); 
gulp.task("webpack", function() { 
    return gulp 
        .src('./') 
        .pipe(webpack(webpackConfig)) 
        .pipe(gulp.dest('./build')); 
}); 
組件編寫
使用Babel提高逼格
Webpack使得咱們可使用Node.js的CommonJS規範來編寫模塊,好比一個簡單的Hello world模塊,就能夠這麼處理:
var React = require('react'); 
 
var HelloWorldComponent = React.createClass({ 
    displayName: 'HelloWorldComponent', 
    render: function() { 
        return (<div>Hello world</div>); 
    } 
}); 
 
module.exports = HelloWorldComponent; 
等等,這和以前的寫法沒啥差異啊,依舊沒有逼格...程序員敲碼要有geek範,要逼格than逼格,這太low了。如今都ES6了,React的代碼也要寫ES6,babel-loader就是幹這個的。Babel可以將ES6代碼轉換成ES5。首先須要經過命令npm install --save-dev babel-loader來進行安裝,安裝完成後就可使用了,一種使用方式是以前介紹的在webpack.config.js的loaders中配置,另外一種是直接在代碼中使用,好比:
var HelloWorldComponent = require('!babel!jsx!./HelloWorldComponent'); 
那咱們應當如何使用Babel提高代碼的逼格呢?改造一下以前的HelloWorld代碼吧:
import React from 'react'; 
 
export default class HelloWorldComponent extends React.Component { 
    constructor() { 
        super(); 
        this.state = {}; 
    } 
    render() { 
        return (<div>Hello World</div>); 
    } 

這樣在其餘組件中須要引入HelloWorldComponent組件,就只要就能夠了:
import HelloWorldComponent from './HelloWorldComponent' 
怎麼樣是否是更有逼格了?經過import引入模塊,還能夠直接定義類和類的繼承關係,這裏也再也不須要getInitialState了,直接在構造函數constructor中用this.state = xxx就行了
Babel帶來的固然還不止這些,在其幫助下還能嘗試不少優秀的ES6特性,好比箭頭函數,箭頭函數的特色就是內部的this和外部保持一致,今後能夠和that、_this說再見了
['H', 'e', 'l', 'l', 'o'].map((c) => { 
    return (<span>{c}</span>); 
}); 
其餘還有不少,具體能夠參照Babel的學習文檔
樣式編寫
我是一個強烈地Less依賴患者,脫離了Less直接寫CSS就會出現四肢乏力、不想幹活、心情煩躁等現象,並且還不喜歡在寫Less時候加前綴,日常都是gulp+less+autoprefixer直接處理的,那麼在Webpack組織的React組件中要怎麼寫呢?
沒錯,依舊是使用loader
能夠在webpack.config.js的loaders中增長Less的配置:

  test: /\.less$/, 
  loader: 'style-loader!css-loader!autoprefixer-loader!less-loader' 

經過這樣的配置,就能夠直接在模塊代碼中引入Less樣式了:
import React from 'react'; 
 
require('./HelloWorldComponent.less'); 
 
export default class HelloWorldComponent extends React.Component { 
    constructor() { 
        super(); 
        this.state = {}; 
    } 
    render() { 
        return (<div>Hello World</div>); 
    } 

其餘
Webpack的loader爲React組件化提供了不少幫助,像圖片也提供了相關的loader:
{ test: /\.png$/, loader: "url-loader?mimetype=image/png" } 
更多地loader能夠移步webpack的wiki
##在Webpack下實時調試React組件
Webpack和React結合的另外一個強大的地方就是,在修改了組件源碼以後,不刷新頁面就能把修改同步到頁面上。這裏須要用到兩個庫webpack-dev-server和react-hot-loader。
首先須要安裝這兩個庫,npm install --save-dev webpack-dev-server react-hot-loader
安裝完成後,就要開始配置了,首先須要修改entry配置:
entry: { 
  helloworld: [ 
    'webpack-dev-server/client?http://localhost:3000', 
    'webpack/hot/only-dev-server', 
    './helloworld' 
  ] 
}, 
經過這種方式指定資源熱啓動對應的服務器,而後須要配置react-hot-loader到loaders的配置當中,好比個人全部組件代碼所有放在scripts文件夾下:

  test: /\.js?$/, 
  loaders: ['react-hot', 'babel'], 
  include: [path.join(__dirname, 'scripts')] 

最後配置一下plugins,加上熱替換的插件和防止報錯的插件:
plugins: [ 
  new webpack.HotModuleReplacementPlugin(), 
  new webpack.NoErrorsPlugin() 

這樣配置就完成了,可是如今要調試須要啓動一個服務器,並且以前配置裏映射到http://localhost:3000,因此就在本地3000端口起個服務器吧,在項目根目錄下面建個server.js:
var webpack = require('webpack'); 
var WebpackDevServer = require('webpack-dev-server'); 
var config = require('./webpack.config'); 
 
new WebpackDevServer(webpack(config), { 
  publicPath: config.output.publicPath, 
  hot: true, 
  historyApiFallback: true 
}).listen(3000, 'localhost', function (err, result) { 
  if (err) console.log(err); 
  console.log('Listening at localhost:3000'); 
}); 
這樣就能夠在本地3000端口開啓調試服務器了,好比個人頁面是根目錄下地index.html,就能夠直接經過http://localhost:3000/index.html訪問頁面,修改React組件後頁面也會被同步修改,這裏貌似使用了websocket來同步數據。圖是一個簡單的效果:

結束
React的組件化開發頗有想法,而Webpack使得React組件編寫和管理更加方便,這裏只涉及到了React和Webpack得很小一部分,還有更多的最佳實踐有待在學習的路上不斷髮掘html

相關文章
相關標籤/搜索