React组建实现新闻下拉刷新加载

新闻列表格式

整体布局:

<!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循环把子列表数据展示出来。
效果图如下:


下拉加载

松开加载

刷新加载中

上滑加载更多
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容