記得大二的時候剛學習 Java,我作的第一個圖形化用戶界面是一個仿QQ的登陸窗口,其實就是一些輸入框和按鈕,可是記得當時以爲超級有成就感,因而後來開始喜歡上寫 Java,還作了不少小遊戲像飛機大戰、坦克大戰啥的,本身還以爲特別有意思。
後來開始學前端,其實想一想也是作圖形化用戶界面,不過是換了一個運行環境而已。可是寫着寫着發現很不順手,和用 Java 寫感受很不同,到底哪不對呢。
用 Java 寫界面的時候,按鈕是按鈕,輸入框是輸入框,我作登陸窗口的時候,只要定義一個登陸窗口類,而後設置佈局、把按鈕、輸入框加進去,一個登陸窗口就出來了。
反觀前端的實現,要寫一個登陸窗口,得先在 html 裏定義結構,在 css 裏制定樣式,而後在 js 裏添加行爲,最頭疼的是 js 裏不只僅只是這個登陸窗口的行爲,還有頁面初始化的代碼、別的按鈕的監聽等等等等一大堆亂七八糟的代碼(做爲菜鳥的自我吐槽)
其實我理解的以上問題的關鍵詞就是 組件化 ,之因此之前寫的那麼彆扭,很大程度上是本身帶着組件化的思想,可是寫不出組件化的代碼。javascript
直到如今使用上 React,真是感受眼前一亮。固然還有不少不少須要學習的地方,就從如今開始,配合着 Webpack,踏上 React 的開發之路吧。css
下面經過 React 編寫一個簡單的例子,就是經常使用的微博發送的表單。html
項目目錄以下:前端
/js -- /components ---- /Publisher ------ Publish.css ------ Publish.jsx -- app.js /css -- base.css index.html webpack.config.js
js/components
目錄存放全部的組件,好比 Publisher
是咱們的表單組件,裏面存放這個表單的子組件(若是有的話)、組件的 jsx
文件以及組件本身的樣式。java
js/app.js
是入口文件node
css
存放全局樣式react
index.html
主頁webpack
webpack.config.js
webpack 的配置文件git
編輯 webpack.config.js
github
var webpack = require('webpack'); module.exports = { entry: './js/app.js', output: { path: __dirname, filename: 'bundle.js' }, module: { loaders: [ { test: /\.jsx?$/, loader: 'babel', query: { presets: ['react', 'es2015'] } }, { test: /\.css$/, loader: 'style!css' } ] }, plugins: [ new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false } }) ] }
上一篇文章 裏是使用 webpack 進行 ES6 開發,其實不論是 ES6 也好,React 也好,webpack 起到的是一個打包器的做用,配置項和這裏大體類似,就再也不贅述。
不一樣的是在 babel-loader
裏增長了 react 的轉碼規則。
另外這裏使用到了 webpack 的一個內置插件 UglifyJsPlugin
,經過他能夠對生成的文件進行壓縮。詳細的介紹請看這裏。
首先保證安裝了 nodejs 。
npm init
npm install webpack -g
npm install react react-dom --save-dev
本項目使用到的有 babel-loader、css-loader、style-loader。
babel-loader 進行轉碼
css-loader 對 css 文件進行打包
style-loader 將樣式添加進 DOM 中
詳細請看這裏。
npm install babel-loader css-loader style-loader --save-dev
npm install babel-preset-es2015 babel-preset-react --save-dev
index.html
中,引用的 js
文件是經過 webpack 生成的 bundle.js
, css
文件是寫在 /css
目錄下的 base.css
。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <link rel="stylesheet" href="css/base.css"> </head> <body> <div id="container"></div> <script src="bundle.js"></script> </body> </html>
base.css
裏面存放的是全局樣式,也就是與組件無關的。
html, body, textarea { padding: 0; margin: 0; } body { font: 12px/1.3 'Arial','Microsoft YaHei'; background: #73a2b0; } textarea { resize: none; } a { color: #368da7; text-decoration: none; }
/js/app.js
是入口文件,引入了 Publisher
組件
import React from 'react'; import ReactDOM from 'react-dom'; import Publisher from './components/Publisher/Publisher.jsx'; ReactDOM.render( <Publisher />, document.getElementById('container') );
好的,下面開始編寫組件,首先,肯定這個組件的組成部分,由於是一個簡單的表單,因此不須要繼續劃分子組件
表單分爲上中下三部分,title
裏面包含熱門微博和剩餘字數的提示,textElDiv
包含輸入框,btnWrap
包含發佈按鈕。
import React from 'react'; class Publisher extends React.Component { constructor(...args) { super(...args); } render() { return ( <div className="publisher"> <div className="title"> <div> <a href="#">網友曝光兩女孩蹲着等地鐵,稱沒教養,你怎麼看(投票)</a> </div> <div className="tips"> <span>還能夠輸入</span><strong>140</strong>字 </div> </div> <div className="textElDiv"> <textarea></textarea> </div> <div className="btnWrap"> <a className="publishBtn" href="javascript:void(0)">發佈</a> </div> </div> ); } } export default Publisher;
咱們暫時經過 className
給組件定義了樣式名,但尚未實際寫樣式代碼,由於要保證組件的封裝性,因此咱們不但願組件的樣式編寫到全局中去以避免影響其餘組件,最好像咱們的目錄劃分同樣,組件本身的樣式跟着組件本身走,並且這個樣式不影響其餘組件。這裏就須要用到 css-loader
了。
css-loader
能夠將 css
文件進行打包,並且能夠對 css
文件裏的 局部 className 進行哈希編碼。這意味着能夠這樣寫樣式文件:
/* xxx.css */ :local(.className) { background: red; } :local .className { color: green; } :local(.className .subClass) { color: green; } :local .className .subClass :global(.global-class-name) { color: blue; }
通過處理以後,則變成:
._23_aKvs-b8bW2Vg3fwHozO { background: red; } ._23_aKvs-b8bW2Vg3fwHozO { color: green; } ._23_aKvs-b8bW2Vg3fwHozO ._13LGdX8RMStbBE9w-t0gZ1 { color: green; } ._23_aKvs-b8bW2Vg3fwHozO ._13LGdX8RMStbBE9w-t0gZ1 .global-class-name { color: blue; }
也就是咱們能夠在不一樣的組件樣式中定義 .btn
的樣式名,可是通過打包以後,在全局裏面就被轉成了不一樣的哈希編碼,由此解決了 css
全局命名衝突的問題。
關於 css-loader
更詳細的使用,請參考這裏。
那麼 Publisher 的樣式以下:
:local .publisher{ width: 600px; margin: 10px auto; background: #ffffff; box-shadow: 0 0 2px rgba(0,0,0,0.15); border-radius: 2px; padding: 15px 10px 10px; height: 140px; position: relative; font-size: 12px; } :local .title{ position: relative; } :local .title div { position: absolute; right: 0; top: 2px; } :local .tips { color: #919191; display: none; } :local .textElDiv { border: 1px #cccccc solid; height: 68px; margin: 25px 0 0; padding: 5px; box-shadow: 0px 0px 3px 0px rgba(0,0,0,0.15) inset; } :local .textElDiv textarea { border: none; border: 0px; font-size: 14px; word-wrap: break-word; line-height: 18px; overflow-y: auto; overflow-x: hidden; outline: none; background: transparent; width: 100%; height: 68px; } :local .btnWrap { float: right; padding: 5px 0 0; } :local .publishBtn { display: inline-block; height: 28px; line-height: 29px; width: 60px; font-size: 14px; background: #ff8140; border: 1px solid #f77c3d; border-radius: 2px; color: #fff; box-shadow: 0px 1px 2px rgba(0,0,0,0.25); padding: 0 10px 0 10px; text-align: center; outline: none; } :local .publishBtn.disabled { background: #ffc09f; color: #fff; border: 1px solid #fbbd9e; box-shadow: none; cursor: default; }
而後就能夠在 Publisher.jsx
中這樣使用了
import React from 'react'; import style from './Publisher.css'; class Publisher extends React.Component { constructor(...args) { super(...args); } render() { return ( <div className={style.publisher}> <div className={style.title}> <div> <a href="#">網友曝光兩女孩蹲着等地鐵,稱沒教養,你怎麼看(投票)</a> </div> <div className={style.tips}> <span>還能夠輸入</span><strong>140</strong>字 </div> </div> <div className={style.textElDiv}> <textarea></textarea> </div> <div className={style.btnWrap}> <a className={style.publishBtn>發佈</a> </div> </div> ); } } export default Publisher;
這樣組件的樣式已經添加進去了,接下來就純粹是進行 React 開發了。
表單的需求以下:
輸入框獲取焦點時,輸入框邊框變爲橙色,右上角顯示剩餘字數的提示;輸入框失去焦點時,輸入框邊框變爲灰色,右上角顯示熱門微博。
輸入字數小於且等於140字時,提示顯示剩餘可輸入字數;輸入字數大於140時,提示顯示已經超過字數。
輸入字數大於0且不大於140字時,按鈕爲亮橙色且可點擊,不然爲淺橙色且不可點擊。
首先,給 textarea
添加 onFocus
、onBlur
、onChange
事件,經過 handleFocus
、handleBlur
、handleChange
來處理輸入框獲取焦點、失去焦點和輸入。
而後將輸入的內容保存在 state
裏,這樣每當內容發生變化時,就能方便的對變化進行處理。
對於按鈕的變化、熱門微博和提示之間的轉換,根據 state
中內容的變化來切換樣式就能輕鬆地作到。
完整代碼以下:
import React from 'react'; import style from './Publisher.css'; class Publisher extends React.Component { constructor(...args) { super(...args); // 定義 state this.state = { content: '' } } /** * 獲取焦點 **/ handleFocus() { // 改變邊框顏色 this.refs.textElDiv.style.borderColor = '#fa7d3c'; // 切換右上角內容 this.refs.hot.style.display = 'none'; this.refs.tips.style.display = 'block'; } /** * 失去焦點 **/ handleBlur() { // 改變邊框顏色 this.refs.textElDiv.style.borderColor = '#cccccc'; // 切換右上角內容 this.refs.hot.style.display = 'block'; this.refs.tips.style.display = 'none'; } /** * 輸入框內容發生變化 **/ handleChange(e) { // 改變狀態值 this.setState({ content: e.target.value }); } render() { return ( <div className={style.publisher}> <div className={style.title}> <div ref="hot"> <a href="#">網友曝光兩女孩蹲着等地鐵,稱沒教養,你怎麼看(投票)</a> </div> <div className={style.tips} ref="tips"> <span>{this.state.content.length > 140 ? '已超出' : '還能夠輸入'}</span><strong>{this.state.content.length > 140 ? this.state.content.length - 140 : 140 - this.state.content.length}</strong>字 </div> </div> <div className={style.textElDiv} ref="textElDiv"> <textarea onFocus={this.handleFocus.bind(this)} onBlur={this.handleBlur.bind(this)} onChange={this.handleChange.bind(this)}></textarea> </div> <div className={style.btnWrap}> <a className={style.publishBtn + ((this.state.content.length > 0 && this.state.content.length <= 140) ? '' : ' ' + style.disabled)} href="javascript:void(0)">發佈</a> </div> </div> ); } } export default Publisher;
經過 --display-error-detail
能夠顯示 webpack 出現錯誤的中間過程,方便在出錯時進行查看。
--progress --colors
能夠顯示進度
--watch
能夠監視文件的變化並在變化後從新加載
運如以下:
webpack --display-error-detail --progress --colors --watch