
整體布局:
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>ListView 2</title>
<meta name="viewport" content="width=device-width,
user-scalable=no, initial-scale=1.0, maximum-scale=1.0,
minimum-scale=1.0">
<script src="https://cdn.bootcss.com/react/15.4.2/react.min.js"></script>
<script src="https://cdn.bootcss.com/react/15.4.2/react-dom.min.js"></script>
<script src="https://cdn.bootcss.com/babel-standalone/6.22.1/babel.min.js">
</script>
<script src="zepto.js"></script>
</head>
<body>
<div id="app"></div>
<script type="text/babel">
//....
</script>
</body>
</html>
首先需要引入React基礎(chǔ)庫,dom庫,jsx解析庫和移動(dòng)端Jquery庫(用于動(dòng)態(tài)請求異步加載數(shù)據(jù)),然后創(chuàng)建一個(gè)Div,引入自己的組建。
整個(gè)應(yīng)用組件
var App = React.createClass({
getInitialState: function () {
return {
page: 1,
article: [],//文章列表
};
},
componentDidMount: function () {//組件被加載之后,默認(rèn)加載第一頁數(shù)據(jù)
this.getData(1);
},
onRefresh: function () {//下拉刷新函數(shù)
this.setState({page: 1});
this.getData(this.state.page);
},
onLoadMore: function () {//加載更多函數(shù)
var page = this.state.page + 1;
console.log(page)
this.setState({page: page});
this.getData(page);
},
getItem: function (article) {
return <Item article={article}/>;
},
getData: function (page) {//獲取數(shù)據(jù)的函數(shù)
var self = this;
$.ajax({
url: "http://wangyi.butterfly.mopaasapp.com/news/api?
type=travel&page=" + page + "&limit=5",
type: 'GET',
success: function (data) {
// 4.對data進(jìn)行處理,并進(jìn)行對應(yīng)的dom渲染
if (page == 1) {//如果是第一頁,直接覆蓋之前的數(shù)據(jù)
self.setState({article: data.list})
//父組件的setState 改變的自己的狀態(tài)的同時(shí)觸發(fā)了自組件
的componentWillReceiveProps
// 子組件可以在componentWillReceiveProps里接受新的參
數(shù),改變自己的state會(huì)自動(dòng)觸發(fā)render渲染
} else {
self.setState({//否則累加數(shù)組
article: self.state.article.concat(data.list)
})
}
},
error: function () {
// 4.錯(cuò)誤處理
}
})
},
render: function () {
return (
<ListView
onRefresh={this.onRefresh} //從外面?zhèn)鬟M(jìn)去的下拉刷新回調(diào)函數(shù)
onLoadMore={this.onLoadMore}//從外面?zhèn)鬟M(jìn)去的加載更過回調(diào)函數(shù)
article={this.state.article}//從外面?zhèn)鬟M(jìn)去的文章列表數(shù)據(jù)數(shù)組
getItem={this.getItem} //從外面?zhèn)鬟M(jìn)去的獲取列表子項(xiàng)的回調(diào)函數(shù)
/>
);
}
});
解析:
1、首先對于組建進(jìn)行初始化狀態(tài)設(shè)置,當(dāng)組建被加載后,默認(rèn)加載第一頁數(shù)據(jù);
2、當(dāng)進(jìn)行下拉刷新時(shí),設(shè)置狀態(tài)為第一頁并獲取第一頁數(shù)據(jù);
3、當(dāng)上拉加載更多時(shí),狀態(tài)為下一頁,并獲取下一頁的數(shù)據(jù)。
通過Ajax獲取新聞數(shù)據(jù),對Data進(jìn)行相應(yīng)的處理,并進(jìn)行對應(yīng)的dom渲染。
** 渲染整個(gè)app**
ReactDOM.render(
<App />,
document.getElementById('app')
);
** 靜態(tài)常量**
var XLJZ = '下拉加載';
var SKJZ = '松開加載';
var JZ = '加載中...'
var dropDownRefreshText = XLJZ;
var dragValve = 40; // 下拉加載閥值
var scrollValve = 40; // 滾動(dòng)加載閥值
子列表項(xiàng)組件,只負(fù)責(zé)渲染外面?zhèn)鬟f給他的數(shù)據(jù)(css設(shè)計(jì)樣式)
var Item = React.createClass({
render: function () {
return (<li className="left_four_ul_li">
<img src={this.props.article.imgurl}/>
<div className="left_four_ul_li_para">
<h1>{this.props.article.title}</h1>
<p>{this.props.article.time}</p>
<span>{this.props.article.docurl}</span>
</div>
</li>
)
}
});
** 列表組件**
var ListView = React.createClass({
getInitialState: function () {//初始化狀態(tài)
return {
translate: 0,//位移
dragLoading: false,//是否在下拉刷新中
scrollerLoading: false,//是否在加載更多中
openDragLoading: true,//是否開啟下拉刷新
openScrollLoading: true,//是否開啟下拉刷新
data: []//默認(rèn)的列表空數(shù)據(jù)
};
},
componentDidMount: function () {//組建加載完畢,進(jìn)行初始化賦值
this.setState(
{
translate: 0,
openDragLoading: this.props.openDragLoading || true,//根據(jù)外面設(shè)置的開關(guān)改變自己的狀態(tài)
openScrollLoading: this.props.openScrollLoading || true
}
);
this.initRefresh();//初始化下拉刷新
this.initScroll();//初始化滾動(dòng)加載更多
},
initRefresh: function (defaults, options) {
var self = this;//對象轉(zhuǎn)存,防止閉包函數(shù)內(nèi)無法訪問
var isTouchStart = false; // 是否已經(jīng)觸發(fā)下拉條件
var isDragStart = false; // 是否已經(jīng)開始下拉
var startX, startY; // 下拉方向,touchstart 時(shí)的點(diǎn)坐標(biāo)
var hasTouch = 'ontouchstart' in window;//判斷是否是在移動(dòng)端手機(jī)上
// 監(jiān)聽下拉加載,兼容電腦端
if (self.state.openDragLoading) {
self.refs.scroller.addEventListener('touchstart', touchStart, false);
self.refs.scroller.addEventListener('touchmove', touchMove, false);
self.refs.scroller.addEventListener('touchend', touchEnd, false);
self.refs.scroller.addEventListener('mousedown', touchStart, false);
self.refs.scroller.addEventListener('mousemove', touchMove, false);
self.refs.scroller.addEventListener('mouseup', touchEnd, false);
}
function touchStart(event) {
if (self.refs.scroller.scrollTop <= 0) {
isTouchStart = true;
startY = hasTouch ? event.changedTouches[0].pageY : event.pageY;
startX = hasTouch ? event.changedTouches[0].pageX : event.pageX;
}
}
function touchMove(event) {
if (!isTouchStart) return;
var distanceY = (hasTouch ? event.changedTouches[0].pageY : event.pageY) - startY;
var distanceX = (hasTouch ? event.changedTouches[0].pageX : event.pageX) - startX;
//如果X方向上的位移大于Y方向,則認(rèn)為是左右滑動(dòng)
if (Math.abs(distanceX) > Math.abs(distanceY))return;
if (distanceY > 0) {
self.setState({
translate: Math.pow((hasTouch ? event.changedTouches[0].pageY : event.pageY) - startY, 0.85)
});
} else {
if (self.state.translate !== 0) {
self.setState({translate: 0});
self.transformScroller(0, self.state.translate);
}
}
if (distanceY > 0) {
if (!isDragStart) {
isDragStart = true;
}
if (self.state.translate <= dragValve) {// 下拉中,但還沒到刷新閥值
if (dropDownRefreshText !== XLJZ)
self.refs.dropDownRefreshText.innerHTML = (dropDownRefreshText = XLJZ);
} else { // 下拉中,已經(jīng)達(dá)到刷新閥值
if (dropDownRefreshText !== SKJZ)
self.refs.dropDownRefreshText.innerHTML = (dropDownRefreshText = SKJZ);
}
self.transformScroller(0, self.state.translate);
}
}
function touchEnd(event) {
isDragStart = false;
if (!isTouchStart) return;
isTouchStart = false;
if (self.state.translate <= dragValve) {
self.transformScroller(0.3, 0);
} else {
self.setState({dragLoading: true});//設(shè)置在下拉刷新狀態(tài)中
self.transformScroller(0.1, dragValve);
self.refs.dropDownRefreshText.innerHTML = (dropDownRefreshText = JZ);
self.props.onRefresh();//觸發(fā)沖外面?zhèn)鬟M(jìn)來的刷新回調(diào)函數(shù)
}
}
},
initScroll: function () {
var self = this;
// 監(jiān)聽滾動(dòng)加載
if (this.state.openScrollLoading) {
this.refs.scroller.addEventListener('scroll', scrolling, false);
}
function scrolling() {
if (self.state.scrollerLoading) return;
var scrollerscrollHeight = self.refs.scroller.scrollHeight; // 容器滾動(dòng)總高度
var scrollerHeight = self.refs.scroller.getBoundingClientRect().height;// 容器滾動(dòng)可見高度
var scrollerTop = self.refs.scroller.scrollTop;//滾過的高度
// 達(dá)到滾動(dòng)加載閥值
if (scrollerscrollHeight - scrollerHeight - scrollerTop <= scrollValve) {
self.setState({scrollerLoading: true});
self.props.onLoadMore();
}
}
},
/**
* 利用 transition 和transform 改變位移
* @param time 時(shí)間
* @param translate 距離
*/
transformScroller: function (time, translate) {
this.setState({translate: translate});
var elStyle = this.refs.scroller.style;
elStyle.webkitTransition = elStyle.MozTransition = elStyle.transition = 'all ' + time + 's ease-in-out';
elStyle.webkitTransform = elStyle.MozTransform = elStyle.transform = 'translate3d(0, ' + translate + 'px, 0)';
},
/**
* 下拉刷新完畢
*/
dragLoadingDone: function () {
this.setState({dragLoading: false});
this.transformScroller(0.1, 0);
},
/**
* 滾動(dòng)加載完畢
*/
scrollLoadingDone: function () {
this.setState({scrollerLoading: false});
this.refs.dropDownRefreshText.innerHTML = (dropDownRefreshText = XLJZ);
},
/**
* 當(dāng)有新的屬性需要更新時(shí)。也就是網(wǎng)絡(luò)數(shù)據(jù)回來之后
* @param nextProps
*/
componentWillReceiveProps: function (nextProps) {
var self = this;
this.setState({data: nextProps.article,});//把新的數(shù)據(jù)填進(jìn)列表
if (this.state.dragLoading) {//如果之前是下拉刷新狀態(tài),恢復(fù)
setTimeout(function () {
self.dragLoadingDone();
}, 1000);
}
if (this.state.scrollerLoading) {//如果之前是滾動(dòng)加載狀態(tài),恢復(fù)
setTimeout(function () {
self.scrollLoadingDone();
}, 1000);
}
},
render: function () {
var self = this;
return (
<div className="scroller" ref="scroller">
<div className="drop-down-refresh-layer">
<p className="drop-down-refresh-text" ref="dropDownRefreshText">下拉加載</p>
</div>
<div className="scroller-content">
<div className="content">
<ul className="left_four_ul" id="list">
{self.state.data.map(function (item) {//通過map循環(huán)把子列表數(shù)據(jù)展示出來
return self.props.getItem(item);
})
}
</ul>
</div>
<div className="scroll-loading">加載中...</div>
</div>
</div>
);
}
});
列表組建下拉刷新解析:
1、通過refs找到滾動(dòng)的容器scroller,給它添加監(jiān)聽事件,為了兼容電腦端和移動(dòng)端,需要監(jiān)聽觸摸事件和鼠標(biāo)事件;
2、當(dāng)觸摸開始或鼠標(biāo)按下時(shí),回調(diào)touchstart函數(shù),判斷是否滾動(dòng)到容器頂端,如果滾動(dòng)到頂端,再判斷是否是手機(jī)觸摸事件,是就記錄第一個(gè)觸摸點(diǎn)的X,Y值,不是就記錄電腦鼠標(biāo)按下的位置;
3、當(dāng)觸摸移動(dòng)或鼠標(biāo)移動(dòng)時(shí),回調(diào)touchMove函數(shù),判斷是否是觸摸狀態(tài),同時(shí)記錄下觸摸移動(dòng)的距離(如果X方向上的位移大于Y方向,則認(rèn)為是左右滑動(dòng)并返回):
- 判斷Y方向的位移是否大于0,如果大于0,滾動(dòng)容器的位移取觸摸位移的0.85次方,當(dāng)觸摸位移過大時(shí),容器的位移到達(dá)一定值就不再跟隨;
- 當(dāng)Y方向的位移大于0時(shí),確定開始拖拽;滾動(dòng)容器在下拉中,但還沒到刷新閥值時(shí),顯示“下拉加載”;已經(jīng)達(dá)到刷新閥值,顯示“松開加載”;
4、當(dāng)觸摸結(jié)束或鼠標(biāo)抬起時(shí),回調(diào)touchEnd函數(shù)。若滾動(dòng)容器在下拉中,但還沒到刷新閥值,經(jīng)過0.3S位移回到0;若已經(jīng)達(dá)到刷新閥值,經(jīng)過0.1s位移為刷新閥值,顯示“加載”,并觸發(fā)沖外面?zhèn)鬟M(jìn)來的刷新回調(diào)函數(shù);
列表組建加載更多解析:
1、監(jiān)聽滾動(dòng)加載:當(dāng)滾動(dòng)容器滾動(dòng)時(shí),回調(diào)滾動(dòng)加載函數(shù);
2、如果是滾動(dòng)加載狀態(tài)則返回;
3、當(dāng)容器滾動(dòng)總高度- 容器滾動(dòng)可見高度-滾過的高度小于滾動(dòng)加載閥值時(shí),設(shè)置滾動(dòng)加載狀態(tài),觸發(fā)從外面?zhèn)鬟M(jìn)來的加載更多回調(diào)函數(shù)。
列表下拉跟隨解析:
transformScroller(time, translate)傳入兩個(gè)參數(shù):時(shí)間和距離;
利用 transition 和transform 改變位移,transition 屬性設(shè)置 'all ' + time + 's ease-in-out'表示過渡階段慢快慢;
transform 屬性設(shè)置'translate3d(0, ' + translate + 'px, 0)'位移過程更流暢;
當(dāng)有新的屬性需要更新時(shí),也就是網(wǎng)絡(luò)數(shù)據(jù)回來之后,把新的數(shù)據(jù)填進(jìn)列表;如果之前是下拉刷新狀態(tài),恢復(fù);如果之前是滾動(dòng)加載狀態(tài),恢復(fù)。
最后渲染列表組建,通過map循環(huán)把子列表數(shù)據(jù)展示出來。
效果圖如下:



