年少还是不要遇见太惊艳的人了,误终生。

前言

各位看官,注意了。

  • 这是篇技术文
  • 小说手法只是陪衬,最重要的还是技术。目的是让技术能更深入人心。
  • 这篇文原本的标题是(萌新入门之 —— 无痕上拉刷新组件(面试篇)

没办法,掘金都把作者逼成啥样了。为了能被更多人看到,无chi之级。

0. 我告诉他要做到 “ 没有痕迹

哎呀,我真的脸红了。因为今晚的米其林相亲会,我穿了件特别简单的粉色纱裙,emmm,我还画了点淡妆,5分钟,就5分钟结束了化妆的战斗。

哎。咱不能瞧不起相亲,更不能有抵触心理。万一我撞大运了不是?万一真的就是Mr.Right?

相亲,从见第一面就开始交锋了。 这人看起来真的年轻,是什么职业?

“小姐姐,堤拉米苏。”

“小姐姐,甜脆泡芙。”

“小姐姐,原浆椰汁。”

我裂开了。一口一个小姐姐管什么用,我饿得要死,晚餐是打算靠点心来约会吗?米其林餐厅什么最贵?这些分量少得可怜的点心好好了解一下?我心里在滴血,我狠狠拿着叉子戳我的泡芙。

我愤怒了。这要是AA,我得好好亏一顿钱包。他的手机来电话了,直接个按掉了。

“妹纸电话?”

“啊?不,不是。”

“手机给我看下。”

“啊!这…… 这不合适吧?”

“我漂亮嚒?”

“额。。 挺漂亮的。”

“我可爱嚒?”

“可……可爱。”

有件事情你要做到无痕,懂吗?

“什么?你说的什么意思?”

“晚上你睡觉嚒?”

“啊……”

“啊什么啊,你丫快把手机给我看看。”

一顿鸡飞狗跳。我拿着他手机,打开天猫App,精选首页。

这个无痕浏览能做嚒?”

我看着眼前已经满脸懵逼的“难”孩纸,我一脸笑。我前几天面试,面试官比较残忍,问了我这样一个问题。

1. 我们来梳理一下什么叫无痕浏览

那天,面试官问我如何做一个能无限滚动,不仅要流畅还要让用户感觉不到内容延迟加载的组件。Oh my god, 面试官大大,你就不能说简单点。例如:如何写一个高性能的上拉加载下拉刷新的List组件?

可是,我不会,我嘴瓢了。写个List我会,写个高性能的我也可以,但什么叫用户感觉不到内容延迟加载?听起来也不像是懒加载啊!我懵了,面试官看我可怜长得可爱,眼神无辜。因为我闺蜜们都这么说。?),又换了个方式问我。

Q: 加入页面要渲染10000条数据,而接口每次返回数据量有限,最多不超过30条。你咋办?

噢,我好像有点明白了。说白了就是用户看不到 加载更多加载中 呗,然后就无限往下滑呗。

说得容易,也想明白了,但还是不会……

“喂,喂,小姐姐,你喜欢吃甜脆泡芙,正好天猫有卖呢。”

气氛开始尴尬。我的思绪也被拉了回来,相亲的时候开小差。没事,颜值即正义,可爱即正义,脸皮厚无敌。

“小哥哥,我看你年纪也不大,只要你告诉我如何做到无痕,我就答应你一个条件。怎样,前端小哥哥?”

唉。这年头,为了工作,到处出卖尊严。

你听,某前端小弟弟心碎的声音。

2. 瓶颈在哪

  • 用户手势交互,因为用户可以无脑快速往下滑
  • 可视区域内容更换频繁,不能使用骨架屏,至少不能用普通的骨架屏。从骨架屏到页面真实内容,只能存在首次加载。
  • 接口请求问题。假如每次请求时间为300ms,那要拿完 10000 条数据的时间位 10000/30 * 300 = 100000ms。 所以,如果 100000ms 的时间必须拆分开,拆成足够细。 我就不信用户这能滑倒第 10000 条数据。
  • DOM渲染瓶颈。随着DOM数量越来越多,切记不要渲染重复或者不变的数据,不然一定会越来越卡顿。

先上一段伪代码:



import React, { Fragment, useRef, useState,useEffect } from 'react';
import styles from './style.module.less';


let scrollTop = -1;
let touchX = 0;
let touchY = 0;
let time = 0;
let type = '';
let pullDownDoneBacking = false; // 下拉刷新完成后,正在回弹至初始位置的状态

let init = true;
export default ((props) => {
    const [translateY, setTranslateY] = useState(0);
    const [pullUpHide, setPullUpHide] = useState(0);
    const [pullUpStatus, setPullUpStatus] = useState(0);
    const [pullDownStatus, setPullDownStatus] = useState(0);
    const wrapperEl = useRef(null);
    const innerEl = useRef(null);



    useEffect(() => {
        if (init) {
            //解决ios和安卓的页面自带滚动回弹
            wrapperEl.current.addEventListener('touchmove', touchMoveHandle, { passive: false });
            init = false;
        }

        //内容发生变化时重新计算 pullUpHide
        const pullUpHide = innerEl.clientHeight < wrapperEl.current.clientHeight;
        setPullUpHide(pullUpHide);
    })


    const scrollHandle = (e) => {


    };

    const touchStartHandle = (e) => {

    };

    const touchMoveHandle = (e) => {

    };

    const touchEndHandle = (e) => {

    };

    /*
    * 可在父组件中通过ref调用该方法,传入自定义异步函数组件会根据异步函数的状态去处理下拉的状态,自动下拉刷新
    * */
    //下拉刷新
    const pullDownRefresh = async (customAsyncFn) => {
    };

    //上拉加载
    const pullUpLoad = async () => {

    };



    const { pullDownRefresh, pullUpLoad, children, noMore, backTop, noMoreTip = '我到底了' } = props;
    return (
         <Fragment>
            <div className={styles.wrapper}
                ref={wrapperEl}
                onScroll={scrollHandle}
                onTouchStart={touchStartHandle}
                onTouchEnd={touchEndHandle}
            >
                <div
                    style={{
                        transform: `translateY(${translateY}px)`,
                        transition: 'transform .3s ease'
                    }}
                >
                    {
                       
                        <div className={styles.pullDownTip}>
                             {pullDownStatus === 1 && '正在刷新'}
                             {pullDownStatus === 2 && '刷新成功'}
                             {pullDownStatus === 3 && '刷新失败'}
                        </div>
                    }
                    <div ref={innerEl}>
                        <Items />
                    </div>
                    <div>我真的到底了</div>
                </div>
            </div>

        </Fragment>
    )
}
)
复制代码

我们先来考虑下组件的模式

  • 下拉刷新,动画效果还是要的。

image.png

image.png

image.png

image.png

(留下了不会制作gif图的泪水????)

  • 核心,Items
    // 我们要做到重复数据的不要重复render
    const Item = React.memo((info)=>{
         return <div>
             {info.price}
             ....
         </div>
    });
    const Items = ((list)=>{
        return list.map((item,index)=> <Item info={item}/>)
    })
    
复制代码
  • 下拉刷新不要更改list长度。只修改第一页或者前1-3页的数据。
const pullDownRefresh = async () => {
    // 之更新前1-3页的数据,不删除任何DOM
    const freshData = await API();
    list[0] = freshData[0];
    list[1] = freshData[1];
    list[2] = freshData[2];
    setList(list);
}
复制代码
  • API返回第几页的数据,就覆盖数组对应index的数据。

  • 调用接口API获取数据交于浏览器空闲时间去做,绝对不能阻塞用户操作。 requestIdleCallback


requestIdleCallback(myWork); // 如window.requestIdleCallback is undefined ,可以使用setTimeout模拟。

// 一个任务队列
let tasks = [
  function task1() {
    // 获取第1页数据
    API.fetchData(1);
    console.log('调用接口API1')
  },
  function task2() {
   // 获取第2页数据
    API.fetchData(2);
    console.log('调用接口API2')
  },
  function task3() {
   // 获取第3页数据
    API.fetchData(3);
    console.log('调用接口API3')
  },
  ...,
  function taskN(){
   // 获取第N页数据
    API.fetchData(N);
    console.log('调用接口API(N)')
  }
]

// deadline是requestIdleCallback返回的一个对象
function myWork(deadline) {
  console.log(`当前帧剩余时间: ${deadline.timeRemaining()}`)
  // 查看当前帧的剩余时间是否大于0 && 是否还有剩余任务
  if (deadline.timeRemaining() > 0 && tasks.length) {
    // 在这里做一些事情
    const task = tasks.shift()
    task()
  }
  // 如果还有任务没有被执行,那就放到下一帧调度中去继续执行,类似递归
  if (tasks.length) {
    requestIdleCallback(myWork)
  }
}

复制代码

所以,我们是不是只需要在把我们的 task 任务栈你交给 requestIdleCallback 来处理?

  • 最后,优化你的渲染组件,图片记得做缓存。

END

“小姐姐,你是如何知道我是一枚前端的?”

“我不知道啊!”

“那你怎么问我这样的问题?”

“回答不上来就可以愉快的结束用餐了啊!”

气氛再度陷入极度尴尬中。

“噢,对了。谁知道你还真是前端,然后还真的辣么菜。”

“那…… 我们还能留个微信吗?”

“饭钱多少,AA吧。”

“小姐姐,一顿饭我还是请得起的……”

我用眼神挪了挪账单,3780.

所以哇,年少的时候容易爱一个人,但不要爱太满。如果你是男生,也请不要和我这样的“老人”谈恋爱。你问为啥?那我就矫情的回答一句。

年少还是不要遇见太惊艳的人了,误终生。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享