Appearance
打造巨好用的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>