繁体   English   中英

为什么我的组件在使用 React 的 Context API 和 useEffect 钩子时渲染了两次?

[英]Why does my component render twice when using React's Context API and the useEffect hook?

我试图了解 Context API 如何与 React 钩子(特别是useEffectuseReducer )一起工作。
我不确定为什么我的一个组件 ( 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>
  );
}

这是在 CodeSandbox 中运行的应用程序

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 中重现这一点:

  • 单击Add Contact链接并观察 CodeSandbox 控制台中的第一个输出。
  • 单击Contacts List链接并观察 CodeSandbox 控制台中的第二个输出。

这是因为当您更改路线时,您的组件会再次安装。 因此,当您从contact listadd 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM