如何解决为什么在使用React的Context API和useEffect钩子时我的组件呈现两次?
我试图了解Context 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>
);
}
这里是the app running in a 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
语句仅触发一次,因为contacts数组没有改变。
我整个下午都在用Google搜索。在SO上找不到的其他答案对我没有帮助。
所以,我的问题是:为什么console.log
语句总是触发两次?
要在CodeSandbox中重现此内容,请执行以下操作:
- 单击添加联系人链接,并观察CodeSandbox控制台中的第一个输出。
- 单击联系人列表链接,并在CodeSandbox控制台中观察第二个输出。
解决方法
这是因为更改路线后,组件将再次安装。因此,当您从contact list
转到add contact
时:
您的减速器已将其设置为name:bob
现在再次回到component list
组件。由于useContext
的初始状态(挂钩也会引起重新渲染),正在呈现两件事情,而由于useEffect
导致了另一件事,即,在安装组件时正在分发FETCH_CONTACTS
。
如果您在第一次加载时看到控制台:
Object {contacts: Array[0]}
Object {contacts: Array[1]}
Observe控制台返回两个语句,第一个状态是联系人为空时,第二个状态是使用reducer更新联系人]
现在,当您来自添加联系人页面时,控制台将显示以下内容:
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]}
由于您仍在应用程序中,因此在组件的第一个渲染时调度的操作将保存在状态中。由于您已经导航了组件。即组件不再位于dom中,因此当您再次在dom中加载组件时,因此在初始渲染之后,useEffect
会发生,并在效果完成后渲染您的组件,因为即使您将另一个动作分派给reducer,即使您拥有价值。
由于调度了新操作,因此将其视为状态更改,即当您看到以下内容时:
Object {contacts: Array[1]} Object {contacts: Array[1]}
您可以通过在减速器的这一行添加console.log
来对此进行测试:
现在,您可以添加诸如检查或标记之类的内容,以确认数据已加载并且无需再次进行提取。
因此,我在您的useEffect
中添加了以下支票:
if (state.contacts.length === 0) {
fetchData();
}
还有尤里卡!!!少渲染一个。
我已经在link上尝试并修改了您的沙箱。因此,如果您希望最大程度地减少不必要的调用和在应用程序上的渲染,可以进行这些检查。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。