整体布局:
<!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基础库,dom库,jsx解析库和移动端Jquery库(用于动态请求异步加载数据),然后创建一个Div,引入自己的组建。
整个应用组件
var App = React.createClass({
getInitialState: function () {
return {
page: 1,
article: [],//文章列表
};
},
componentDidMount: function () {//组件被加载之后,默认加载第一页数据
this.getData(1);
},
onRefresh: function () {//下拉刷新函数
this.setState({page: 1});
this.getData(this.state.page);
},
onLoadMore: function () {//加载更多函数
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) {//获取数据的函数
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进行处理,并进行对应的dom渲染
if (page == 1) {//如果是第一页,直接覆盖之前的数据
self.setState({article: data.list})
//父组件的setState 改变的自己的状态的同时触发了自组件
的componentWillReceiveProps
// 子组件可以在componentWillReceiveProps里接受新的参
数,改变自己的state会自动触发render渲染
} else {
self.setState({//否则累加数组
article: self.state.article.concat(data.list)
})
}
},
error: function () {
// 4.错误处理
}
})
},
render: function () {
return (
<ListView
onRefresh={this.onRefresh} //从外面传进去的下拉刷新回调函数
onLoadMore={this.onLoadMore}//从外面传进去的加载更过回调函数
article={this.state.article}//从外面传进去的文章列表数据数组
getItem={this.getItem} //从外面传进去的获取列表子项的回调函数
/>
);
}
});
解析:
1、首先对于组建进行初始化状态设置,当组建被加载后,默认加载第一页数据;
2、当进行下拉刷新时,设置状态为第一页并获取第一页数据;
3、当上拉加载更多时,状态为下一页,并获取下一页的数据。
通过Ajax获取新闻数据,对Data进行相应的处理,并进行对应的dom渲染。
** 渲染整个app**
ReactDOM.render(
<App />,
document.getElementById('app')
);
** 静态常量**
var XLJZ = '下拉加载';
var SKJZ = '松开加载';
var JZ = '加载中...'
var dropDownRefreshText = XLJZ;
var dragValve = 40; // 下拉加载阀值
var scrollValve = 40; // 滚动加载阀值
子列表项组件,只负责渲染外面传递给他的数据(css设计样式)
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 () {//初始化状态
return {
translate: 0,//位移
dragLoading: false,//是否在下拉刷新中
scrollerLoading: false,//是否在加载更多中
openDragLoading: true,//是否开启下拉刷新
openScrollLoading: true,//是否开启下拉刷新
data: []//默认的列表空数据
};
},
componentDidMount: function () {//组建加载完毕,进行初始化赋值
this.setState(
{
translate: 0,
openDragLoading: this.props.openDragLoading || true,//根据外面设置的开关改变自己的状态
openScrollLoading: this.props.openScrollLoading || true
}
);
this.initRefresh();//初始化下拉刷新
this.initScroll();//初始化滚动加载更多
},
initRefresh: function (defaults, options) {
var self = this;//对象转存,防止闭包函数内无法访问
var isTouchStart = false; // 是否已经触发下拉条件
var isDragStart = false; // 是否已经开始下拉
var startX, startY; // 下拉方向,touchstart 时的点坐标
var hasTouch = 'ontouchstart' in window;//判断是否是在移动端手机上
// 监听下拉加载,兼容电脑端
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方向,则认为是左右滑动
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 { // 下拉中,已经达到刷新阀值
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});//设置在下拉刷新状态中
self.transformScroller(0.1, dragValve);
self.refs.dropDownRefreshText.innerHTML = (dropDownRefreshText = JZ);
self.props.onRefresh();//触发冲外面传进来的刷新回调函数
}
}
},
initScroll: function () {
var self = this;
// 监听滚动加载
if (this.state.openScrollLoading) {
this.refs.scroller.addEventListener('scroll', scrolling, false);
}
function scrolling() {
if (self.state.scrollerLoading) return;
var scrollerscrollHeight = self.refs.scroller.scrollHeight; // 容器滚动总高度
var scrollerHeight = self.refs.scroller.getBoundingClientRect().height;// 容器滚动可见高度
var scrollerTop = self.refs.scroller.scrollTop;//滚过的高度
// 达到滚动加载阀值
if (scrollerscrollHeight - scrollerHeight - scrollerTop <= scrollValve) {
self.setState({scrollerLoading: true});
self.props.onLoadMore();
}
}
},
/**
* 利用 transition 和transform 改变位移
* @param time 时间
* @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);
},
/**
* 滚动加载完毕
*/
scrollLoadingDone: function () {
this.setState({scrollerLoading: false});
this.refs.dropDownRefreshText.innerHTML = (dropDownRefreshText = XLJZ);
},
/**
* 当有新的属性需要更新时。也就是网络数据回来之后
* @param nextProps
*/
componentWillReceiveProps: function (nextProps) {
var self = this;
this.setState({data: nextProps.article,});//把新的数据填进列表
if (this.state.dragLoading) {//如果之前是下拉刷新状态,恢复
setTimeout(function () {
self.dragLoadingDone();
}, 1000);
}
if (this.state.scrollerLoading) {//如果之前是滚动加载状态,恢复
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循环把子列表数据展示出来
return self.props.getItem(item);
})
}
</ul>
</div>
<div className="scroll-loading">加载中...</div>
</div>
</div>
);
}
});
列表组建下拉刷新解析:
1、通过refs找到滚动的容器scroller,给它添加监听事件,为了兼容电脑端和移动端,需要监听触摸事件和鼠标事件;
2、当触摸开始或鼠标按下时,回调touchstart函数,判断是否滚动到容器顶端,如果滚动到顶端,再判断是否是手机触摸事件,是就记录第一个触摸点的X,Y值,不是就记录电脑鼠标按下的位置;
3、当触摸移动或鼠标移动时,回调touchMove函数,判断是否是触摸状态,同时记录下触摸移动的距离(如果X方向上的位移大于Y方向,则认为是左右滑动并返回):
- 判断Y方向的位移是否大于0,如果大于0,滚动容器的位移取触摸位移的0.85次方,当触摸位移过大时,容器的位移到达一定值就不再跟随;
- 当Y方向的位移大于0时,确定开始拖拽;滚动容器在下拉中,但还没到刷新阀值时,显示“下拉加载”;已经达到刷新阀值,显示“松开加载”;
4、当触摸结束或鼠标抬起时,回调touchEnd函数。若滚动容器在下拉中,但还没到刷新阀值,经过0.3S位移回到0;若已经达到刷新阀值,经过0.1s位移为刷新阀值,显示“加载”,并触发冲外面传进来的刷新回调函数;
列表组建加载更多解析:
1、监听滚动加载:当滚动容器滚动时,回调滚动加载函数;
2、如果是滚动加载状态则返回;
3、当容器滚动总高度- 容器滚动可见高度-滚过的高度小于滚动加载阀值时,设置滚动加载状态,触发从外面传进来的加载更多回调函数。
列表下拉跟随解析:
transformScroller(time, translate)传入两个参数:时间和距离;
利用 transition 和transform 改变位移,transition 属性设置 'all ' + time + 's ease-in-out'表示过渡阶段慢快慢;
transform 属性设置'translate3d(0, ' + translate + 'px, 0)'位移过程更流畅;
当有新的属性需要更新时,也就是网络数据回来之后,把新的数据填进列表;如果之前是下拉刷新状态,恢复;如果之前是滚动加载状态,恢复。
最后渲染列表组建,通过map循环把子列表数据展示出来。
效果图如下: