[英]React/React Context API: Wait for context values when using useEffect() hook
[英]Why does my component render twice when using React's Context API and the useEffect hook?
我试图了解 Context API 如何与 React 钩子(特别是useEffect
和useReducer
)一起工作。
我不确定为什么我的一个组件 ( ContactListPage
) 似乎渲染了两次。
一个最小的例子来演示:
索引.js
ReactDOM.render(
<ContactContextProvider>
<BrowserRouter>
<App />
</BrowserRouter>
</ContactContextProvider>,
document.getElementById("root")
);
联系上下文.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>
);
};
联系人列表页面.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>
);
}
当ContactListPage
组件首次呈现时,控制台会记录以下内容:
Object {contacts: Array[0]}
Object {contacts: Array[1]}
这对我来说很有意义。 最初contacts
数组是空的,然后在ContactListPage
组件的useEffect
钩子中,我调度了一个操作来添加一个联系人,因此该组件使用更新的联系人列表重新呈现。
当我离开这个视图(我正在使用 React Router),然后我导航回来时,问题就出现了。 在这种情况下,我看到:
Object {contacts: Array[1]}
Object {contacts: Array[1]}
我原以为console.log
语句只触发一次,因为contacts 数组没有改变。
我整个下午都在谷歌上搜索这个。 我在 SO 上找不到的其他答案都对我有用。
所以,我的问题是:为什么console.log
语句总是触发两次?
注意要在 CodeSandbox 中重现这一点:
这是因为当您更改路线时,您的组件会再次安装。 因此,当您从contact list
中add contact
:
您的减速器已将其设置为name:bob
现在再次回到您的component list
组件。 由于useContext
初始状态(钩子也会导致重新渲染),正在发生两件事组件正在渲染,另一useEffect
是由于您的useEffect
引起的,即您在安装组件时分派FETCH_CONTACTS
。
如果您在第一次加载时看到控制台:
Object {contacts: Array[0]}
Object {contacts: Array[1]}
观察控制台返回两个语句,第一个状态是当联系人为空时,第二个状态是当使用减速器更新联系人时]
现在,当您来自添加联系人页面时,控制台会显示以下内容:
Object {contacts: Array[1]}
Object {contacts: Array[1]}
两者都具有相同的值,第一个来自上下文初始状态,第二个再次出现,因为 reducer 再次更新值。
如果您想看到自己,请添加更多控制台以保持清晰。 在此处查看组件是如何安装和卸载的: https ://codesandbox.io/s/elegant-ishizaka-enck6 ? file =/ src/components/ContactListPage.js: 478-492
所以这里的useEffect
实现发生在第一次渲染组件时。 那就是你得到结果的时候:
Object {contacts: Array[0]} Object {contacts: Array[1]}
由于您仍在应用程序中,因此在组件的第一次渲染时分派的操作将保存在状态中。 由于您已经导航离开组件 .ie 组件不再在 dom 中,当您再次在 dom 中加载组件时,因此在初始渲染之后, useEffect
发生并在效果完成后渲染您的组件,因为您正在调度另一个动作减速器,即使你有价值。
由于分派了一个新操作,这被视为状态更改,并且您会看到以下内容:
Object {contacts: Array[1]} Object {contacts: Array[1]}
您可以通过在减速器的这一行添加console.log
来测试:
现在,您可以添加诸如检查或标志之类的内容,以确认数据已加载并且您不必再次进行提取。
所以我在你的useEffect
添加了以下检查:
if (state.contacts.length === 0) {
fetchData();
}
还有尤里卡!!! 少一个渲染。
我已在此链接上尝试并修改了您的沙箱。 因此,如果您想最大程度地减少应用程序上不必要的调用和渲染,最好进行这些检查。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.