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)