#javascript #reactjs #use-effect #react-context
#javascript #reactjs #использование-эффект #реагировать-контекст
Вопрос:
Я пытаюсь понять, как контекстный API работает вместе с хуками React (в частности useEffect
, и useReducer
).
Я не уверен, почему один из моих компонентов ( ContactListPage
), похоже, отображается дважды.
Минимальный пример для демонстрации:
index.js
ReactDOM.render(
<ContactContextProvider>
<BrowserRouter>
<App />
</BrowserRouter>
</ContactContextProvider>,
document.getElementById("root")
);
contact-context.js
export const ContactContext = createContext();
function reducer(state, action) {
switch (action.type) {
case "FETCH_CONTACTS": {
return { ...state, contacts: action.payload };
}
default:
throw new Error();
}
}
export const ContactContextProvider = (props) => {
const [state, dispatch] = useReducer(reducer, { contacts: [] });
const store = React.useMemo(() => ({ state, dispatch }), [state, dispatch]);
return (
<ContactContext.Provider value={store}>
{props.children}
</ContactContext.Provider>
);
};
ContactListPage.js
export default function ContactListPage() {
const { state, dispatch } = useContext(ContactContext);
console.log(state);
useEffect(() => {
const fetchData = async () => {
try {
// Make Ajax request for contacts here
dispatch({
type: "FETCH_CONTACTS",
payload: [{ name: "Bob" }]
});
} catch (error) {
// handle error
}
};
fetchData();
}, []); // eslint-disable-line react-hooks/exhaustive-deps
return (
<div>
<h1>List of Contacts</h1>
</div>
);
}
Вот приложение, запущенное в CodeSandbox.
При первом ContactListPage
рендеринге компонента в консоль записывается следующее:
Object {contacts: Array[0]}
Object {contacts: Array[1]}
Для меня это имеет смысл. Сначала contacts
массив пуст, затем в useEffect
перехвате в ContactListPage
компоненте я отправляю действие для добавления контакта, поэтому компонент повторно отображается с обновленным списком контактов.
Проблема возникает, когда я ухожу от этого представления (я использую React Router), а затем возвращаюсь назад. В этом случае я вижу:
Object {contacts: Array[1]}
Object {contacts: Array[1]}
Я бы ожидал console.log
, что оператор сработает только один раз, поскольку массив контактов не изменился.
Я потратил весь день на поиск в Google. Ни один из других ответов, которые я мог найти на SO, не сработал для меня.
Итак, мой вопрос: почему console.log
оператор всегда запускается дважды?
Примечание. Чтобы воспроизвести это в CodeSandbox:
- Нажмите на ссылку Добавить контакт и просмотрите первый вывод в консоли CodeSandbox.
- Нажмите на ссылку Список контактов и просмотрите второй вывод в консоли CodeSandbox.
Ответ №1:
Таким useEffect
образом, реализация здесь происходит, когда компонент отображается в первый раз. Вот когда вы получаете результат:
Object {contacts: Array[0]} Object {contacts: Array[1]}
Поскольку вы все еще находитесь в приложении, действие, отправленное при первом рендеринге компонента, сохраняется в состоянии. Поскольку вы удалили компонент. т.Е. Компонент больше не находится в dom, когда вы снова загружаете компонент в dom, следовательно, после первоначального рендеринга useEffect
выполняется и отображает ваш компонент после завершения эффекта, потому что вы отправляете другое действие в редуктор, даже если у вас есть значение.
Поскольку отправляется новое действие, это рассматривается как изменение состояния, и именно тогда вы видите следующее:
Object {contacts: Array[1]} Object {contacts: Array[1]}
Вы можете проверить это, добавив console.log
в эту строку в свой редуктор:
Теперь вы можете добавить что-то вроде проверки или флага, которые могут подтвердить, что данные были загружены, и вам не нужно снова выполнять выборку.
Итак, я добавил следующую проверку в вашем useEffect
:
if (state.contacts.length === 0) {
fetchData();
}
И Эврика !!! На один рендеринг меньше.
Я опробовал и модифицировал вашу песочницу по этой ссылке . Поэтому неплохо иметь эти проверки, если вы хотите свести к минимуму ненужный вызов и рендеринг в приложении.
Ответ №2:
Это потому, что ваш компонент снова монтируется при изменении маршрута. Итак, когда вы переходите от contact list
к add contact
:
Ваш редуктор уже установил его на name:bob
И теперь снова, когда вы возвращаетесь к своему component list
компоненту. Происходит две вещи: компонент рендерится из-за useContext
начального состояния (перехваты также вызывают повторный рендеринг), а другая — из-за вашего useEffect
, т.е. Вы отправляете свой FETCH_CONTACTS
, когда компонент смонтирован.
Если вы видите консоль при первой загрузке:
Object {contacts: Array[0]}
Object {contacts: Array[1]}
Обратите внимание, что консоль возвращает два оператора: первое состояние — это когда контакт пуст, а второе состояние — когда контакт обновляется с помощью reducer]
Теперь, когда вы переходите со страницы добавления контакта, консоль показывает это:
Object {contacts: Array[1]}
Object {contacts: Array[1]}
Оба имеют одинаковое значение, первое из которых исходит из начального состояния контекста, а второе приходит снова, поскольку reducer снова обновляет значение.
Если вы хотите увидеть себя, добавьте консоль больше для ясности. Проверьте здесь, как монтируется и размонтируется компонент: https://codesandbox.io/s/elegant-ishizaka-enck6?file=/src/components/ContactListPage.js:478-492