使用 useReducer 和 React Context API 时 Reducer 运行 两次

Reducer running twice when using useReducer and React Context API

我有点 运行 在使用 React Context API 以及 useReducer 和 useContext 挂钩的奇怪情况下,我的头撞到了墙上。

这是我的主要商店。

import React, { createContext, useReducer } from 'react';
import { Reducers } from './Reducers';
import { surveys } from '../../testData.json';

export const context = createContext();
const { Provider } = context;

export const Store = ({ children }) => {
  const [store, dispatch] = useReducer(Reducers, {
    editSurvey: { display: false },
    surveys,
  });
  return <Provider value={{ store, dispatch }}>{children}</Provider>;
};

这是我的 Reducers 函数和我的 Action 类型:

import { Actions } from './Actions';
const { DISPLAY_NEW_SURVEY, HIDE_SURVEY } = Actions;
export const Reducers = (state, action) => {
  const { type, payload } = action;
  console.log(state, action);
  switch (type) {
    case DISPLAY_NEW_SURVEY:
      return { ...state, editSurvey: { display: true } };
    case HIDE_SURVEY:
      return { ...state, editSurvey: { display: false } };
    default:
      return state;
  }
};
export const Actions = {
  DISPLAY_NEW_SURVEY: 'DISPLAY_NEW_SURVEY',
  HIDE_SURVEY: 'HIDE_SURVEY',
};

我的编辑 属性 中有一个显示 属性,用于有条件地呈现 React 门户,见下文:

import React, { useContext } from 'react';
import { EditSurveyPortal } from '../EditSurvey/EditSurveyPortal';
import { context } from '../../store/Store';

export const NavItem = ({ name, NavImage }) => {
  const { dispatch } = useContext(context);
  return (
    <div
      id={name}
      style={{ cursor: 'pointer' }}
      onClick={() => {
        dispatch({ type: 'DISPLAY_NEW_SURVEY' });
      }}
    >
      <NavImage alt={name} width={10} height={10} />
      <EditSurveyPortal />
    </div>
  );
};
import React, { useContext } from 'react';
import { createPortal } from 'react-dom';
import { context } from '../../store/Store';
import { EditSurvey } from './EditSurvey';

export const EditSurveyPortal = () => {
  const {
    store: {
      editSurvey: { display },
    },
    dispatch,
  } = useContext(context);
  return display
    ? createPortal(
        <div className="absolute top-0 left-0 w-screen h-screen z-10 flex justify-center items-center bg-gray-400 bg-opacity-50">
          <EditSurvey />
        </div>,
        document.getElementById('root'),
      )
    : null;
};

这里是实际的编辑调查组件:

import React from 'react';
import { Card, CardHeader } from '../Utility/Card';
import { Close } from '../Images/Close';

export const EditSurvey = ({ dispatch }) => {
  return (
    <Card>
      <CardHeader className="flex justify-between align-center">
        <div className="inline-block relative rounded">
          <span className="absolute top-0 l-0 bg-gray-200 text-gray-800 rounded-l px-2 py-1">
            Title
          </span>
          <input type="text" className="rounded p-1" />
        </div>
        <div
          className="text-gray-800"
          style={{ cursor: 'pointer' }}
          onClick={() => {
            dispatch({ type: 'HIDE_SURVEY' });
          }}
        >
          <Close width={8} height={8} />
        </div>
      </CardHeader>
    </Card>
  );
};

我的问题是,当我单击门户上的关闭按钮时,它会调度 HIDE_SURVEY 然后立即调度 DISPLAY_NEW_SURVEY:

我这辈子都弄不明白。任何帮助将不胜感激。

谢谢!

事件冒泡,伙计。

这里的问题是您的 EditSurvey 在 NavItem 的可点击区域下方。 这里发生的事情是,当您在 EditSurvey 中单击 div 时,它会首先注册对 div 的单击,然后冒泡到您的 NavItem 可单击 div。这基本上就是所谓的 event-bubbling

您可以了解事件冒泡和捕获here

另外,你也可以检查事件冒泡问题here

为了阻止事件冒泡,你可以简单地停止事件的传播。

<div
    className="text-gray-800"
    style={{ cursor: 'pointer' }}
    onClick={(event) => {
      event.stopPropagation();
      dispatch({ type: 'HIDE_SURVEY' });
    }}
  >
    <Close width={8} height={8} />
  </div>