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

时间:2020-09-20 16:46:02

标签: javascript reactjs use-effect react-context

我试图了解Context API如何与React钩子(特别是useEffectuseReducer)一起使用。
我不确定为什么我的一个组件(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控制台中观察第二个输出。

2 个答案:

答案 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上尝试并修改了您的沙箱。因此,如果您希望最大程度地减少不必要的调用和在应用程序上的渲染,可以进行这些检查。