Webpack + React 開發之路

ReggiePedig 8年前發布 | 33K 次閱讀 React 前端技術 webpack

來自: https://segmentfault.com/a/1190000004506929

雜七雜八的想法

記得大二的時候剛學習 Java,我做的第一個圖形化用戶界面是一個仿QQ的登錄窗口,其實就是一些輸入框和按鈕,但是記得當時覺得超級有成就感,于是后來開始喜歡上寫 Java,還做了很多小游戲像飛機大戰、坦克大戰啥的,自己還覺得特別有意思。

后來開始學前端,其實想想也是做圖形化用戶界面,不過是換了一個運行環境而已。但是寫著寫著發現很不順手,和用 Java 寫感覺很不一樣,到底哪不對呢。

用 Java 寫界面的時候,按鈕是按鈕,輸入框是輸入框,我做登錄窗口的時候,只要定義一個登錄窗口類,然后設置布局、把按鈕、輸入框加進去,一個登錄窗口就出來了。

反觀前端的實現,要寫一個登錄窗口,得先在 html 里定義結構,在 css 里制定樣式,然后在 js 里添加行為,最頭疼的是 js 里不僅僅只是這個登錄窗口的行為,還有頁面初始化的代碼、別的按鈕的監聽等等等等一大堆亂七八糟的代碼(作為菜鳥的自我吐槽)

其實我理解的以上問題的關鍵詞就是 組件化 ,之所以以前寫的那么別扭,很大程度上是自己帶著組件化的思想,但是寫不出組件化的代碼。

</div>

直到現在使用上 React,真是感覺眼前一亮。當然還有很多很多需要學習的地方,就從現在開始,配合著 Webpack,踏上 React 的開發之路吧。

制作一個微博發送表單

下面通過 React 編寫一個簡單的例子,就是常用的微博發送的表單。

一、新建項目

項目目錄如下:

/js
-- /components
---- /Publisher
------ Publish.css
------ Publish.jsx
-- app.js
/css
-- base.css
index.html
webpack.config.js
  • js/components 目錄存放所有的組件,比如 Publisher 是我們的表單組件,里面存放這個表單的子組件(如果有的話)、組件的 jsx 文件以及組件自己的樣式。

  • js/app.js 是入口文件

  • css 存放全局樣式

  • index.html 主頁

  • webpack.config.js webpack 的配置文件

  • </ul>

    二、配置 Webpack

    編輯 webpack.config.js

    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 } }) ] }</pre>

    上一篇文章 里是使用 webpack 進行 ES6 開發,其實不管是 ES6 也好,React 也好,webpack 起到的是一個打包器的作用,配置項和這里大致相似,就不再贅述。

    不同的是在 babel-loader 里增加了 react 的轉碼規則。

    另外這里使用到了 webpack 的一個內置插件 UglifyJsPlugin ,通過他可以對生成的文件進行壓縮。詳細的介紹請看 這里

    三、安裝一系列東東

    首先保證安裝了 nodejs 。

    1) 初始化項目

    npm init

    2) 安裝 webpack

    npm install webpack -g

    3) 安裝 React

    npm install react react-dom --save-dev

    4) 安裝加載器

    本項目使用到的有 babel-loader、css-loader、style-loader。

    • babel-loader 進行轉碼

    • css-loader 對 css 文件進行打包

    • style-loader 將樣式添加進 DOM 中

    詳細請看 這里

    npm install babel-loader css-loader style-loader --save-dev

    5) 安裝轉碼規則

    npm install babel-preset-es2015 babel-preset-react --save-dev

    四、碼代碼

    index.html 中,引用的 js 文件是通過 webpack 生成的 bundle.js , css 文件是寫在 /css 目錄下的 base.css 。

    index.html

    <!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>

    /css/base.css

    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; }</pre>

    /js/app.js

    /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') );</pre>

    /js/components/Publisher/Publisher.jsx

    好的,下面開始編寫組件,首先,確定這個組件的組成部分,因為是一個簡單的表單,所以不需要繼續劃分子組件

    表單分為上中下三部分, 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;</pre>

    我們暫時通過 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; }</pre>

    經過處理之后,則變成:

    ._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 的樣式如下:

    /js/components/Publisher/Publisher.css

    :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; }</pre>

    然后就可以在 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;</pre>

    這樣組件的樣式已經添加進去了,接下來就純粹是進行 React 開發了。

    編寫 Publisher.jsx

    表單的需求如下:

    1. 輸入框獲取焦點時,輸入框邊框變為橙色,右上角顯示剩余字數的提示;輸入框失去焦點時,輸入框邊框變為灰色,右上角顯示熱門微博。

    2. 輸入字數小于且等于140字時,提示顯示剩余可輸入字數;輸入字數大于140時,提示顯示已經超過字數。

    3. 輸入字數大于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;</pre>

    五、運行

    • 通過 --display-error-detail 可以顯示 webpack 出現錯誤的中間過程,方便在出錯時進行查看。

    • --progress --colors 可以顯示進度

    • --watch 可以監視文件的變化并在變化后重新加載

    運如如下:

    webpack --display-error-detail --progress --colors --watch
    </div>

 本文由用戶 ReggiePedig 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!