Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

简单聊一聊 window resizing 和 reduce #18

Open
hacker0limbo opened this issue Dec 23, 2020 · 0 comments
Open

简单聊一聊 window resizing 和 reduce #18

hacker0limbo opened this issue Dec 23, 2020 · 0 comments
Labels
javascript 原生 JavaScript 笔记整理 react react 笔记整理

Comments

@hacker0limbo
Copy link
Owner

水文一篇...

其实这里要讲两个事情, 但是由于这两个东西可以写的东西都不多, 所以就用一篇文章了. 另外这两个东西几乎没有任何关联性

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 时候 windowwidth 变化, 但我不需要在 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 这个状态是一直在 truefalse 之间切换, 但 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>
  );
}

handleR1handleR2 分别是两种写法, 结果都是一样的, 对应 immutable 和 mutable. 我个人是倾向第一种. 但是第一种有一些性能问题

这里我用 console.timeconsole.timeEnd 分别记录两种操作所需要的时间, 第一种 immutable 写法的时间大致为 2000ms, 第二种 mutable 写法时间大致为 2ms, 2000 倍的差距...

效果图如下:
reduce_pattern

点击 immutable 的按钮花了很久才显示结束, 卡顿非常明显, 而 mutable 的按钮秒结束...

所以 immutable 的写法有时候可能不一定那么好?

@hacker0limbo hacker0limbo added react react 笔记整理 javascript 原生 JavaScript 笔记整理 labels Dec 23, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
javascript 原生 JavaScript 笔记整理 react react 笔记整理
Projects
None yet
Development

No branches or pull requests

1 participant