1. 程式人生 > >React.js留言板(Mock.js模仿資料互動)

React.js留言板(Mock.js模仿資料互動)



React 起源於 Facebook 的內部專案,因為該公司對市場上所有JavaScript 框架都不滿意,都不滿意,就決定自己寫一套,用來架設Instagram的網站。做出來以後,發現這套東西很好用,就在2013年5月開開源了。

由於 React 的設計思想極其獨特,屬於革命性創新,效能出眾,程式碼邏輯卻非常簡單。所以,越來越多的人開始關注和使用,認為它可能是將來 Web 開發的主流工具。

先說明下,本專案是在初學React.js之後實現的簡單留言板的新增和展示(Mock.js實現偽資料互動),功能比較簡單,不過相對也更適合React初學者檢視。此篇文件用來說明專案的想法思路以及某些關鍵程式碼的展示,可以分為專案說明,專案思路,程式碼展示三個部分,專案效果:

一、專案說明:

效果檢視:下載下來後想要檢視的話,直接瀏覽器開啟dist/html/index.html即可檢視並進行新增等操作,無需開啟服務;

主要程式碼:src/js/index.js下是實現留言板的js檔案程式碼,200行帶註釋,dist下的index.js經過了壓縮,要檢視的話請轉到src下;

學習前提:專案依託於node.js,檢視可以直接開啟,修改的還想看到效果的話需要node.js輔助壓縮拷貝檔案;

專案修改程式碼並且檢視效果(僅限你想在git下來的此專案裡修改):

首先下載安裝nodejs,然後git clone專案;

根據專案中的package.json使用npm下載安裝需要的依賴包;

在src下修改程式碼,不修改檔案路徑的話,不用修改gulpfile.js。

目錄結構說明:

src/js/index.js 關鍵程式碼,自己寫的js事件實現都在這個裡面了;

src/asserts 依賴的公共js和css檔案(jQuery、Bootstrap和Mock);

src/css/index.css 自己寫的頁面css效果,主要是提示訊息冬天顯示隱藏的CSS3動畫效果;

src/html/index.html html頁面。

修改後在控制檯專案檔案路徑下執行gulp即可自動壓縮和拷貝src檔案到dist中去,瀏覽器開啟dist/html/index.html即可檢視頁面。

二、專案思路:

模組化設計:

React.js可以將虛擬DOM封裝為模組,方便維護和複用,本專案前端可以分為四個部分:

表單MessageForm,用於留言板新增留言;

列表MessageList,用於留言展示;

提示MessageAlert,用於新增留言提交時顯示提交成功/失敗訊息;

總模組Message,將其他模組整合,傳送ajax請求更新State值以重新整理介面。

後端資料才有Mock.js是阿里做的偽資料js檔案,頁面引用此檔案後能夠攔截頁面網後臺傳送的ajax請求,同時提供了Mock物件;

Mock.mock()方法可以攔截指定請求並返回自定義的json資料,所以實現了無需後臺的資料互動;

攔截url需要使用者自己定義,可以參照src/js/mock.value.js,裡面寫了攔截/get和/post兩個url的方法。

三、專案程式碼:

這裡貼上部分程式碼(index.js,index.css,mock.value.js),有興趣的可以下載下來看下:

關鍵程式碼index.js

var React = require('react');
var ReactDOM = require('react-dom');

// 留言板表單模組
var MessageForm = React.createClass({
    onSubmit: function(e){ // 點選提交按鈕,獲取輸入框內容,呼叫提交函式傳送請求
        e.preventDefault();
        var that = this, // 快取this指標指向
        name = encodeURIComponent(that.refs.name.value.trim()), // 獲取名稱輸入框資料
        content = encodeURIComponent(that.refs.content.value.trim());
        that.props.submit(name,content); // 呼叫submit處理
        // 提交完成,清空輸入框
        that.refs.name.value = '';
        that.refs.content.value = '';
    },
    onReset: function(e){ // 輸入框返回初始狀態,及內容為空(重置按鈕呼叫)
        e.preventDefault();
        this.refs.form.reset();
    },
    render: function(){
        return (
            <form role="form" ref="form">
                <div className="form-group m-bottom-10 clearfix">
                    <div className="col-xs-4">
                        <input className="form-control" type="text" name="name" ref="name" placeholder="請輸入標題" required/>
                    </div>
                </div>
                <div className="form-group m-bottom-10 clearfix">
                    <div className="col-xs-12">
                        <textarea className="form-control" name="content" ref="content" placeholder="請輸入內容" required></textarea>
                    </div>
                </div>
                <div className="form-group m-bottom-10 clearfix">
                    <button className="btn btn-default btn-sm pull-right" onClick={this.onReset}>重置</button>
                    <button className="btn btn-default btn-sm pull-right" onClick={this.onSubmit}>提交</button>
                </div>
            </form>
            )
    }
})

// 留言板顯示模組
var MessageList = React.createClass({
    render: function(){
        // 根據獲取到的留言資訊,每條訊息對應一個li標籤進行顯示
        var message = this.props.data.map(function(item,idx){
            return (
                <li className="list-group-item" key={idx}>
                <h4> {item.name} </h4>
                <p> {item.content} </p>
                </li>
            )
        });
        return(
            <ul className="list-group" id="message-container">
            {message}
            </ul>
        );
    }
});

// 留言板提示訊息模組
var MessageAlert = React.createClass({
    render: function(){
        switch (this.props.alert){
            case 'info':
                return (
                    <div className="alert alert-info">
                        <strong>提示!</strong>由於後臺資料是假資料,假資料設定了當資料條目超過50條時,資料內容自動初始化為最初的三條。
                    </div>
                );
            case 'success':
                return (
                    <div className="alert alert-success">
                        <strong>通知!</strong>提交成功。
                    </div>
                );
            case 'warning':
                return (
                    <div className="alert alert-warning">
                        <strong>警告!</strong>標題和內容不能為空。
                    </div>
                );
            default: 
                return null;
        }
    }
});

// 留言板總體模組
var MessagePanel = React.createClass({
    getInitialState: function(){ // 初始化data為空
        return {
            data: [],
            alert: ''
        }
    },
    submit: function(name,content){ // 提交資料,將表單中的資料ajax提交
        var that = this; // 快取this指標指向
        that.setState({
            alert: ''
        });
        if($.isEmptyObject(name) || $.isEmptyObject(content)){
            window.setTimeout(function(){ // 設計延遲,否則此處的setState會和前面的融合處理,導致多次空點選時無法彈出
                that.setState({ // 提示內容不能為空
                 alert: 'warning'
            });
            },100)
        }else{
            $.ajax({
                type: 'post',
                url: '/post',
                contentType: 'application/json; charset=utf-8',
                data:{name: name, content: content},
                dataType: 'json'
            }).done(function(resp){
                if('success' === resp.status){ // 提交成功
                    that.listContent(); // 更新留言板列表
                    that.scrollBottom(); // 頁面滾動到底部
                    that.setState({ // 提示內容不能為空
                         alert: 'success'
                    });
                }else{
                    that.setState({ // 提示內容不能為空
                         alert: 'info'
                    });
                }
            }.bind(that));
        }
    },
    scrollBottom: (function gotoBottom(){ // 新增留言成功後頁面滾動到最下方進行檢視
        var speeding = 1.1, stime = 10,
        top = document.documentElement.scrollTop || document.body.scrollTop || 1; // 多瀏覽器相容獲取頁面Y軸滾動偏移位置,若位置為0,賦值為1
        window.scrollTo(0, Math.ceil(top * speeding)); // 頁面向下滾動滾動
        var topnew = document.documentElement.scrollTop || document.body.scrollTop; // 獲取向下滾動後的Y軸偏移位置
        if(topnew > top) {
            // 迴圈timeout是動態滾動效果
            window.setTimeout(gotoBottom, stime);
        }
    }),
    listContent: function(){ // 獲取留言列表
        var that = this;
        $.ajax({
            type: 'get',
            url: '/get',
            dataType: 'json'
        }).done(function(resp){
            if('success' === resp.status){ // 獲取留言成功,將留言賦值到模組的state值中
                that.setState({
                    data:resp.data
                })
            }
        }.bind(that));
    },
    componentDidMount: function(){ // 初始化載入介面獲取列表
        this.listContent();
    },
    gotoTop: (function gotoTop(){ // 函式名是為了遞迴呼叫(鑑於嚴格模式下無法使用calllee)
        var speeding = 1.1, stime = 10;
        var top = document.documentElement.scrollTop || document.body.scrollTop; // 多瀏覽器相容獲取頁面Y軸滾動偏移位置
        window.scrollTo(0, Math.floor(top / speeding)); // 頁面向上滾動
        if(top > 0) {
            // 迴圈timeout是動態滾動效果
            window.setTimeout(gotoTop, stime);
        }
    }),
    render: function(){
        return (
            <div>
                <div className="col-xs-2">
                </div>
                <div className="col-xs-8">
                    <MessageForm submit={this.submit} />
                    <MessageList data={this.state.data} />
                    <MessageAlert alert={this.state.alert}/>
                </div>
                <div className="col-xs-2">
                    <span className="icon-top" onClick={this.gotoTop}></span>
                </div>
            </div>
        );
    }
});
ReactDOM.render(<MessagePanel />, document.getElementById("message-panel"));

index.css程式碼

/* 用於控制標籤之間的上下邊距 */
.m-bottom-10 {
    margin-bottom: 10px;
}
/* textarea大小固定 */
textarea {
    resize:none;
}
.btn {
    margin-right: 5px;
}
/* 返回頂部圖片樣式 */
.icon-top {
    position: fixed;
    right: 20px;
    bottom: 10px;
    background: transparent url("../css/img/top.png") no-repeat scroll 0px 0px;
    display: inline-block;
    width: 38px;
    height: 65px;
    cursor: pointer;
    vertical-align: middle;
}
/* 返回頂部滑鼠經過圖片樣式 */
.icon-top:hover {
    background: transparent url("../css/img/top.png") no-repeat scroll 0px -65px;
}
/* 提示訊息效果 */
.alert {
    position: fixed;
    top:-50px;
    left:25%;
    width:50%;
    animation: alertheight 3s;
    -moz-animation: alertheight 3s; /* Firefox */
    -webkit-animation: alertheight 3s; /* Safari and Chrome */
    -o-animation: alertheight 3s; /* Opera */
}
@keyframes alertheight
{
0%   {top: -50px;}
50%  {top: 20px;}
100% {top: -50px;}
}
@-moz-keyframes alertheight /* Firefox */
{
0%   {top: -50px;}
50%  {top: 20px;}
100% {top: -50px;}
}
@-webkit-keyframes alertheight /* Safari and Chrome */
{
0%   {top: -50px;}
50%  {top: 20px;}
100% {top: -50px;}
}
@-o-keyframes alertheight /* Opera */
{
0%   {top: -50px;}
50%  {top: 20px;}
100% {top: -50px;}
}

mock.value.js程式碼

(function($){
    // 假資料
    var initRes = [
        {name: '這是第一條留言', content: '這是第一條留言的內容'},
        {name: '這是第二條留言', content: '這是第二條留言的內容'},
        {name: '這是第一條留言', content: '這是第一條留言的內容'}
    ],resArr = $.extend([],initRes);
    Mock.mock(/\/get/,function(options){ // 攔截獲取留言列表請求,返回列表資訊
        return {status: 'success', data: resArr};
    }).mock(/\/post/,function(options){ // 攔截新增留言請求
        var arr = options.body.split('&'),obj = {};
        arr.map(function(item){
            var items = item.split('=');
            obj[items[0]] = decodeURIComponent(decodeURIComponent(items[1]));
        })
        resArr.push(obj); // 將留言載入到假資料中
        if(resArr.length < 50){ //如果假資料不超過50條,返回成功,否則返回false,同時初始化假資料
            return {status: 'success'};
        }else{
            resArr = $.extend([],initRes);
            return {status: 'false'};
        }
    })
})(jQuery)