问题一、 react组件优化
思路:
- 组件什么时候会更新?两种情况,1是组件的props,state或者context发生变化时会更新。注意,useRef是不会触发组件更新的。2是父组件的更新会触发内部子组件的更新
- 有了上面的内容,就可以知道优化重点是防止重复更新。一般使用三件套,React.memo,useMemo,useCallback,缓存组件,缓存值和缓存函数,三个需要搭配使用。
- React,memo,第一个参数是组件,第二个参数是自定义比较函数,入参是旧props和新props,返回true表示不需要更新,返回false表示需要更新。
- react18中提供了useDeferredValue,它接受一个值,并返回一个延迟更新的值,可以用来延迟更新UI的某些部分
- react18中提供了useTransition,它提供了startTransition,它接收一个必须是同步的函数,该函数内部会调用useState的set函数,表明该值的更新不会阻塞UI的渲染。不要在受控组件中使用,会使用户输入的内容延迟更新
问题二、一个父组件中有一个输入框和子组件,子组件接收一个函数,该函数内部会使用输入框的值,问该如何优化。
思路:
有了上面的内容就知道,子组件使用React.memo,传递给子组件的函数必须不能变,如果该函数只是用useCallback处理的话,由于其内容使用了输入框的值,得将这个值加入到依赖项中。这样的话,每次输入值改变都会创造出一个新的函数,并没有优化的效果。有两种解决方法
- 该函数内部不直接使用输入框的值,而是使用useRef创造出来的副本,这样就不需要把它写进依赖项中了
- 像ahooks中的useMemoizedFn一样,传递给子组件的函数外面包一个壳,使这个函数的地址永远不会发生改变,这样就避免更新子组件,详情可以看juejin.cn/post/709368…
问题三、有一个网页需要用到一个很大的图片,如何优化
思路
-
在图片不失真的情况下进行压缩,图片链接使用CDN
-
将大图片拆分成小图片
-
如果图片不是一开始就要展示,而且知道图片地址的话,可以使用图片预加载,需要展示时直接取缓存。new Image和link标签的prefetch属性可以实现,详情见
juejin.cn/post/691520…
juejin.cn/post/740627… -
图片的懒加载,先用占位元素放置空的图片内容,加上data-src属性,当图片出现在视窗内的时候再用data-src的值,代替image的src属性。检测出现在视窗可以使用IntersectionObserve,详见juejin.cn/post/727079…
-
img标签有
fetchpriority属性,可以设置图片加载优先级,以及loading属性可以设置图片懒加载,同时jpg格式图片可以在ps导出时设置为渐进式和标准式加载,详见www.jb51.net/photoshop/1…
问题四、首屏加载优化
思路:1是减少请求数量,2是减少资源体积,可以使用路由懒加载,Suspense组件,React.lazy。使用浏览器缓存,开启http的gzip,webpack做代码分割和Treeshaking等,详见juejin.cn/collection/…
问题五、一个类似于百度搜索的,上面输入框,下面展示搜索结果列表,你能想到要注意哪些地方
思路:
- 用户输入时防抖
- 避免阻塞用户输入,可以看上面react组件优化
- 注意请求时序,第二次请求的结果比第三次请求的结果晚回来。可以中断前一次请求,在原生的 XHR 对象里方法为:XMLHttpRequest.abort()。在 axios 里有 cancelToken 的 API 提供完成。详见 juejin.cn/post/688552…
- 可以使用问题六的解法,封装时让promise的结果永远都指向最后一次请求
问题六、写出下面的题
function task() { /*** 要求实现一个takeLatest函数* 能够合并多次同一个请求,只取最后一次请求的返回值*/ function takeLatest(fn) { let latest = null; let prevReject = null; return function (...args) { if (prevReject) prevReject() latest = fn(...args); return new Promise((resolve, reject) => { prevReject = reject; latest.then((v) => { resolve(v) prevReject = null; latest = null }).catch(() => { reject() }) }) // return latest } } const request = (v) => new Promise(resolve => { setTimeout(() => { resolve(v) }, 10) }) const fn = takeLatest(request); console.log(Date.now()); fn(1).then((v) => { console.log("1--->", v); }); fn(1).then((v) => { console.log("2--->", v); }); fn(1).then((v) => { console.log("3--->", v); console.log(Date.now()); }); setTimeout(() => { fn(1).then((v) => { console.log("4--->", v); console.log(Date.now()); }); }, 2000); //1秒后 输出 3---> //3秒后 输出 4---> // fn(1).then((v) => { // console.log("3--->", v); // console.log(Date.now()); // }); // setTimeout(() => { // fn(1).then((v) => { // console.log("4--->", v); // console.log(Date.now()); // }); // }, 200); //1200毫秒后输出 4---> } task()
思路:利用闭包,返回的promise的在最近的一个请求成功时会将状态变为fulfilled。同时将reject函数保存起来,当发起新的请求时存在reject函数,就执行,把前一次请求的promise置为rejected。
问题七、script标签相关
思路:在现代前端框架下,已经很少需要考虑引入script标签,大部分都是npm包加import方式引入,webpack等打包工具会处理script标签引入。script标签一般要放在标签前,放在head内的要加上defer和async属性。两者都是让js下载和解析html并行,区别是defer会按顺序执行,且在DomConentLoaded事件之前执行。而async会在下载成功后立即执行,不保证执行顺序。如果脚本应该立刻运行且没有任何依赖,那么应使用 async。如果脚本需要等待页面解析,且依赖于其他脚本或 DOM,请使用 defer 加载脚本,并将关联的脚本按你想要浏览器加载它们的顺序置于相应 <script> 元素中。