美文网首页
React组建实现新闻下拉刷新加载

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

作者: 朝槿123 | 来源:发表于2017-05-07 10:40 被阅读0次
    新闻列表格式

    整体布局:

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


    下拉加载
    松开加载
    刷新加载中
    上滑加载更多

    相关文章

      网友评论

          本文标题:React组建实现新闻下拉刷新加载

          本文链接:https://www.haomeiwen.com/subject/wviozttx.html