React組建實(shí)現(xiàn)新聞下拉刷新加載

新聞列表格式

整體布局:

<!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ù)展示出來。
效果圖如下:


下拉加載

松開加載

刷新加載中

上滑加載更多
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,063評論 25 709
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,376評論 4 61
  • 內(nèi)容抽屜菜單ListViewWebViewSwitchButton按鈕點(diǎn)贊按鈕進(jìn)度條TabLayout圖標(biāo)下拉刷新...
    皇小弟閱讀 47,162評論 22 665
  • 1、當(dāng)你很想做一件事情的時(shí)候,全世界都會(huì)幫你#### 在我剛進(jìn)入媒體行業(yè)的時(shí)候,主編問我,你會(huì)做采訪版面嗎?我說,...
    行動(dòng)派琦琦閱讀 1,305評論 4 22
  • 莫不是你在江南早已游人滿園又遇好雨時(shí)節(jié)點(diǎn)綴了風(fēng)花兩岸 自古才子在江南春來愛詠菩薩蠻 只可惜我在草原北風(fēng)不似剪刀川河...
    千石硯閱讀 360評論 0 4

友情鏈接更多精彩內(nèi)容