避免在初始化时使用 useState 挂钩到 运行

Avoid useState hook to run on initialisation

我有一个简单的按钮,用于订阅和取消订阅 onPress()

<Button
      onPress={() => setRunning(running => !running)}
      title={running ? 'Stop' : 'Start'}
/>

但是当我初始化状态时,它会运行致命的 useState 函数,因为我无法取消订阅我还没有订阅的内容。

const [running, setRunning] = useState(false);

useState(() => {
  if (running) {
    subscription.subscribe(...)
  } else {
    subscription.unsubscribe();
  }
});

如何避免 useState 运行 除非我按下按钮?

你看到函数 get 运行 的原因是 useState 所做的,它接受初始状态,如果这是一个函数,它 运行 是获取初始状态的函数(仅在挂载时)。

您不会使用 useState 来获得订阅内容等(副作用),这根本不是它的用途。对于(副作用),通常使用 useEffect:

useEffect(() => {
    if (running) {
        subscription.subscribe(/*...*/)
        return () => {
            subscription.unsubscribe();
        };
    }
}, [running]);

这样做的:

  • 任何时候 running 更改,它 运行 就是您传递给它的函数。
  • 如果此时 running 为真,它将订阅该事物,并且 returns 一个 cleanup function 将取消订阅它。
  • 下次 running 更改时,或者当组件被卸载时,调用清理回调,执行取消订阅。

const { useState, useEffect } = React;

const subscription = {
    subscribe() {
        console.log("subscribed");
    },
    unsubscribe() {
        console.log("unsubscribe");
    }
};

const Example = () => {
    const [running, setRunning] = useState(false);

    useEffect(() => {
        if (running) {
            subscription.subscribe(/*...*/)
            return () => {
                subscription.unsubscribe();
            };
        }
    }, [running]);

    return <input type="button" value={running ? "stop" : "run"} onClick={() => setRunning(r => !r)} />;
};

const App = () => {
    const [showExample, setShowExample] = useState(true);

    return <div>
        {showExample ? <Example /> : <em>(example hidden)</em>}
        <div>
            <label>
                <input
                    type="button"
                    value="show/hide example"
                    onClick={() => setShowExample(e => !e)}
                />
            </label>
        </div>
    </div>;
};

ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>

另一个选项是在按钮单击处理程序中 subscribe/unsubscribe,但您仍然需要一个 useEffect 清理回调以确保您在卸载时取消订阅。 (这可能很棘手,因为您不能使用 running 标志来确定是否执行此操作,因为清理函数将关闭它的陈旧副本。为此您可能需要一个参考。)