Я использую Redux для подписки на магазин и обновления компонента.
Это упрощенный пример без Redux. Он использует магазин макетов для подписки и отправки.
Пожалуйста, следуйте инструкциям ниже фрагмента, чтобы воспроизвести проблему.
Изменить: перейдите ко второму фрагменту демонстрации в разделе Обновление, чтобы получить более сжатый и близкий к реальности сценарий. Вопрос не в Redux. Речь идет об идентификаторе функции setState React, вызывающей повторный рендеринг в определенных обстоятельствах, даже если состояние не изменилось.
Редактировать 2: добавлена еще более краткая демонстрация в разделе «Обновление 2».
const {useState, useEffect} = React;
let counter = 0;
const createStore = () => {
const listeners = [];
const subscribe = (fn) => {
listeners.push(fn);
return () => {
listeners.splice(listeners.indexOf(fn), 1);
};
}
const dispatch = () => {
listeners.forEach(fn => fn());
};
return {dispatch, subscribe};
};
const store = createStore();
function Test() {
const [yes, setYes] = useState('yes');
useEffect(() => {
return store.subscribe(() => {
setYes('yes');
});
}, []);
console.log(`Rendered ${++counter}`);
return (
<div>
<h1>{yes}</h1>
<button onClick={() => {
setYes(yes === 'yes' ? 'no' : 'yes');
}}>Toggle</button>
<button onClick={() => {
store.dispatch();
}}>Set to Yes</button>
</div>
);
}
ReactDOM.render(<Test />, document.getElementById('root'));
<div id="root"></div>
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
Что происходит
- ✅ Нажмите «Установить на Да». Поскольку значение
yes
уже равно «да», состояние не изменилось, следовательно, компонент не визуализируется повторно. - ✅ Нажмите «Переключить».
yes
установлен на «нет». Состояние изменилось, поэтому компонент повторно отрисован. - ✅ Нажмите «Установить на Да».
yes
установлено на «да». Состояние снова изменилось, поэтому компонент повторно отрисовывается. - ⛔ Снова нажмите «Установить на Да». Состояние не изменилось, но компонент все еще обрабатывается повторно.
- ✅ Последующие нажатия на «Установить на Да» не вызывают ожидаемого повторного рендеринга.
Что должно произойти
На шаге 4 компонент не должен быть повторно отрисован, так как состояние не изменилось.
Обновлять
Как указано в React docs, useEffect
подходит для многих распространенных побочных эффектов, таких как настройка подписок и обработчиков событий ...
Одним из таких вариантов использования может быть прослушивание таких событий браузера, как online
и offline
.
В этом примере мы вызываем функцию внутри useEffect
один раз при первой визуализации компонента, передавая ей пустой массив []
. Функция настраивает прослушиватели событий для изменения состояния в сети.
Предположим, в интерфейсе приложения у нас также есть кнопка для ручного переключения онлайн-состояния.
Пожалуйста, следуйте инструкциям ниже фрагмента, чтобы воспроизвести проблему.
const {useState, useEffect} = React;
let counter = 0;
function Test() {
const [online, setOnline] = useState(true);
useEffect(() => {
const onOnline = () => {
setOnline(true);
};
const onOffline = () => {
setOnline(false);
};
window.addEventListener('online', onOnline);
window.addEventListener('offline', onOffline);
return () => {
window.removeEventListener('online', onOnline);
window.removeEventListener('offline', onOffline);
}
}, []);
console.log(`Rendered ${++counter}`);
return (
<div>
<h1>{online ? 'Online' : 'Offline'}</h1>
<button onClick={() => {
setOnline(!online);
}}>Toggle</button>
</div>
);
}
ReactDOM.render(<Test />, document.getElementById('root'));
<div id="root"></div>
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
Что происходит
- ✅ Компонент сначала отображается на экране, а сообщение регистрируется в консоли.
- ✅ Нажмите «Переключить».
online
установлен наfalse
. Состояние изменилось, поэтому компонент повторно отрисован. - ⛔ Откройте инструменты разработчика и на панели «Сеть» переключитесь в «офлайн».
online
уже былfalse
, поэтому состояние не изменилось, но компонент все еще обрабатывается повторно.
Что должно произойти
На шаге 3 компонент не должен быть повторно отрисован, так как состояние не изменилось.
Обновление 2
const {useState, useEffect} = React;
let counterRenderComplete = 0;
let counterRenderStart = 0;
function Test() {
const [yes, setYes] = useState('yes');
console.log(`Component function called ${++counterRenderComplete}`);
useEffect(() => console.log(`Render completed ${++counterRenderStart}`));
return (
<div>
<h1>{yes ? 'yes' : 'no'}</h1>
<button onClick={() => {
setYes(!yes);
}}>Toggle</button>
<button onClick={() => {
setYes('yes');
}}>Set to Yes</button>
</div>
);
}
ReactDOM.render(<Test />, document.getElementById('root'));
<div id="root"></div>
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
Что происходит
- ✅ Нажмите «Установить на Да». Поскольку значение
yes
уже равноtrue
, состояние не изменилось, поэтому компонент не визуализируется повторно. - ✅ Нажмите «Переключить».
yes
установлен наfalse
. Состояние изменилось, поэтому компонент повторно отрисован. - ✅ Нажмите «Установить на Да».
yes
установлен наtrue
. Состояние снова изменилось, поэтому компонент повторно отрисовывается. - ⛔ Снова нажмите «Установить на Да». Состояние не изменилось, несмотря на то, что компонент запускает процесс отрисовки, вызывая функцию. Тем не менее, React отказывается от рендеринга где-то в середине процесса, и эффекты не вызываются.
- ✅ Последующие нажатия на «Установить в Да» не вызывают повторного рендеринга (вызовов функций), как ожидалось.
Вопрос
Почему компонент все еще перерисовывается? Я что-то делаю не так или это ошибка React?
setState
с тем же значением - это просто оптимизация производительности, а не семантическая гарантия ... Я не видел исходный код, но первым делом я предполагаю, что откажусь, еслиsetState
вызывается из стека вызовов функций, вложенных вuseEffect
(но это может быть совершенно другая причина) - person Aprillion   schedule 27.03.2020