Происходит две вещи:
getBooks()
использует const
значения, которые определены в окружающей функции. Когда функция ссылается на переменные const
или let
вне ее определения, она создает то, что называется замыканием . Замыкания берут значения из этих внешних переменных и дают внутренним функциям копии значений как они были, когда функция была построена. В этом случае функция была построена сразу после первоначального вызова состояния, с loadMoreClicked
установлен на false
.
Так почему же setLoadMore(true)
не вызвал повторное рендеринг и не переписал функцию? Когда мы устанавливаем состояние, повторное отображение не происходит мгновенно. Он добавляется в очередь, которой управляет React. Это означает, что при выполнении loadMore()
, setLoadMore(true)
говорит: «обновите состояние после того, как я закончу выполнять остальную часть кода». Повторное отображение происходит после окончания функции, поэтому используемая копия getBooks()
- это копия, созданная и поставленная в очередь в этом цикле, со встроенными исходными значениями.
Для чего вы Вы можете захотеть, чтобы в вашем тайм-ауте вызывались разные функции, в зависимости от того, была ли нажата кнопка. Или вы можете создать другое, более немедленное закрытие, в зависимости от того, хотите ли вы getBooks()
считать кнопку нажатой или нет, например:
const getBooks = wasClicked => // Now calling getBooks(boolean) returns the following function, with wasClicked frozen
() => {
const endPoint = `http://localhost/getBooks`;
axios
.get(endPoint, {
params: newFilters
})
.then(res => {
console.log(wasClicked); // This references the value copied when the inner function was created by calling getBooks()
})
.catch(err => {
console.log(err);
});
}
...
const loadMore = () => {
setLoadMore(true);
setTimeout(
getBooks(true), // Calling getBooks(true) returns the inner function, with wasClicked frozen to true for this instance of the function
5000
);
};
Существует третий вариант, который переписывает const [loadMoreClicked, setLoadMore]
до var [loadMoreClicked, setLoadMore]
. Если ссылка на переменные const
замораживает значение в этот момент, var
- нет. var
позволяет функции динамически ссылаться на переменную, так что значение определяется при выполнении функции, а не когда она была определена.
Это звучит как быстрое и простое исправление, но может вызвать путаницу при использовании в закрытии, таком как второе решение выше. В этой ситуации значение снова фиксируется из-за того, как работают замыкания. Таким образом, в вашем коде значения будут заморожены в замыканиях, но не в обычных функциях, что может привести к еще большей путанице в будущем.
Моя личная рекомендация - сохранить определения const
. var
используется сообществом разработчиков менее часто из-за путаницы в том, как это работает в замыканиях по сравнению со стандартными функциями. Большинство, если не все крючки, заселяют консты на практике. Использование этого в качестве единственного var
ссылки приведет в замешательство будущих разработчиков, которые, вероятно, сочтут это ошибкой и изменит ее в соответствии с шаблоном, нарушая ваш код.
Если вы хотите динамически ссылаться на состояние loadMoreClicked
, и вам не обязательно нужен компонент для рендеринга, я бы на самом деле рекомендовал использовать useRef()
вместо useState()
.
useRef
для создания объекта с одним свойством, current
, который имеет значение, которое вы вкладываете в него. Когда вы изменяете current
, вы обновляете значение изменяемого объекта. Таким образом, даже если ссылка на объект заморожена во времени, она ссылается на объект, который доступен с самым текущим значением.
Это будет выглядеть так:
function component() {
const loadMoreClicked = useRef(false);
const getBooks = () => {
const endPoint = `http://localhost/getBooks`;
axios
.get(endPoint, {
params: newFilters
})
.then(res => {
console.log(loadMoreClicked.current); // This references the property as it is currently defined
})
.catch(err => {
console.log(err);
});
}
const loadMore = () => {
loadMoreClicked.current = true; // property is uodated immediately
setTimeout(getBooks(), 5000);
};
}
Это работает, потому что в то время как loadMoreClicked
определяется как const
сверху, это постоянная ссылка на объект, не постоянное значение. Однако объект, на который ссылаются, может быть видоизменен вам нравится.
Это одна из самых запутанных вещей в Javascript, и обычно она закрывается в уроках, поэтому, если вы не вступаете с каким-то внутренним опытом с указателями, такими как C или C ++, это будет странно.
Итак, для того, что вы делаете, я бы рекомендовал использовать useRef () вместо useState (). Если вы действительно хотите повторно визуализировать компонент, скажем, если вы хотите отключить кнопку во время загрузки контента, а затем снова включить ее при загрузке контента, я, вероятно, использовал бы оба, и переименовал бы их, чтобы прояснить их назначение :
function component() {
const isLoadPending = useRef(false);
const [isLoadButtonDisabled, setLoadButtonDisabled] = useState(false);
const getBooks = () => {
const endPoint = `http://localhost/getBooks`;
axios
.get(endPoint, {
params: newFilters
})
.then(res => {
if (isLoadPending.current) {
isLoadPending.current = false:
setLoadButtonDisabled(false);
}
})
.catch(err => {
console.log(err);
});
};
const loadMore = () => {
isLoadPending.current = true;
setLoadButtonDisabled(true);
setTimeout(getBooks(), 5000);
};
}
Это немного более многословно, но работает и разделяет ваши проблемы. Ссылка - ваш флаг, чтобы сообщить вашему компоненту, что он делает сейчас. Состояние указывает, как компонент должен отображаться для отражения кнопки.
Установка состояния - операция «забыл и забыл». На самом деле вы не увидите изменений в нем, пока не будет выполнена вся функция вашего компонента. Имейте в виду, что вы получите свое значение, прежде чем сможете использовать функцию setter. Поэтому, когда вы устанавливаете состояние, вы ничего не меняете в этом цикле, вы говорите React запустить другой цикл. Он достаточно умен, чтобы ничего не визуализировать до завершения второго цикла, поэтому он быстрый, но он все еще выполняет два полных цикла, сверху вниз.