为啥我的组件在使用 React 的 Context API 和 useEffect 挂钩时会渲染两次?
Posted
技术标签:
【中文标题】为啥我的组件在使用 React 的 Context API 和 useEffect 挂钩时会渲染两次?【英文标题】:Why does my component render twice when using React's Context API and the useEffect hook?为什么我的组件在使用 React 的 Context API 和 useEffect 挂钩时会渲染两次? 【发布时间】:2021-01-06 21:44:31 【问题描述】:我正在尝试了解 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
数组是空的,然后在ContactListPage
组件的useEffect
钩子中,我调度了一个动作来添加一个联系人,因此该组件使用更新的联系人列表重新渲染。
当我离开这个视图(我正在使用 React 路由器),然后我导航回来时,问题就出现了。在这种情况下,我看到:
Object contacts: Array[1]
Object contacts: Array[1]
我原以为 console.log
语句只会触发一次,因为联系人数组没有改变。
我整个下午都在谷歌上搜索这个。我在 SO 上找到的其他答案都没有对我有用。
那么,我的问题是:为什么console.log
语句总是触发两次?
注意要在 CodeSandbox 中重现此内容:
单击添加联系人链接并观察 CodeSandbox 控制台中的第一个输出。 单击联系人列表链接并观察 CodeSandbox 控制台中的第二个输出。【问题讨论】:
【参考方案1】:所以这里的useEffect
实现发生在第一次渲染组件时。那是你得到结果的时候:
Object contacts: Array[0] Object contacts: Array[1]
由于您仍在应用程序中,因此在第一次渲染组件时调度的操作将保存在状态中。由于您已导航离开组件 .i.e.组件不再在 dom 中,当您在 dom 中再次加载组件时,因此在初始渲染后,useEffect
发生并在效果完成后渲染您的组件,因为您正在向减速器调度另一个动作,即使您有价值。
由于调度了一个新操作,这被视为状态更改,此时您会看到以下内容:
Object contacts: Array[1] Object contacts: Array[1]
您可以通过在减速器的这一行添加console.log
来测试:
现在您可以添加诸如检查或标志之类的内容,以确认数据已加载并且您不必再次进行提取。
所以我在您的useEffect
中添加了以下检查:
if (state.contacts.length === 0)
fetchData();
还有尤里卡!!!少一个渲染。
我已经在link 上试用并修改了您的沙盒。因此,如果您想最大程度地减少不必要的调用和应用程序上的渲染,最好有这些检查。
【讨论】:
【参考方案2】:这是因为当您更改路线时,您的组件再次挂载。所以当你从contact list
转到add contact
时:
您的 reducer 已将其设置为 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
【讨论】:
以上是关于为啥我的组件在使用 React 的 Context API 和 useEffect 挂钩时会渲染两次?的主要内容,如果未能解决你的问题,请参考以下文章