Skip to content

打造巨好用的Scroll 组件 ​

本节目标:理解“打造巨好用的Scroll 组件”的核心思路,并能把它用于实际开发或面试表达。 ==本项目最大的亮点之一就是采用== better-scroll ==打造了移动端滑动基础组件,不仅仅可以用在本项目,也可以直接移植到其他所有的移动端== React ==项目。现在,我们来一起封装这个实用且强大的组件。== 分步拆解 scroll 组件// ==安装==

 better-scrollnpm install better-scroll@next --save

==我们依然采用函数式组件的形式进行开发,不过作为一个通用组件,==scroll ==组件在业务中会被经常取到原生== DOM ==对象,而函数式组件天生不具备被上层组件直接调用== ref ==的条件,因此需要用== React ==当中一些特殊的方式来处理,即使用== forwardRef ==进行包裹。==

const Scroll = forwardRef ((props, ref) => {  //

==编写组件内容==

})

==首先梳理一下这个组件需要接受哪些参数==

:
Scroll.propTypes = {  direction: PropTypes.oneOf (['vertical', 'horizental']),//

==滚动的方向==

  click: true,//

==是否支持点击==

  refresh: PropTypes.bool,//

==是否刷新==

  onScroll: PropTypes.func,//

==滑动触发的回调函数==

  pullUp: PropTypes.func,//

==上拉加载逻辑==

  pullDown: PropTypes.func,//

==下拉加载逻辑==

  pullUpLoading: PropTypes.bool,//

==是否显示上拉== loading ==动画==

  pullDownLoading: PropTypes.bool,//

==是否显示下拉== loading ==动画==

  bounceTop: PropTypes.bool,//

==是否支持向上吸顶==

  bounceBottom: PropTypes.bool//

==是否支持向下吸底==

};

==目前归纳出了这些可能的参数,也正是后面的开发中所需要的,给他们赋默认值==

:
Scroll.defaultProps = {  direction: "vertical",  click: true,  refresh: true,  onScroll:null,  pullUpLoading: false,  pullDownLoading: false,  pullUp: null,  pullDown: null,  bounceTop: true,  bounceBottom: true};

==现在来写== scroll ==组件的核心逻辑代码,首先声明如下== hooks ==变量==

:
//better-scroll

==实例对象==

const [bScroll, setBScroll] = useState ();//current

==指向初始化== bs ==实例需要的== DOM ==元素==

 const scrollContaninerRef = useRef ();

==从外面接受== props==,解构赋值拿到这些参数==

:
const { direction, click, refresh, pullUpLoading, pullDownLoading, bounceTop, bounceBottom } = props;const { pullUp, pullDown, onScroll } = props;

==接下来创建== better-scroll==,==

useEffect (() => {  const scroll = new BScroll (scrollContaninerRef.current, {    scrollX: direction === "horizental",    scrollY: direction === "vertical",    probeType: 3,    click: click,    bounce:{      top: bounceTop,      bottom: bounceBottom    }  });  setBScroll (scroll);  return () => {    setBScroll (null);  }}, []);

==每次重新渲染都要刷新实例,防止无法滑动==

:
useEffect (() => {  if (refresh && bScroll){    bScroll.refresh ();  }});

==给实例绑定== scroll ==事件,==

useEffect (() => {  if (!bScroll || !onScroll) return;  bScroll.on ('scroll', (scroll) => {    onScroll (scroll);  })  return () => {    bScroll.off ('scroll');  }}, [onScroll, bScroll]);

==进行上拉到底的判断,调用上拉刷新的函数==

useEffect (() => {  if (!bScroll || !pullUp) return;  bScroll.on ('scrollEnd', () => {    //

==判断是否滑动到了底部==

    if (bScroll.y <= bScroll.maxScrollY + 100){      pullUp ();    }  });  return () => {    bScroll.off ('scrollEnd');  }}, [pullUp, bScroll]);

==进行下拉的判断,调用下拉刷新的函数==

useEffect (() => {  if (!bScroll || !pullDown) return;  bScroll.on ('touchEnd', (pos) => {    //

==判断用户的下拉动作==

    if (pos.y > 50) {      pullDown ();    }  });  return () => {    bScroll.off ('touchEnd');  }}, [pullDown, bScroll]);

==完成了滑动事件、上拉下拉事件的判断,现在需要给外界暴露组件方法,如==

:
//

==上层组件代码==

const scrollRef = useRef ();...<Scroll ref={scrollRef}></Scroll>

==想要通过这种调用方法的方式刷新== scroll ==组件:== scrollRef.current.refresh (); ==这应该怎么办呢?== React Hooks ==中的== useImperativeHandle ==已经给了我们解决方案,我们这样做就好了==

:
//

==一般和== forwardRef ==一起使用,==ref ==已经在== forWardRef ==中默认传入==


    useImperativeHandle(ref, () => ({
        refresh() {
            if (bScroll) {
                bScroll.refresh();
                bScroll.scrollTo(0, 0);
            }
        },
        getBScroll() {
            if (bScroll) {
                return bScroll;
            }
        }
    }));

==剩下的是== UI ==的渲染工作==

:
return (  <ScrollContainer ref={scrollContaninerRef}>    {props.children}  </ScrollContainer>);

==同时贴出样式部分的== js ==代码==

:
const ScrollContainer = styled.div`  width: 100%;  height: 100%;  overflow: hidden;`

==加载动画部分涉及到== loading ==组件我们放到下一章拆解:)== 综合代码 ==这里给出综合后的代码==

:
import React, { forwardRef, useState, useEffect, useRef, useImperativeHandle } from "react"
import PropTypes from "prop-types"
import BScroll from "better-scroll"
import styled from 'styled-components';
const ScrollContainer = styled.div`
  width: 100%;
  height: 100%;
  overflow: hidden;
`
const Scroll = forwardRef((props, ref) => {
    const [bScroll, setBScroll] = useState();
    const scrollContaninerRef = useRef();
    const { direction, click, refresh, bounceTop, bounceBottom } = props;
    const { pullUp, pullDown, onScroll } = props;
    useEffect(() => {
        const scroll = new BScroll(scrollContaninerRef.current, {
            scrollX: direction === "horizental",
            scrollY: direction === "vertical",
            probeType: 3,
            click: click,
            bounce: {
                top: bounceTop,
                bottom: bounceBottom
            }
        });
        setBScroll(scroll);
        return () => {
            setBScroll(null);
        }
        //eslint-disable-next-line
    }, []);
    useEffect(() => {
        if (!bScroll || !onScroll) return;
        bScroll.on('scroll', (scroll) => {
            onScroll(scroll);
        })
        return () => {
            bScroll.off('scroll');
        }
    }, [onScroll, bScroll]);
    useEffect(() => {
        if (!bScroll || !pullUp) return;
        bScroll.on('scrollEnd', () => {
            //

判断是否滑动到了底部

            if (bScroll.y <= bScroll.maxScrollY + 100) {
                pullUp();
            }
        });
        return () => {
            bScroll.off('scrollEnd');
        }
    }, [pullUp, bScroll]);
    useEffect(() => {
        if (!bScroll || !pullDown) return;
        bScroll.on('touchEnd', (pos) => {
            //

判断用户的下拉动作

            if (pos.y > 50) {
                pullDown();
            }
        });
        return () => {
            bScroll.off('touchEnd');
        }
    }, [pullDown, bScroll]);
    useEffect(() => {
        if (refresh && bScroll) {
            bScroll.refresh();
        }
    });
    useImperativeHandle(ref, () => ({
        refresh() {
            if (bScroll) {
                bScroll.refresh();
                bScroll.scrollTo(0, 0);
            }
        },
        getBScroll() {
            if (bScroll) {
                return bScroll;
            }
        }
    }));
    return (
        <ScrollContainer ref={scrollContaninerRef}>
            {props.children}
        </ScrollContainer>
    );
})
Scroll.defaultProps = {
    direction: "vertical",
    click: true,
    refresh: true,
    onScroll: null,
    pullUpLoading: false,
    pullDownLoading: false,
    pullUp: null,
    pullDown: null,
    bounceTop: true,
    bounceBottom: true
};
Scroll.propTypes = {
    direction: PropTypes.oneOf(['vertical', 'horizental']),
    refresh: PropTypes.bool,
    onScroll: PropTypes.func,
    pullUp: PropTypes.func,
    pullDown: PropTypes.func,
    pullUpLoading: PropTypes.bool,
    pullDownLoading: PropTypes.bool,
    bounceTop: PropTypes.bool,//

是否支持向上吸顶

    bounceBottom: PropTypes.bool//

是否支持向上吸顶

};
export default Scroll;

在项目中应用scroll ==组件已经初步实现。但是,这还不够。还有一些细节,比如防抖,==loading ==控制等等,后期会一步一步完善。更重要的是,我们还需要将它运用到项目中,进入到== Recommend ==目录下的== index.js==,函数返回的== JSX ==代码变化如下:==

<Content>  <Scroll className="list">    <div>      <Slider bannerList={bannerList}></Slider>      <RecommendList recommendList={recommendList}></RecommendList>    </div>  </Scroll></Content>

==可能你会不解,==Content ==样式组件是个什么鬼?在这里我要强调一下,==better-scroll ==的原理并不复杂,就是在容器元素高度固定,当子元素高度超过容器元素高度时,通过== transfrom ==动画产生滑动效果,因此它的使用原则就是外部容器必须是固定高度,不然没法滚动。而== Content ==就是这个外部容器。== ==我们在对应== style.js ==中增加以下代码==

:
import styled from'styled-components';
export const Content = styled.div`  position: fixed;  top: 90px;  bottom: 0;  width: 100%;`

==现在打开页面,你就能体会到下拉吸顶、上拉吸底的感觉了。不过还是有一个问题,当你下拉的时候,中间会有一段空白,感觉比较突兀,没错,这就是默认的背景颜色。那么怎么来解决这个问题呢?== ==还是从遮罩入手吧,还记得那个==.before ==的== div ==吗?==

.before {  position: absolute;  top: -300px;  height: 400px;  width: 100%;  background: ${style ["theme-color"]};}

==如此修改即可,这样下拉间隙就变成了主题色了。== > 来自

 <https://juejin.im/book/5da96626e51d4524ba0fd237/section/5da974815188256ab73dd2f2>

本站总访问 — 次