简单聊一聊 window resizing 和 reduce
hacker0limbo opened this issue · 0 comments
水文一篇...
其实这里要讲两个事情, 但是由于这两个东西可以写的东西都不多, 所以就用一篇文章了. 另外这两个东西几乎没有任何关联性
window resizing
业务上碰到的一个需求, 我需要监听 window
在什么时候 resizing
结束, 在结束的那一刻我可以做一个标记, 比如最简单的进行一次 setState
错误思路
一开始没想太多, 直接写. 写到一半发现不对了. 代码可能长这样:
import React, { useState, useEffect } from "react";
export default function WindowResize() {
const [windowResizing, setWindowResizing] = useState(false);
const handleWindowResize = e => {
setWindowResizing(true);
};
useEffect(() => {
window.addEventListener("resize", handleWindowResize);
return () => window.removeEventListener("resize", handleWindowResize);
}, []);
return <div>{JSON.stringify({ windowResizing })}</div>;
}
很明显, 最后的结果就是一旦开始 resize
, windowResizing
就会一直保持 true
, 即使我已经停止了 resize
. 原因也很简单, resize
这个事件只会去监听 change
时候的变化, 至于什么时候 stop
, 他没有暴露任何 API, 他只知道变了, 但不知道这个变了啥时候停止
但我恰恰就需要知道啥时候停止
防抖
先考虑另外一种场景, 我需要监听 resize
时候 window
的 width
变化, 但我不需要在 resize
发生每一帧都去监并显示变化, 这是一个高频率触发的事件. 比如 resize
到一半的时候, 停顿了一下, 这个频率是比正常 resize
频率低一些的, 那我在这个时候是可以去获得或者说监听到 width
变化.
实际上说了这么多就是要对 resize
事件做一个防抖(debounce
)...
import React, { useState, useEffect } from "react";
const WindowWidth = () => {
const [windowWidth, setWindowWidth] = useState(window.innerWidth);
useEffect(() => {
let timerId;
const handleResize = () => {
clearTimeout(timerId);
timerId = setTimeout(() => {
setWindowWidth(window.innerWidth);
}, 200);
};
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
return <div>{JSON.stringify({ windowWidth }, null, 2)}</div>;
};
export default WindowWidth;
理解起来也不难, 这里我给了 200ms 的限制, 也就是说低于 200ms 速率的 resize
操作, handleResize
这个回调函数里的 setWindowWidth
操作还没等到 200ms, 就被 clearTimeout
掉了, 然后 timerId
又存了最新的一次操作的 id
, 只要 resize
速率是低于 200ms 的, 那么一定是出现上一次操作还没来得及执行就被 clear
掉, timerId
被(下一次)最新一次的操作给覆盖. 所以 windowWidth
永远还是最初的, setState
也就不会被高频触发
直到某次 resize
操作后有大于 200ms 的停顿, 这个时候 setWindowWidth
等到了时间, 可以执行一次 setState
, 虽然再开始 resize
的时候 timerId
还是被 clear
掉, 但是 setWindowWidth
已经执行过了, 因此不影响.
总结就是上一次 resize
和 下一次 resize
之间间隔在 200ms 以内, 限制对应操作, 否则允许操作函数执行
解决方案
扯了很多防抖, 其实去监听 window
resize
什么时候停止, 也是类似的方案
首先要明确, 什么时候才算 resize
停止, 我可以认为没有 resize
事件后的 10s 算停止(有点长), 我也可以认为是 0.000001ms 之后算停止(太短了), 所以我还是用 200ms 作为界限, 上一次 resize
和下一次 resize
之间间隔在 200ms 以内的, 都算还在 resizing
, 超过 200ms 的, 记为 resize
停止了.
代码如下:
import React, { useState, useEffect } from "react";
const WindowResize = () => {
const [windowResizing, setWindowResizing] = useState(false);
useEffect(() => {
let timerId;
const handleResize = () => {
clearTimeout(timerId);
setWindowResizing(true);
timerId = setTimeout(() => {
setWindowResizing(false);
}, 200);
};
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
return <div>{JSON.stringify({ windowResizing }, null, 2)}</div>;
};
export default WindowResize;
从代码上来看, windowResizing
这个状态是一直在 true
和 false
之间切换, 但 false
状态是有延迟的. 所以可以模拟出 resize
停止的那个状态
reduce
这个函数也用的很多了, 面试也经常考让手写一个简易的.
这里想记录的是做项目时遇到的一个 immutable 或者 mutable 的写法问题, 由于真实项目的数据还要复杂和繁琐, 这里只做模拟实现
目标
[['abcde', 0.9], ['fghij', 0.8], ...]
用 reduce
将上面的二位数组转成下面这种格式
{ abcde: 0.9, fghij: 0.8, ... }
二位数组里的数据至少有 3000 条
两种做法
直接看代码:
import React, { useState } from "react";
const arr = Array.from(Array(3000)).map(v => [
Math.random()
.toString(36)
.substring(7),
Math.random()
]);
export default function App() {
const [data1, setData1] = useState("immutable");
const [data2, setData2] = useState("mutable");
const handleR1 = () => {
console.time("immutable in reduce");
const r1 = arr.reduce(
(r, v) => ({
...r,
[v[0]]: v[1]
}),
{}
);
console.timeEnd("immutable in reduce");
setData1("r1 finish");
};
const handleR2 = () => {
console.time("mutable in reduce");
const r2 = arr.reduce((r, v) => {
r[v[0]] = v[1];
return r;
}, {});
console.timeEnd("mutable in reduce");
setData2("r2 finish");
};
return (
<div>
<div>
<button onClick={handleR1}>{data1}</button>
</div>
<div>
<button onClick={handleR2}>{data2}</button>
</div>
</div>
);
}
handleR1
和 handleR2
分别是两种写法, 结果都是一样的, 对应 immutable 和 mutable. 我个人是倾向第一种. 但是第一种有一些性能问题
这里我用 console.time
和 console.timeEnd
分别记录两种操作所需要的时间, 第一种 immutable 写法的时间大致为 2000ms, 第二种 mutable 写法时间大致为 2ms, 2000 倍的差距...
点击 immutable 的按钮花了很久才显示结束, 卡顿非常明显, 而 mutable 的按钮秒结束...
所以 immutable 的写法有时候可能不一定那么好?