【问题标题】:How do I lower the rate of http requests triggered due to onChange for Material UI Slider?如何降低 Material UI Slider 的 onChange 触发的 http 请求率?
【发布时间】:2020-06-22 20:30:20
【问题描述】:

如何缓冲我的请求?

目前我在我的 React 项目中使用 Material UI 的滑块。当我沿着滑块移动时,我正在使用 onChange 属性触发 http 发布请求。代码看起来像这样。

<Slider
          onChange={handleChange}
          valueLabelFormat={valueLabelFormat}
          //   aria-labelledby="discrete-slider"
          aria-labelledby="non-linear-slider"
          valueLabelDisplay="auto"
          value={value}
          min={1}
          max={5}
          step={0.05}
        />

但是,如果您可以想象,onChange 会过于频繁,并且会发送太多的 HTTP 请求。这绝对不推荐。我也不打算使用 onChangeCommitted,因为它不是我想要的交互方式。

所以这就是我想做的:
如果我沿着滑块(或 onChange)移动,onChange 将不断更新该值,但 http 请求只会每 500 毫秒触发一次(最新值使用 onChange 更新)。而不是触发每个 onChange 的请求!
我完全不知道如何实现这一点..... setInterval 或..?不太确定。我们将不胜感激。
谢谢!


更新
答案是使用 debounce(请看下面 k.s. 的答案)
但是,我的用例与通常的用例略有不同。我在 debounce 中使用的函数需要对其进行输入。我在我的 handleChange() 函数中使用 debounce,因为我需要 handleChange 来设置我的滑块的值(以便它平滑滚动)并在内部执行 http 调用的函数上调用 debounce。

<Slider
          onChange={handleChange}
          valueLabelFormat={valueLabelFormat}
          //   aria-labelledby="discrete-slider"
          aria-labelledby="non-linear-slider"
          valueLabelDisplay="auto"
          value={value}
          min={1}
          max={5}
          step={0.05}
        />

这就是我的 handleChange() 的样子

const boostedLabel ="some label"

const handleChange = (_event, newValue) => {
    //Update value back to Slider
    setValue(newValue);

    const debouncedFunc = () => updateWeightValue(boostedLabel, newValue);
    debounce(debouncedFunc, 300, {leading:false, trailing:true});
  };

但是这不起作用!滑块移动,但似乎根本没有触发 debouncedFunc。

【问题讨论】:

  • 您好,感谢您的发帖!您能否显示您当前的反应代码,特别是 onChange 函数。谢谢!
  • 你可以查看 lodash debounce lodash.com/docs/4.17.15#debounce

标签: javascript reactjs slider material-ui onchange


【解决方案1】:

您的用例与其他去抖动用例没有明显不同,只是您还没有完全理解应该如何使用debounce。调用debounce 不会调用你的函数;相反,它会返回您的函数的去抖动版本,然后可以使用您需要的许多参数正常调用该版本。

您不应该在handleChange 中调用debounce。您只想在使用它的元素的整个生命周期内调用它一次 - 然后在您的 handleChange 函数中使用该函数的去抖动版本(由 debounce 返回)。

最简单的方法是在顶层创建去抖动函数(完全在组件之外)。但是,如果您可以在页面上为该类型的组件拥有多个元素,这并不完全安全。如果您有多个元素共享相同的去抖动功能,那么非常快速的用户可能(尽管不太可能)在延迟结束之前(例如,在 300 毫秒内)更改多个滑块,在这种情况下,第一个元素更改会不会保存到后端。如果 debounced 函数实际上是保存页面上所有可编辑元素的当前状态(而不仅仅是触发 change 事件的那个),那么在顶层调用 debounce 就是你想要的。

为避免顶级去抖动的轻微危险,您可以在组件中调用 debounce 以确保只为您的元素调用一次。对于下面示例中的第三个滑块,我在lazy state initializer 中调用debounce,并忽略useState 返回的设置器。然后在每次重新渲染时,将使用相同的去抖动函数。稍作改动,也可以利用useRef 而不是useStateuseCallbackuseMemo(尽管保证每个元素只调用一次去抖动对于useCallbackuseMemo 来说并不安全-- documentation 声明“你可以依赖 useMemo 作为性能优化,而不是语义保证。”)。

下面的示例显示了 3 个滑块(全部共享相同的状态)。您可以看到callHttpRequest 只是将其参数记录到控制台。 3 个滑块中的每一个都以不同的方式在其 handleChange 函数中调用它。第一个根本没有去抖动,因此您可以在移动滑块时看到许多控制台日志。第二个是在顶层去抖动的,所以当您停止移动滑块至少 300 毫秒时,您只会看到控制台日志。第三个在传递给useStatelazy initializer 内的组件中消除抖动,其行为与第二个滑块相同。

import React from "react";
import { makeStyles } from "@material-ui/core/styles";
import Grid from "@material-ui/core/Grid";
import Typography from "@material-ui/core/Typography";
import Slider from "@material-ui/core/Slider";
import VolumeDown from "@material-ui/icons/VolumeDown";
import VolumeUp from "@material-ui/icons/VolumeUp";
import debounce from "lodash/debounce";

const useStyles = makeStyles({
  root: {
    width: 300
  }
});

const callHttpRequest = (eventSrcDesc, newValue) => {
  console.log({ eventSrcDesc, newValue });
};
const topLevelDebounceCallHttpRequest = debounce(callHttpRequest, 300, {
  leading: false,
  trailing: true
});

export default function ContinuousSlider() {
  const classes = useStyles();
  const [value, setValue] = React.useState(30);

  const handleChangeNoDebounce = (event, newValue) => {
    setValue(newValue);
    callHttpRequest("volume-no-debounce", newValue);
  };
  const handleChangeUsingTopLevelDebounce = (event, newValue) => {
    setValue(newValue);
    topLevelDebounceCallHttpRequest("volume-top-level", newValue);
  };
  const [stateDebounceCallHttpRequest] = React.useState(() =>
    debounce(callHttpRequest, 300, {
      leading: false,
      trailing: true
    })
  );
  const handleChangeUsingStateDebounce = (event, newValue) => {
    setValue(newValue);
    stateDebounceCallHttpRequest("volume-useState", newValue);
  };

  return (
    <div className={classes.root}>
      <Typography id="continuous-slider" gutterBottom>
        Volume (No Debounce)
      </Typography>
      <Grid container spacing={2}>
        <Grid item>
          <VolumeDown />
        </Grid>
        <Grid item xs>
          <Slider
            value={value}
            onChange={handleChangeNoDebounce}
            aria-labelledby="continuous-slider"
          />
        </Grid>
        <Grid item>
          <VolumeUp />
        </Grid>
      </Grid>
      <Typography id="continuous-slider-top-level-debounce" gutterBottom>
        Volume (Debounce called at top-level)
      </Typography>
      <Grid container spacing={2}>
        <Grid item>
          <VolumeDown />
        </Grid>
        <Grid item xs>
          <Slider
            value={value}
            onChange={handleChangeUsingTopLevelDebounce}
            aria-labelledby="continuous-slider-top-level-debounce"
          />
        </Grid>
        <Grid item>
          <VolumeUp />
        </Grid>
      </Grid>
      <Typography id="continuous-slider-useState-debounce" gutterBottom>
        Volume (Debounce called in useState lazy initializer)
      </Typography>
      <Grid container spacing={2}>
        <Grid item>
          <VolumeDown />
        </Grid>
        <Grid item xs>
          <Slider
            value={value}
            onChange={handleChangeUsingStateDebounce}
            aria-labelledby="continuous-slider-useState-debounce"
          />
        </Grid>
        <Grid item>
          <VolumeUp />
        </Grid>
      </Grid>
    </div>
  );
}

这是第二个沙箱,它有助于展示顶级去抖动与特定元素去抖动之间的区别:https://codesandbox.io/s/debounce-slider-onchange-jimls?file=/demo.js。在这个沙箱中,整个组件被渲染了两次,延迟是 3 秒而不是 300 毫秒。这提供了足够的时间在延迟结束之前使用顶级去抖动来移动两个不同的滑块,并看到只有第二个产生控制台日志;而使用特定于元素的 debounced 函数(通过 useState 管理)对两个滑块执行相同的操作会从每个元素生成一个控制台日志。

Lodash 去抖文档:https://lodash.com/docs/4.17.15#debounce

【讨论】:

  • 这正是我所需要的,我相信它会对每个正在寻找类似答案的人有所帮助!代码演示非常有用!谢谢
  • 嗨瑞恩,我有一个扩展。这很好用,但是当我拖着走时,会进行一些 api 调用,这并不理想。我如何只调用 api,比如在我停止滑动后 0.3 秒内?换句话说,当滑块仍然“活动”时,不要触发。
  • @Daryll 这就是它的工作原理。它仅在保持滑块静止至少 300 毫秒后才会调用。如果您希望在触发 API 调用之前等待更长的时间,您可以随时将该值提高到 500 或 750(随心所欲)。
【解决方案2】:

如评论中所述。这是去抖动功能的完美工作。那里有很多变化。这是一个使用lodash/debounce 函数onChange={debounce(handleChange)} 的示例,它返回一个函数,这很好,因为按钮需要一个函数引用。因此,在渲染时,您的 handleChange 被称为 debounce 函数参数,然后您在实际更改事件中调用它,无论您将提供多少 handleChange 事件 - 只有最后一个会运行。

下面是 debounce 函数的底层实现之一:

function debounce(func, wait, immediate) {
    var timeout;
    return function() {
        var context = this, args = arguments;
        var later = function() {
            timeout = null;
            if (!immediate) func.apply(context, args);
        };
        var callNow = immediate && !timeout;
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
        if (callNow) func.apply(context, args);
    };
};

取自here,如果您想进一步了解它的工作原理。

更新

debounce 函数返回一个您需要调用的函数。

const boostedLabel = "foo"

const handleChange = (_event, newValue) => {
  setValue(newValue);
  updateWeightValue(boostedLabel, newValue)
}

并且在传递给onChange 属性时仍然使用debounce 进行包装,它将包含传递给handleChange 的所有参数

<Slider
          onChange={debounce(handleChange)}
          valueLabelFormat={valueLabelFormat}
          //   aria-labelledby="discrete-slider"
          aria-labelledby="non-linear-slider"
          valueLabelDisplay="auto"
          value={value}
          min={1}
          max={5}
          step={0.05}
        />

【讨论】:

  • 非常感谢!这就是我要找的。但是我不能让它继续工作,因为我的实现略有不同。看看我上面更新的问题!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-09-06
相关资源
最近更新 更多