如何降低 Material UI Slider 因 onChange 触发的 http 请求率?

Dar*_*ll 2 javascript onchange slider reactjs material-ui

我如何缓冲我的请求?

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

<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}
        />
Run Code Online (Sandbox Code Playgroud)

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

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


更新
答案是使用 debounce (请参阅下面ks的答案)
但是我的用例与通常的用例略有不同。我在去抖动中使用的函数需要输入。我在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}
        />
Run Code Online (Sandbox Code Playgroud)

这就是我的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});
  };
Run Code Online (Sandbox Code Playgroud)

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

Rya*_*ell 5

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

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

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

为了避免顶级去抖的轻微危险,您可以在组件中调用去抖,确保只为元素调用一次。对于下面示例中的第三个滑块,我调用debounce惰性状态初始值设定项并忽略也由useState. 然后在每次重新渲染时,将使用相同的去抖函数。经过细微的改变,也可以利用useReforuseStateuseCallbackor (尽管每个元素只调用 debounce 一次的保证对于anduseMemo来说并不安全——文档指出“您可以依赖 useMemo 作为性能优化,而不是像语义保证。”)。useCallbackuseMemo

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

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>
  );
}
Run Code Online (Sandbox Code Playgroud)

编辑去抖动滑块 onChange

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

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