Webpack + React 開發之路

雜七雜八的想法

記得大二的時候剛學習 Java,我作的第一個圖形化用戶界面是一個仿QQ的登陸窗口,其實就是一些輸入框和按鈕,可是記得當時以爲超級有成就感,因而後來開始喜歡上寫 Java,還作了不少小遊戲像飛機大戰、坦克大戰啥的,本身還以爲特別有意思。
後來開始學前端,其實想一想也是作圖形化用戶界面,不過是換了一個運行環境而已。可是寫着寫着發現很不順手,和用 Java 寫感受很不同,到底哪不對呢。
用 Java 寫界面的時候,按鈕是按鈕,輸入框是輸入框,我作登陸窗口的時候,只要定義一個登陸窗口類,而後設置佈局、把按鈕、輸入框加進去,一個登陸窗口就出來了。
反觀前端的實現,要寫一個登陸窗口,得先在 html 裏定義結構,在 css 裏制定樣式,而後在 js 裏添加行爲,最頭疼的是 js 裏不只僅只是這個登陸窗口的行爲,還有頁面初始化的代碼、別的按鈕的監聽等等等等一大堆亂七八糟的代碼(做爲菜鳥的自我吐槽)
其實我理解的以上問題的關鍵詞就是 組件化 ,之因此之前寫的那麼彆扭,很大程度上是本身帶着組件化的思想,可是寫不出組件化的代碼。javascript

直到如今使用上 React,真是感受眼前一亮。固然還有不少不少須要學習的地方,就從如今開始,配合着 Webpack,踏上 React 的開發之路吧。css

製做一個微博發送表單

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

圖片描述

1、新建項目

項目目錄以下:前端

/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

2、配置 Webpack

編輯 webpack.config.jsgithub

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,經過他能夠對生成的文件進行壓縮。詳細的介紹請看這裏

3、安裝一系列東東

首先保證安裝了 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

4、碼代碼

index.html 中,引用的 js 文件是經過 webpack 生成的 bundle.jscss 文件是寫在 /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;
}

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

/js/components/Publisher/Publisher.jsx

好的,下面開始編寫組件,首先,肯定這個組件的組成部分,由於是一個簡單的表單,因此不須要繼續劃分子組件

clipboard.png

表單分爲上中下三部分,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 的樣式以下:

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

而後就能夠在 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 開發了。

編寫 Publisher.jsx

表單的需求以下:

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

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

  3. 輸入字數大於0且不大於140字時,按鈕爲亮橙色且可點擊,不然爲淺橙色且不可點擊。

首先,給 textarea 添加 onFocusonBluronChange 事件,經過 handleFocushandleBlurhandleChange 來處理輸入框獲取焦點、失去焦點和輸入。

而後將輸入的內容保存在 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;

5、運行

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

  • --progress --colors 能夠顯示進度

  • --watch 能夠監視文件的變化並在變化後從新加載

運如以下:

webpack --display-error-detail --progress --colors --watch
相關文章
相關標籤/搜索