使用 Context API 在非嵌套组件之间传递变量

Passing a variable between non-nested components using Context API

假设我有两个未嵌套的组件:一个按钮和一个面板。单击按钮时,面板将根据之前的状态显示或隐藏(如 on/off 开关)。它们不是嵌套组件,因此结构如下所示:

<div>
    <Toolbar>
        <Button />
    </Toolbar>
    <Content>
        ...
        <ButtonPanel />
    </Content>
</div>

我无法更改 DOM 的结构。除了按钮和面板组件之外,我也无法修改任何其他组件。

但是,ButtonButtonPanel 组件是相关的,并且将在整个解决方案中一起使用。我需要将 属性 传递给面板,让它知道何时显示或何时隐藏。我正在考虑使用 Context API 来做这件事,但我认为我做错了什么,属性 永远不会更新。

这是我的代码:

上下文

import React from 'react';

export const ButtonContext = React.createContext({
  showPanel: false,
});

按钮

import React, { Component } from 'react';
import { ButtonContext } from './ButtonContext';

class Button extends Component {
  constructor() {
    super();
    this.state = {
      showPanel: false,
    };
  }

  render() {
    return (
      <ButtonContext.Provider value={{ showPanel: this.state.showPanel }}>
        <li>
          <a
            onClick={() => this.setState({ showPanel: !this.state.showPanel }, () => console.log('Changed'))}
          >
            <span>Button</span>
          </a>
        </li>
      </ButtonContext.Provider>
    );
  }
}

export { Button };

面板

import React, { Component } from 'react';
import { Panel, ListGroup, ListGroupItem } from 'react-bootstrap';
import { ButtonContext } from './ButtonContext';

class ButtonPanel extends Component {
  static contextType = ButtonContext;
  render() {
    return (
      <ButtonContext.Consumer>
        {
          ({ showPanel }) => {
            if (showPanel) {
              return (
                <Panel id="tasksPanel">
                  <Panel.Heading >Panel Heading</Panel.Heading>
                  <ListGroup>
                    <ListGroupItem>No Items.</ListGroupItem>
                  </ListGroup>
                </Panel>
              );
            }
            return null;
          }
        }
      </ButtonContext.Consumer>
    );
  }
}

export { ButtonPanel };

我也试过像这样简单地访问 ButtonPanel 组件中的上下文:

render() {
    const context = this.context;
    return context.showPanel ?
      (
        <Panel id="tasksPanel">
          <Panel.Heading >Tasks</Panel.Heading>
          <ListGroup>
            <ListGroupItem className="tasks-empty-state">No tasks available.</ListGroupItem>
          </ListGroup>
        </Panel>
      )
      :
      null;
  }

我做错了什么?

谢谢

来自 React docs:

Accepts a value prop to be passed to consuming components that are descendants of this Provider.

所以这意味着 <ButtonContext.Provider> 必须包装 <ButtonContext.Consumer> 或者它必须在组件层次结构中更高。

因此,根据您的用例,您可以这样做:

// This app component is the div that wraps both Toolbar and Content. You can name it as you want
class App extends Component {
  state = {
    showPanel: false,
  }

  handleTogglePanel = () => this.setState(prevState => ({ togglePanel: !prevState.togglePanel }));

 render() {
   return (
     <ButtonContext.Provider value={{ showPanel: this.state.showPanel, handleTogglePanel: this.handleTogglePanel }}>
       <Toolbar>
         <Button />
       </Toolbar>
       <Content>
         <ButtonPanel />
       </Content>
     </ButtonContext.Provider>
   );
 }
}

class Button extends Component {
  ...
    <ButtonContext.Consumer>
      {({ handleTogglePanel }) => <a onClick={handleTogglePanel} />}
    </ButtonContext.Consumer>
}

class ButtonPanel extends Component {
  ...
    <ButtonContext.Consumer>
      {({ showPanel }) => showPanel && <Panel>...</Panel>}
    </ButtonContext.Consumer>
}