Ionic 使用调用异步函数的函数时,下划线的反跳似乎不起作用

vddsk6oq  于 2022-12-09  发布在  Ionic
关注(0)|答案(2)|浏览(424)

我有一些离子段按钮,基本上作为切换开关。我试图限制切换的影响,可以切换/选择的速度),并尝试去反弹和节流从两个破折号和现在下划线没有成功。
我目前正在使用下划线,尽管使用了去反跳功能,但仍然存在该函数被自由调用的问题。
下面是我的代码:

const SensorMeasurements: React.FC = () => {
//.....

const setOptionsHandler = (value: string, option: string, key: string) => {
if (value === "true") {
  console.log("RESULT: True / " + value + " / " + option + " / " + key);
  if (!settingsHandler(key)) {
    setControlHandler(option, false); //toggle on
    console.log("TOGGLE " + option + " ON!");
  }
}
else { // false
  console.log("RESULT: False / " + value + " / " + option + " / " + key);
    if (settingsHandler(key)) {
    setControlHandler(option, false); //toggle off
    console.log("TOGGLE " + option + " OFF!");
    }
  }
}

const setOptionsHandlerThrottled = _.debounce(setOptionsHandler, 5000, true);

const setControlHandler = async (input: string, notify?: boolean) => {
    let sysCommand = "";
    switch (input) {
      case "Reset":
        sysCommand = "R";
        break;
      case "Test":
        sysCommand = "T";
        break;
      case "Calibration":
        sysCommand = "B";
        break;
      case "Logging":
        sysCommand = "L";
        break;
      case "Trigger":
        sysCommand = "H";
        break;
      case "Measuring":
        sysCommand = "M";
        break;
      case "Unit":
        sysCommand = "U";
        break;
    }
    try {
      const abControl = str2ab(sysCommand);
      const dvControl = new DataView(abControl.buffer);

      //console.log("CONTROL BUFFER", abControl);

      await BleClient.initialize();

      //if (isAndroid && (devices.deviceId ?? true)) {
      //  await BleClient.getDevices(devices.deviceId);
      //  await BleClient.disconnect(devices.deviceId);
      //}

      await BleClient.getDevices(devices.deviceId);
      await BleClient.connect(devices.deviceId, (deviceId) => onDisconnect(deviceId));
      await BleClient.write(devices.deviceId, SERV_SYSTEM_CONTROL, CHAR_OPERATIONAL_MODE, dvControl);

      if (notify !== false) {
        present(input + ' Command Sent.', 3000);
      }

    } catch (error) {
      CatchError(error, "Disconnected");
    }
  }

  const settingsHandler = (string: string) => {
    return trueOrFalse(iTrueStates, string) as unknown as string;
  }

 const trueOrFalse = (array: string[], string: string) => {
   //check if string is in the array - returns boolean
   return array.includes(string);
 }

return (   
 <IonSegment onIonChange={e => setOptionsHandlerThrottled(e.detail.value as string, "Trigger", "statusSensorHighTrigger")} className="lowercase" color="brand" value={settingsHandler("statusSensorHighTrigger")}>
      <IonSegmentButton value="false">
        <IonLabel>Low</IonLabel>
      </IonSegmentButton>
      <IonSegmentButton value="true">
        <IonLabel>High</IonLabel>
      </IonSegmentButton>
    </IonSegment>
);
export default SensorMeasurements;

当IonSegmentButton改变时,为什么setOptionsHandler仍然被调用,而不管去抖超时?我是否在每次调用它时都意外地创建了一个新的去抖函数?

hjzp0vay

hjzp0vay1#

As you guessed, the problem is that you are creating a new debounced function every time the component is rendered. The multiple debounced functions are unaware of each other's timeouts. If we omit the other code for clarity, the problem is relatively easy to see:

// This function is called on every render
const SensorMeasurements: React.FC = () => {
    //.....

    const setOptionsHandler = // ...

    // This constant is recreated on every invocation of SensorMeasurements
    const setOptionsHandlerThrottled = _.debounce(setOptionsHandler, 5000, true);

    return (
        // The onIonChange event handler is unique on each render
        <IonSegment onIonChange={e => setOptionsHandlerThrottled(e.detail.value as string, "Trigger", "statusSensorHighTrigger")} className="lowercase" color="brand" value={settingsHandler("statusSensorHighTrigger")}>
            <IonSegmentButton value="false">
                <IonLabel>Low</IonLabel>
            </IonSegmentButton>
            <IonSegmentButton value="true">
                <IonLabel>High</IonLabel>
            </IonSegmentButton>
        </IonSegment>
    );
}

The simple solution is to use a class component instead. By creating the debounced function in the constructor, it can be retained between renderings. Most other functions can be free-standing at module scope, since they do not depend on component state.
Do note that if the instance of the component is replaced as a whole (for example because a containing component is re-rendered), the timeout will still be reset. In that case, you might need to lift the debounced function into the larger component.

// Module scope, not inside component

const setOptionsHandler = (value: string, option: string, key: string) => {
    // Same as before
}

const setControlHandler = async (input: string, notify?: boolean) => {
    // Same as before
}

const settingsHandler = (string: string) => {
    // Same as before
}

const trueOrFalse = (array: string[], string: string) => {
    // Same as before
}

// Class component

class SensorMeasurements extends React.Component {
    setOptionsHandlerThrottled: typeof setOptionsHandler;

    constructor() {
        // This debounced function persists throughout the lifetime of the component
        this.setOptionsHandlerThrottled = _.debounce(setOptionsHandler, 5000, true);
    }

    render() {
        return (
            <IonSegment onIonChange={e => this.setOptionsHandlerThrottled(e.detail.value as string, "Trigger", "statusSensorHighTrigger")} className="lowercase" color="brand" value={settingsHandler("statusSensorHighTrigger")}>
                <IonSegmentButton value="false">
                    <IonLabel>Low</IonLabel>
                </IonSegmentButton>
                <IonSegmentButton value="true">
                    <IonLabel>High</IonLabel>
                </IonSegmentButton>
            </IonSegment>
        );
    }
}

export default SensorMeasurements;

A possible alternative to using _.debounce would be to use the debounce operator from RxJS. In that case, a similar consideration would apply that you might need to lift the subject to a larger containing component.
In the comments, you stated that the problem went away when you removed the async / await syntax from the setControlHandler function. I cannot explain this; with your original code, you will create a new debounced function on each render regardless. I suspect some other, unrelated bug is preventing the event handler from triggering again in that case.
As an encore, I would like to show you a cleaner way to write your setControlHandler function:

// The mapping from inputs to sysCommands is entirely static, so you can
// define it in advance.
const inputCommand = {
    Reset: 'R',
    Test: 'T',
    Calibration: 'B',
    Logging: 'L',
    Trigger: 'H',
    Measuring: 'M',
    Unit: 'U',
};

const setControlHandler = async (input: string, notify?: boolean) => {
    // Replacing your switch pyramid by a property lookup
    const sysCommand = inputCommand[input];
    // Remainder as before
}
wljmcqd8

wljmcqd82#

最后,我最终使用了AwesomeDebouncePromise,因为这是最好的。为了防止每次重新渲染都创建一个新的Debounced函数,我将debounced函数 Package 在一个useMemo中。

const setOptionsHandlerThrottled = useMemo( () => AwesomeDebouncePromise(setOptionsHandler, 3000), [iTrueStates] );

相关问题