#reactjs #react-hooks
#reactjs #реагирующие хуки
Вопрос:
Может кто-нибудь, пожалуйста, помочь мне понять, почему эта переменная requestCount
привязывается к 0? Я ожидаю, что оно будет увеличиваться каждые 3 секунды, но вместо этого этого не происходит.
const SomeComponent = () => {
const [requestCount, setRequestCount] = useState(-1);
const doSomeWork= () => {
setRequestCount(requestCount 1);
setTimeout(doSomeWork, 3000);
};
return (
<>
<button onClick={doSomeWork}>Click</button>
{requestCount}
</>
);
};
Ответ №1:
Сначала следует знать некоторые знания: почему я вижу устаревшие реквизиты или состояние внутри моей функции?
У вас есть два варианта:
- Используйте функциональные обновления:
- Используйте
useCallback
с зависимостями для создания новыхdoSomeWork
каждый раз при измененииrequestCount
состояния. Так чтоdoSomeWork
вместо устаревшего значения будет считываться последнееrequestCount
состояние. И вы должны выполнить задачу макроса, поставленную в очередь,setTimeout
используяuseEffect()
hook сdoSomeWork
зависимостью. Потому что нам нужно дождаться завершения выполненияuseCallback
и создать новуюdoSomeWork
функцию, чтобы макрос task(doSomeWork
) прочитал последнююrequestCount
версию.
В приведенном ниже примере показаны эти два варианта:
SomeComponent.tsx
:
import React, { useCallback, useEffect, useRef } from 'react';
import { useState } from 'react';
export const SomeComponent = () => {
const [requestCount, setRequestCount] = useState(-1);
// should execute macro task queued by setTimeout
const ref = useRef(null);
// option 1
// const doSomeWork = () => {
// setRequestCount((pre) => pre 1);
// setTimeout(doSomeWork, 3000);
// };
// option 2
const doSomeWork = useCallback(() => {
setRequestCount(requestCount 1);
if (ref.current) return;
ref.current = true;
}, [requestCount]);
useEffect(() => {
if (ref.current) {
setTimeout(doSomeWork, 3000);
}
}, [ref.current, doSomeWork]);
return (
<>
<button onClick={doSomeWork}>Click</button>
{requestCount}
</>
);
};
Модульный тест:
import { render, screen, fireEvent, act } from '@testing-library/react';
import React from 'react';
import { SomeComponent } from './SomeComponent';
describe('70297876', () => {
test('should pass', async () => {
jest.useFakeTimers();
const { container } = render(<SomeComponent />);
expect(container).toMatchInlineSnapshot(`
<div>
<button>
Click
</button>
-1
</div>
`);
const button = screen.getByText('Click');
fireEvent.click(button);
act(() => {
jest.advanceTimersByTime(3000);
});
expect(container).toMatchInlineSnapshot(`
<div>
<button>
Click
</button>
1
</div>
`);
act(() => {
jest.advanceTimersByTime(3000);
});
expect(container).toMatchInlineSnapshot(`
<div>
<button>
Click
</button>
2
</div>
`);
});
});
Результат теста:
PASS stackoverflow/70297876/SomeComponent.test.tsx
70297876
✓ should pass (36 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 3 passed, 3 total
Time: 3.865 s, estimated 4 s