繁体   English   中英

无法访问 redux state 和 Redux 中嵌套组件内的操作

[英]Unable to access redux state and actions inside nested component in Redux

我有一个 NoteItem 组件,它从自身内部的数组递归地呈现自己,但是在最顶层的组件中,当我在顶级父 NoteItem 上使用 redux 函数时,我能够使用操作并访问 state 但在嵌套的 NoteItem 内组件当我尝试使用它时,它是未定义的,我想我们可以在任何我们想使用它的地方使用 redux 并访问 state 和功能

这是父侧边栏组件

Sidebar.js

import React, { Component } from "react";
import { Popover, Tooltip } from "antd";
import { connect } from "react-redux";

import "./sidebar.css";

import settingsIcon from "../icons/settings.svg";

import addIcon from "../icons/plus.svg";

import NoteItem from "../NoteItem/NoteItem";
import { getNotes, createNote } from "../actions/noteActions";
import store from "../store";
import { SET_CONTEXTMENU_VISIBLE, SET_ACTIVE_NOTE } from "../actions/types";

class Sidebar extends Component {
  state = {
    notes: this.props.notes,
    ownUpdate: false,
    xPos: 0,
    yPos: 0,
    mouseDownOnMenu: this.props.mouseDownOnMenu,
    contextMenuVisible: this.props.contextMenuVisible,
  };

  static getDerivedStateFromProps(props, state) {
    if (state.ownUpdate) {
      return {
        ...state,
        ownUpdate: false,
      };
    } else {
      if (props.notes != state.notes) {
        return {
          notes: props.notes,
        };
      }
      if (props.contextMenuVisible != state.contextMenuVisible) {
        return {
          contextMenuVisible: props.contextMenuVisible,
        };
      }
      if (props.mouseDownOnMenu != state.mouseDownOnMenu) {
        return {
          mouseDownOnMenu: props.mouseDownOnMenu,
        };
      }
    }

    return null;
  }

  addNote = () => {
    const newNote = {
      name: "Untitled",
    };

    this.props.createNote(newNote);
  };

  componentDidMount() {
    window.addEventListener("mousedown", (e) => {
      const { mouseDownOnMenu } = this.state;
      if (mouseDownOnMenu) {
        return;
      }
      store.dispatch({
        type: SET_CONTEXTMENU_VISIBLE,
        payload: false,
      });
      store.dispatch({
        type: SET_ACTIVE_NOTE,
        payload: undefined,
      });
    });

    this.props.getNotes();
  }

  componentWillUnmount() {
    window.removeEventListener("mousedown", window);
  }

  addSubNote = (note, path) => {
    this.props.addSubNote(note, path);
  };

  setMouseDownActive = () => {
    this.setState({
      mouseDownOnMenu: !this.state.mouseDownOnMenu,
    });
  };

  render() {
    const { notes, contextMenuVisible, xPos, yPos } = this.state;
    return (
      <div className="sidebar-container">
        <div className="header-container">
          <h4>Username</h4>
          <Tooltip title="Settings" placement="right">
            <img src={settingsIcon} />
          </Tooltip>
        </div>

        <div className="sidebar-content-container">
          <div className="sidebar-controls-container">
            <h4>Notes</h4>
            <div className="add-icon-button" onClick={this.addNote}>
              <img src={addIcon} />
            </div>
          </div>
          <div className="sidebar-page-container">
            {notes &&
              notes.map((note, index) => (
                <NoteItem note={note} key={note._id}></NoteItem> // I can use all the functions and 
 // access the state in these components
              ))}
          </div>
        </div>
      </div>
    );
  }
}

const mapStateToProps = (state) => {
  return {
    notes: state.note.notes,
    contextMenuVisible: state.note.contextMenuVisible,
    mouseDownOnMenu: state.note.mouseDownOnMenu,
  };
};

const mapDispatchToProps = (dispatch) => {
  return {
    createNote: (noteData) => dispatch(createNote(noteData)),
    getNotes: () => dispatch(getNotes()),
  };
};

export default connect(mapStateToProps, mapDispatchToProps)(Sidebar);

这是我的 NoteItem 组件

NoteItem.js

import React, { Component } from "react";
import "./noteitem.css";
import pageIcon from "../icons/file-text.svg";

import { connect } from "react-redux";

import addIcon from "../icons/plus.svg";
import caretIcon from "../icons/play.svg";

import { Popover, Button, Input, Menu } from "antd";
import { addSubNote, deleteNote } from "../actions/noteActions";
import { DeleteOutlined, EditOutlined, LinkOutlined } from "@ant-design/icons";
import {
  SET_ACTIVE_NOTE,
  SET_CONTEXTMENU_VISIBLE,
  SET_MOUSEDOWNON_MENU,
} from "../actions/types";
import axios from "axios";
import apiUrl from "../utils/getApiUrl";
import store from "../store";

class NoteItem extends Component {
  state = {
    subNoteOpen: false,
    contextMenuVisible: this.props.contextMenuVisible,
    xPos: 0,
    yPos: 0,
    deleteNoteActive: false,
    renamePopupVisible: false,
    note: this.props.note,
    ownUpdate: false,
    activeNote: this.props.activeNote,
    subNotes: [],
    mouseDownOnMenu: this.props.mouseDownOnMenu,
  };

  componentDidUpdate(prevProps, prevState, snap) {
    if (
      prevProps.activeNote != this.props.activeNote &&
      this.props.activeNote
    ) {
      this.setState({
        activeNote: this.props.activeNote,
        ownUpdate: true,
      });
    }
  }

  static getDerivedStateFromProps(props, state) {
    if (state.ownUpdate) {
      return {
        ...state,
        ownUpdate: false,
      };
    } else {
      if (props.note != state.note) {
        return {
          note: props.note,
        };
      }
      if (props.contextMenuVisible != state.contextMenuVisible) {
        return {
          contextMenuVisible: props.contextMenuVisible,
        };
      }
      if (props.mouseDownOnMenu != state.mouseDownOnMenu) {
        return {
          mouseDownOnMenu: props.mouseDownOnMenu,
        };
      }
      if (props.activeNote != state.activeNote) {
        return {
          activeNote: props.activeNote,
        };
      }
    }

    return null;
  }

  getSubNotes = async () => {
    try {
      const { note } = this.state;
      if (note) {
        const subNotes = await axios.get(`${apiUrl}/note/sub-notes`, {
          headers: {
            "Content-Type": "application/json",
          },
          params: {
            id: note._id,
          },
        });
        this.setState({
          subNotes: subNotes.data,
        });
      }
    } catch (err) {
      console.log(err);
    }
  };

  addNewSubNote = async (note) => {
    try {
      const subNote = await axios.post(
        `${apiUrl}/note/subnote/new`,
        {
          note: {
            name: "Untitled",
          },
          path: note.path,
        },
        {
          headers: {
            "Content-Type": "application/json",
          },
        }
      );
      this.setState({
        subNotes: [...this.state.subNotes, subNote.data],
        subNoteOpen: true,
      });
    } catch (err) {
      console.log(err);
    }
  };

  getRenameNoteInput = () => {
    return (
      <div className="rename-note-container">
        <Input placeholder="Enter name" />
        <Button type="primary">Save</Button>
      </div>
    );
  };

  toggleSubNote = () => {
    this.setState(
      {
        subNoteOpen: !this.state.subNoteOpen,
      },
      () => {
        if (this.state.subNoteOpen) {
          this.getSubNotes();
        }
      }
    );
  };

  toggleContextMenu = (e, note) => {
    e.preventDefault();
    var body = document.body,
      html = document.documentElement;
    var height = Math.max(
      body.scrollHeight,
      body.offsetHeight,
      html.clientHeight,
      html.scrollHeight,
      html.offsetHeight
    );
    if (e.pageY > height - 100) {
      this.setState(
        {
          xPos: e.pageX,
          yPos: e.pageY - 130,
        },
        () => {
          store.dispatch({
            type: SET_ACTIVE_NOTE,
            payload: note,
          });
        }
      );
    } else {
      this.setState(
        {
          xPos: e.pageX,
          yPos: e.pageY,
        },
        () => {
          store.dispatch({
            type: SET_ACTIVE_NOTE,
            payload: note,
          });
        }
      );
    }
    store.dispatch({
      type: SET_CONTEXTMENU_VISIBLE,
      payload: true,
    });
  };

  deleteNote = ({ domEvent }) => {
    domEvent.preventDefault();
    store.dispatch({
      type: SET_MOUSEDOWNON_MENU,
      payload: true,
    });
    store.dispatch({
      type: SET_CONTEXTMENU_VISIBLE,
      payload: false,
    });
    const { note } = this.state;
    console.log(this.props);
    this.props.deleteNote(note);
  };

  setMouseDownActive = () => {
    store.dispatch({
      type: SET_MOUSEDOWNON_MENU,
      payload: !this.state.mouseDownOnMenu,
    });
  };

  render() {
    const {
      subNoteOpen,
      note,
      renamePopupVisible,
      activeNote,
      subNotes,
      contextMenuVisible,
      xPos,
      yPos,
    } = this.state;
    return (
      <div style={{ width: "100%" }}>
        {activeNote && contextMenuVisible && activeNote._id == note._id && (
          <div
            className="context-menu"
            style={{ position: "fixed", top: yPos, left: xPos }}
          >
            <Menu
              style={{ width: "200px", borderRadius: "3px" }}
              onMouseDown={this.setMouseDownActive}
              onMouseUp={this.setMouseDownActive}
            >
              <Menu.Item icon={<EditOutlined />}>Rename</Menu.Item>
              <Menu.Item icon={<LinkOutlined />}>Copy Link</Menu.Item>
              <Menu.Item icon={<DeleteOutlined />} onClick={this.deleteNote}>
                Delete
              </Menu.Item>
            </Menu>
          </div>
        )}
        <Popover
          content={this.getRenameNoteInput()}
          placement="bottom"
          overlayClassName="no-arrow"
          visible={renamePopupVisible}
        >
          <div
            className={
              activeNote && activeNote._id == note._id
                ? "page-item page-item-active"
                : "page-item"
            }
            onContextMenu={(e) => this.toggleContextMenu(e, note)}
          >
            <img
              src={caretIcon}
              onClick={this.toggleSubNote}
              className={subNoteOpen ? "caret-open" : "caret"}
            />
            <img src={pageIcon} />

            <span>{note.name}</span>
            <div
              className="add-page-button"
              onClick={() => this.addNewSubNote(note)}
            >
              <img src={addIcon} />
            </div>
          </div>
        </Popover>
        <div
          className={
            subNoteOpen
              ? "sub-page-container subpage-open"
              : "sub-page-container"
          }
        >
          {subNotes.length > 0 ? (
            <div>
              {subNotes.map((subNote) => (
                <NoteItem note={subNote} key={subNote._id} /> // I can't use any redux functions or 
 // access redux state in these components
              ))}
            </div>
          ) : (
            <p className="empty-text">Empty</p>
          )}
        </div>
      </div>
    );
  }
}

const mapStateToProps = (state) => {
  return {
    activeNote: state.note.activeNote,
    contextMenuVisible: state.note.contextMenuVisible,
    mouseDownOnMenu: state.note.mouseDownOnMenu,
  };
};

const mapDispatchToProps = (dispatch) => {
  return {
    addSubNote: (note, path) => dispatch(addSubNote(note, path)),
    deleteNote: (note) => dispatch(deleteNote(note)),
  };
};

export default connect(mapStateToProps, mapDispatchToProps)(NoteItem);


您连接的默认导出的 NoteItem 组件接收道具 - 但随后它递归地呈现 NoteItem 的未连接版本。 该版本不知道 redux 商店。

你可以做类似的事情

const ConnectedNoteItem = connect(mapStateToProps, mapDispatchToProps)(
class NoteItem extends Component {
// ... use ConnectedNoteItem in here
})
export default ConnectedNoteItem

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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