#javascript #react-hooks
#javascript #реагирующие крючки
Вопрос:
Я хочу быть уверен, что блок кода не выполняется одновременно. Однако библиотека async-Mutex, похоже, не работает для меня. Вот минимальная репликация:
/* eslint-disable */
import React, { useState, useEffect } from "react";
var Mutex = require("async-mutex").Mutex;
const Timer = () => {
const [isActive, setIsActive] = useState(false);
const mutex = new Mutex();
useEffect(() => {
mutex.runExclusive(async function () {
console.log("starting", isActive);
await new Promise((resolve) => setTimeout(resolve, 1000));
console.log("ending", isActive);
});
}, [isActive]);
return (
<div className="app">
<button onClick={() => setIsActive((prev) => !prev)} type="button">
{isActive ? "Active" : "Inactive"}
</button>
</div>
);
};
export { Timer as default };
Если вы дважды быстро нажмете на кнопку, вы увидите, что она будет печатать
starting
true
starting
false
starting
true
ending
true
ending
false
Вы можете видеть, что они чередуются.
Как я могу заставить это не выполняться одновременно?
Ответ №1:
import React, { useState, useEffect, useRef, useMemo} from "react";
var Mutex = require("async-mutex").Mutex;
const Timer = () => {
const [isActive, setIsActive] = useState(false);
const mutex = useRef(new Mutex());
// Or another way, it might be a bit more performant,
// although the Mutex class has an almost empty constructor
// const mutex= useMemo(()=> new Mutex(), []);
useEffect(() => {
mutex.current.runExclusive(async function () {
console.log("starting", isActive);
await new Promise((resolve) => setTimeout(resolve, 1000));
console.log("ending", isActive);
});
}, [isActive]);
return (
<div className="app">
<button onClick={() => setIsActive((prev) => !prev)} type="button">
{isActive ? "Active" : "Inactive"}
</button>
</div>
);
};
export { Timer as default };
На случай, если вы хотите отменить предыдущий вызов функции и поддерживать очистку асинхронных подпрограмм при размонтировании (демонстрация):
/* eslint-disable */
import React, { useState, useEffect } from "react";
import CPromise from "c-promise2";
import { useAsyncEffect, useAsyncCallback } from "use-async-effect2";
export default function Timer() {
const [isActive, setIsActive] = useState(false);
const callback = useAsyncCallback(
function* () {
console.log("starting", isActive);
yield CPromise.delay(1000);
console.log("ending", isActive);
},
{ combine: true }
);
useAsyncEffect(
function* () {
console.log("mount");
yield callback();
},
[isActive]
);
return (
<div className="app">
<button onClick={() => setIsActive((prev) => !prev)} type="button">
{isActive ? "Active" : "Inactive"}
</button>
</div>
);
}
Ответ №2:
Проблема, скорее всего, вызвана тем, что мьютекс выделяется при каждом повторном рендеринге, предоставляя вам новый экземпляр.
- Вы можете переместить
const mutex = new Mutex();
out из своего компонента, предоставив вам одну глобальную версию для всех ваших компонентов таймера, что означает, что таймеры могут блокировать друг друга. - Вы могли бы сделать экземпляр мьютекса стабильным во время повторного рендеринга, обернув его в
React.useMemo
хук, при этом у каждого компонента таймера был бы свой собственный мьютекс, что означает, что таймеры не могли блокировать друг друга, только себя.
Замените ваше объявление внутри вашего компонента на:
const mutex = React.useMemo(() => new Mutex(), []);
Затем добавьте мьютекс в свой массив зависимостей в useEffect
:
useEffect(() => {
// Your code
}, [isActive, mutex]);