【问题标题】:React hooks - how to force useEffect to run when state changes to the same value?React hooks - 当状态更改为相同值时如何强制 useEffect 运行?
【发布时间】:2021-06-08 03:21:18
【问题描述】:

所以我正在构建一个鼓垫类型的应用程序,除了这个之外,几乎所有东西都可以正常工作。

编辑:如果有人想看看,请将整个内容放在代码和框上:codesandbox.io/s/sleepy-darwin-jc9b5?file=/src/App.js

const [index, setIndex] = useState(0);
const [text, setText] = useState("");
const [theSound] = useSound(drumBank[index].url)   

function playThis(num) {
  setIndex(num)
}

useEffect(()=>{
  if (index !== null) {
    setText(drumBank[index].id);
    theSound(index);
    console.log(index)
  }
}, [index])

当我按下一个按钮时,索引会更改为与该按钮关联的值,然后 useEffect 挂钩会从位于该 索引 的数组中播放声音。但是,当我多次按下同一个按钮时,它只会播放一次,因为当 index 更改为相同的值时,useState 不会重新渲染应用程序。

我希望能够多次按下同一个按钮并让应用重新渲染,因此每次按下按钮时都会运行 useEffect。谁能帮我怎么做?

【问题讨论】:

  • 我发现这个自定义的按键挂钩可能对你有帮助 useKeyPress
  • 为什么要为此使用useEffect 钩子,而不仅仅是将keyPress 事件侦听器回调与要传递给theSound 的特定索引值相关联?也许我在这个最小的例子中遗漏了一些上下文? useEffect 旨在在依赖项更改时运行效果,而不是在发生异步事件时运行。它似乎不是这项工作的正确工具。
  • @DrewReese 是对的,为什么在useEffect 对您没有任何好处时使用它。只需在一个回调中包装所有 playThis 就足够了。
  • @DrewReese 一开始我试过了,把所有东西都放在 playThis() 中,但是 useSound 是一个自定义钩子,不能放在普通函数中。这就是为什么我将它设置为在 index 而不是 num 播放声音。如果我把它放在 playThis 中的当前形式,它会滞后并在前一个索引处播放声音,因为状态是异步的。
  • 您不需要将useSound 放在任何回调中,并且除了明显的挂钩原因之外,但theSound 可以。我认为 React ref 可以帮助你。您是否有能力创建一个 running 代码框,该代码大部分都在工作?我有一个想法,但我不确定text 状态或drumBank 在哪里发挥作用。

标签: javascript reactjs react-hooks use-effect use-state


【解决方案1】:

您是否考虑过将这些离散状态变量(值类型)组合成一个引用类型的状态对象?

您只需同时设置整个状态,而不是在索引更改时设置文本。

只要确保这是一个新对象/引用,效果就会触发。然后效果器只负责根据当前状态播放声音。

这里有一些示例代码

const [state, setState] = useState({ index: 0, text: '', }); 
const playThis = (num) => { 
    setState({ index: num, text: drumBank[num].id, }); 
}; 
useEffect(() => { 
    theSound(state.index); 
}, [state]);

【讨论】:

  • 能否请您详细说明一下,也许有代码示例?我对 js 和 react 还很陌生,所以有些术语让我有些困惑。
  • 就一个非常简单的示例而言,它看起来像这样:``` const [state, setState] = useState({ index: 0, text: '', }); const playThis = (num) => { setState({ index: num, text:drumBank[num].id, }); }; useEffect(() => { theSound(state.index); }, [state]); ``` 上面的一位评论者提出了一个很好的观点——如果 playThis 只是为了响应用户输入而被调用,你可能不需要 useEffect 并且可以在 playThis 中做所有事情。
  • 呃,有人知道如何在这里格式化代码块吗?!
  • 是的,我认为这是 useSound(我不熟悉)的工作方式。我想如果我是你,我会为你想要播放的每个声音声明一个 useSound 实例,然后根据所选项目进行适当的函数调用。不需要任何效果。
【解决方案2】:

这是我可以从您的沙盒中得出的结果。

  1. 根据文档,每个 useSound 只是一个声音,因此当尝试将索引更新到音库以通过 React 状态使用时,播放的声音将始终延迟至少一个渲染周期。我建议创建一个新的自定义挂钩来封装您的 9 种鼓声。

    useDrumBank 使用鼓组数组并将 9 个鼓声实例化为一个数组。

    const useDrumBank = (drumbank) => {
      const [drum0] = useSound(drumbank[0].url);
      const [drum1] = useSound(drumbank[1].url);
      const [drum2] = useSound(drumbank[2].url);
      const [drum3] = useSound(drumbank[3].url);
      const [drum4] = useSound(drumbank[4].url);
      const [drum5] = useSound(drumbank[5].url);
      const [drum6] = useSound(drumbank[6].url);
      const [drum7] = useSound(drumbank[7].url);
      const [drum8] = useSound(drumbank[8].url);
    
      return [drum0, drum1, drum2, drum3, drum4, drum5, drum6, drum7, drum8];
    };
    
  2. 更新组件逻辑以将drumBank 数组传递给新的自定义挂钩。

    const sounds = useDrumBank(drumBank);
    

这是完整的代码:

function App() {
  useEffect(() => {
    document.addEventListener("keypress", key);

    return () => document.removeEventListener("keypress", key);
  }, []);

  const [text, setText] = useState("");
  const sounds = useDrumBank(drumBank);

  function playThis(index) {
    drumBank[index]?.id && setText(drumBank[index].id);
    sounds[index]();
  }

  function key(e) {
    const index = drumBank.findIndex((drum) => drum.keyTrigger === e.key);
    index !== -1 && playThis(index);
  }

  return (
    <div id="drum-machine" className="drumpad-container">
      <div id="display" className="drumpad-display">
        <p>{text}</p>
      </div>
      <button className="drum-pad" id="drum-pad-1" onClick={() => playThis(0)}>
        Q
      </button>
      <button className="drum-pad" id="drum-pad-2" onClick={() => playThis(1)}>
        W
      </button>
      <button className="drum-pad" id="drum-pad-3" onClick={() => playThis(2)}>
        E
      </button>
      <button className="drum-pad" id="drum-pad-4" onClick={() => playThis(3)}>
        A
      </button>
      <button className="drum-pad" id="drum-pad-5" onClick={() => playThis(4)}>
        S
      </button>
      <button className="drum-pad" id="drum-pad-6" onClick={() => playThis(5)}>
        D
      </button>
      <button className="drum-pad" id="drum-pad-7" onClick={() => playThis(6)}>
        Z
      </button>
      <button className="drum-pad" id="drum-pad-8" onClick={() => playThis(7)}>
        X
      </button>
      <button className="drum-pad" id="drum-pad-9" onClick={() => playThis(8)}>
        C
      </button>
    </div>
  );
}

演示

使用说明

No sounds immediately after load

为了用户着想,浏览器不允许网站发出声音 直到用户与他们互动(例如,通过点击 某物)。在用户点击、轻敲或 触发一些东西。

让按键持续工作似乎是一个问题,我没有立即想到解决方案,但至少此时按钮点击工作并且声音同步播放。

【讨论】:

  • 谢谢!昨晚我自己修补并想出了一个类似的解决方案,但很高兴知道我走对了路。按键问题也发生在我的应用程序版本中,因此 useSound 钩子或其 Howler 依赖项再次出现问题。我会继续自己寻找解决方案,如果没有,请和我一起回到 StackoverFlow。
  • @Lursmani 好吧,我从文档中链接了有关按键的相关说明(至少似乎相关),但是当它们工作时似乎有点“随机”......就像在代码和框中的编辑将使按键工作。不过,这不是应用程序依赖的东西。不过,修补起来很有趣,所以谢谢。如果您也想为帮助和信息加 1,我不介意。祝你好运,伙计。
  • 现在将添加 +1。刚刚获得 15 次代表,所以我之前无法 +1。是的,我有同样的问题,即使在代码中修改一个不相关的字符也会使声音起作用。我尝试添加一个“电源按钮”,仅在打开电源时才允许播放声音,但按键仍然不起作用。所以调查仍在继续。
猜你喜欢
  • 1970-01-01
  • 2021-02-09
  • 1970-01-01
  • 2023-03-07
  • 2019-11-01
  • 2020-11-28
  • 2019-08-04
  • 2022-11-20
  • 2019-12-11
相关资源
最近更新 更多