Контекст React не передается при использовании компонента в качестве prop

Я использую ModalTrigger react-bootstrap для показа модального модального поля (на основе модального react-bootstrap), что означает отправку ему кучу реквизита:

<ModalTrigger modal={<MyModal field1={value1} field2={value2} (more fields...)/>}>
    Click here to open
</ModalTrigger>

родительский компонент, который создает триггер, имеет поля/значения, переданные через реквизит, и родительский компонент это компонент также прошел как реквизит, компонентом верхнего уровня, который фактически содержит данные. Оба являются в основном трубами, что является классическим сценарием childContext, за исключением что это не работает. Вот упрощенная версия того, что я пробовал:

var MyModal = React.createClass({
    contextTypes : {foo : React.PropTypes.string},
    render : function() {
        return (
            <Modal {...this.props} title="MyTitle">
                <div className="modal-body">
                    The context is {this.context.foo}
                </div>
            </Modal>
        );
    }
});

var Content = React.createClass({
    childContextTypes : {foo: React.PropTypes.string},
    getChildContext : function() {return {foo : "bar"}},
    render : function() {
        return (
            <ModalTrigger modal={<MyModal/>}>
                <span>Show modal</span>
            </ModalTrigger>
        )
    }
});

модальный всплывает с "контекстом", не показывая фактический контекст.

Я считаю, что это происходит потому, что пропеллер, отправленный в ModalTrigger, уже каким-то образом визуализирован/смонтирован, но я не уверен, почему. Насколько я понимаю, владелец MyModal является компонентом контента, что означает, что контекст должен быть в порядке, но это не так.

некоторые более информация: я уже пробовал пройти {...this.props} и context={this.context} в MyModal без успеха. Кроме того, возможно, уместно, ModalTrigger использует cloneElement, чтобы убедиться, что опора onrequesthide модального указывает на функцию скрытия триггера.

так что я упускаю здесь? :/

2 ответов


React.cloneElement изменит владельца элемента, когда ref prop переопределен, что означает, что контекст не будет передан от предыдущего владельца. однако, похоже, это не так с ModalTrigger.

обратите внимание, что подход на основе владельца не будет работать в React 0.14, так как контекст будет передаваться от родителя к ребенку, а не от владельца к владельцу. ModalTrigger оказывает modal узел prop в другой ветви DOM (см. OverlayMixin). Таким образом, ваш Modal компонент не является ни дочерним, ни потомком вашего Content компонент и не будет передан дочерний контекст из Content.

что касается решения вашей проблемы, вы всегда можете создать компонент, единственной целью которого является передача контекста его детям.

var PassContext = React.createClass({
  childContextTypes: {
    foo: React.PropTypes.string
  },

  getChildContext: function() {
    return this.props.context;
  },

  render: function() {
    return <MyModal />;
  },
});

использовать:

<ModalTrigger modal={<PassContext context={this.getChildContext()}/>}>

как намекнул Мэтт Смит, оказывается, что react-bootstrap уже включает в себя очень похожий подход к контексту пересылки via ModalTrigger.withContext. Это позволяет создать ModalTrigger класс компонентов, который будет пересылать свой контекст в его modal node prop, независимо от его положения в дереве VDOM.

// MyModalTrigger.js
module.exports = ModalTrigger.withContext({
  foo: React.PropTypes.String
});

существует гораздо лучший способ передачи контекста на компоненты типа "портал", которые отображают своих детей в другой контейнер вне дерева React.

использование" renderSubtreeIntoContainer "вместо" render " также передаст контекст в поддерево.

его можно использовать так:

import React, {PropTypes} from 'react';
import {
    unstable_renderSubtreeIntoContainer as renderSubtreeIntoContainer,
    unmountComponentAtNode
} from 'react-dom';

export default class extends React.Component {
  static displayName = 'ReactPortal';

  static propTypes = {
    isRendered: PropTypes.bool,
    children: PropTypes.node,
    portalContainer: PropTypes.node
  };

  static defaultProps = {
    isRendered: true
  };

  state = {
    mountNode: null
  };

  componentDidMount() {
    if (this.props.isRendered) {
      this._renderPortal();
    }
  }

  componentDidUpdate(prevProps) {
    if (prevProps.isRendered && !this.props.isRendered ||
      (prevProps.portalContainer !== this.props.portalContainer &&
         prevProps.isRendered)) {
      this._unrenderPortal();
    }

    if (this.props.isRendered) {
      this._renderPortal();
    }
  }

  componentWillUnmount() {
    this._unrenderPortal();
  }

  _getMountNode = () => {
    if (!this.state.mountNode) {
      const portalContainer = this.props.portalContainer || document.body;
      const mountNode = document.createElement('div');
      portalContainer.appendChild(mountNode);
      this.setState({
        mountNode
      });

      return mountNode;
    }

    return this.state.mountNode;
  };

  _renderPortal = () => {
    const mountNode = this._getMountNode();
    renderSubtreeIntoContainer(
      this,
      (
        <div>
          {this.props.children}
        </div>
      ),
      mountNode,
    );
  };

  _unrenderPortal = () => {
    if (this.state.mountNode) {
      unmountComponentAtNode(this.state.mountNode);
      this.state.mountNode.parentElement.removeChild(this.state.mountNode);
      this.setState({
        mountNode: null
      });
    }
  };

  render() {
    return null;
  }
};

Это пример портала, который я использую в my производства приложение Casalova которые правильно отображают контекст в их дети.

Примечание: этот API недокументирован и, вероятно, изменится в будущем. На данный момент, однако, это правильный способ визуализации контекста в компоненты портала.