我试图了解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中重现此内容,请执行以下操作:
答案 0 :(得分:1)
这是因为更改路线后,组件将再次安装。因此,当您从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
答案 1 :(得分:1)
因此,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上尝试并修改了您的沙箱。因此,如果您希望最大程度地减少不必要的调用和在应用程序上的渲染,可以进行这些检查。