首页 > 解决方案 > 如何在 ReactJS 的 Promise 中解析 useReducer 的调度函数

问题描述

我正在构建一个带有反应的简单鼓机应用程序,并且我正在使用带有钩子和 useReducer 的 Context Api。应用程序的初始状态是电源键设置为 false 的对象。

基本上我想要实现的是;当电源“打开”(真)时,它会发送一个动作来显示一个闪烁的“-LOADING PRESETS-”,持续很短的时间,比如 2 秒,然后触发另一个调度动作来显示默认设置,比如“鼓”不眨眼

当电源关闭时,它会将所有内容重置为初始状态。我将 useEffect 与 2 个函数一起使用,这些函数返回一个模拟异步步骤的承诺。

我几乎把所有事情都做好了;除了加载功能外,鼓垫、音频和灯光都可以正常工作。问题是它跳过显示闪烁的“加载预设”并自动解决以显示闪烁的默认设置“鼓”(它不应该)。我正在附加一个类以使文本闪烁并在 2 秒延迟到期后删除该类。我不确定如何以及在哪里清理/清除 setTimeout,所以我现在将其注释掉。需要帮忙!

这是 App.js 代码:

const initialState = {
  power: false,
  display: '...',
  sound_mode: '...',
  volume_lvl: 0.4
};

const stateReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'TOGGLE_POWER':
      return { ...state, power: !state.power };
    case 'ADJUST_VOLUME':
      return { ...state, volume_lvl: action.volume };
    case 'SWITCH_TO_SYNTH_MODE':
      return {
        ...state,
        display: action.display,
        sound_mode: action.sound_mode
      };
    case 'SWITCH_TO_DRUM_MODE':
      return {
        ...state,
        display: action.display,
        sound_mode: action.sound_mode
      };
    case 'CHANGE_DISPLAY':
      return { ...state, display: action.display };
    case 'LOAD_SETTINGS':
      return {
        ...state,
        display: 'Kick',
        sound_mode: 'Drums',
        volume_lvl: 0.4
      };
    case 'RESET_DEFAULTS':
      return {
        ...state,
        power: false,
        display: '...',
        sound_mode: '...',
        volume_lvl: 0.4
      };
    default:
      return state;
  }
};

function App() {
  const [appState, dispatch] = useReducer(stateReducer, initialState);

  return (
    <AppContext.Provider value={{ appState, dispatch }}>
      <div className='DrumApp'>
        <Header />
        <DrumInterface />
        <Footer />
      </div>
    </AppContext.Provider>
  );
}

电源按钮所在的标题部分:

function Header() {
  const { appState, dispatch } = useContext(AppContext);
  const { power } = appState;
  const powerAudioRef = useRef();
  const displayRef = useRef();

  useEffect(() => {
    function loadPrompt(power) {
      return new Promise((resolve, reject) => {
        if (power === true) {
          displayRef.current = setTimeout(() => {
            resolve([
              dispatch({
                type: 'CHANGE_DISPLAY',
                display: '--loading preset--'
              }),
              document.getElementById('preset-status').classList.add('loading')
            ]);
          }, 2000);
        }
      });
    }

    function loadSettings() {
      return new Promise((resolve, reject) => {
        resolve([
          //document.getElementById('preset-status').classList.remove('loading'),
          dispatch({ type: 'LOAD_SETTINGS' }),
          clearTimeout(displayRef.current)
        ]);
      });
    }

    async function loadPresetSettings() {
      await loadPrompt(power);
      await loadSettings();
    }

    loadPresetSettings();

    // return () => {
    //   clearTimeout(displayRef.current);
    // };
  }, [power, dispatch]);

  useEffect(() => {
    if (power === false) {
      dispatch({ type: 'RESET_DEFAULTS' });
    }
  }, [power, dispatch]);

  const handlePower = () => {
    dispatch({ type: 'TOGGLE_POWER' });
    handlePowerSound();
  };

  const handlePowerSound = () => {
    document.getElementById('power-btn-audio').play();
    document.getElementById('power-btn-audio').currentTime = 0;
  };

  return (
    <header id='App-header'>
      <div id='logo'>
        <h1>
          <span className='orange'>Drum</span>Machine
        </h1>{' '}
        <p className='small'>
          Powered by:{' '}
          <FaReact
            className={power === true ? 'react-icon spin' : 'react-icon'}
          />{' '}
          React
        </p>
      </div>
      <div id='power'>
        <button
          id='power-btn'
          className={power === true ? 'on' : null}
          onClick={handlePower}
        >
          <FaPowerOff />
        </button>
        <audio
          ref={powerAudioRef}
          id='power-btn-audio'
          src='https://res.cloudinary.com/dzsmdyknz/video/upload/v1532750168/sample-swap/sfx-and-unusual-sounds/bleeps-blips-blonks-blarts-and-zaps/boip.mp3'
          className='btn-audio-efx'
        >
          Your browser does not support the audio element.
        </audio>
      </div>
    </header>
  );
}

标签: javascriptreactjs

解决方案


更新:

最后,我能够解决模拟异步进程的问题,这是我的代码:

useEffect(() => {

    /* Display the loading preset and add a classname to make it blink */
    function promptLoadingStart() {
      return new Promise((resolve, reject) => {
        let wait = setTimeout(() => {
          dispatch({ type: 'CHANGE_DISPLAY', display: '--loading preset--' });
          document.getElementById('preset-status').classList.add('loading');
          clearTimeout(wait);
          resolve('prompt loading started');
        }, 2000);
      });
    }

    /* Remove the classname for blinking text */
    function prompLoadingEnd() {
      return new Promise((resolve, reject) => {
        let wait = setTimeout(() => {
          document.getElementById('preset-status').classList.remove('loading');
          clearTimeout(wait);
          resolve('prompt loading ended');
        }, 1000);
      });
    }

    /* Display the initial settings */
    function loadSettings() {
      return new Promise((resolve, reject) => {
        let wait = setTimeout(() => {
          dispatch({ type: 'LOAD_SETTINGS' });
          handlePowerSound();
          clearTimeout(wait);
          resolve('load settings done');
        }, 200);
      });
    }

    async function loadPreset() {
      const step_one = await promptLoadingStart();
      console.log(step_one);
      const step_two = await prompLoadingEnd();
      console.log(step_two);
      const step_three = await loadSettings();
      console.log(step_three);
    }
    if (power === true) {
      loadPreset();
    }
  }, [power, dispatch]);

伙计们,如果您有更好的解决方案,我想知道如何。谢谢!


推荐阅读