7

我有一个包含以下内容react-routerreact-router-relay设置的网站:

主.jsx:

<Router history={browserHistory} render={applyRouterMiddleware(useRelay)}>
  <Route path="/:classroom_id" component={mainView} queries={ClassroomQueries}>
    <IndexRoute component={Start} queries={ClassroomQueries} />
    <Route path="tasks" component={TaskRoot} queries={ClassroomQueries}>
      <IndexRoute component={TaskList} queries={ClassroomQueries} />
      <Route path="task/:task_id" component={TaskView} queries={ClassroomTaskQueries} />
      <Route path="task/:task_id/edit" component={TaskEdit} queries={ClassroomTaskQueries} />
    </Route>
  </Route>
</Router>

该站点是一个虚拟教室,教师可以在其中为学生创建任务。这些任务也可以编辑。

当用户在其中编辑任务TaskEdit并点击保存时,将应用更改并将用户重定向到TaskList. 现在,我想在编辑任务后给用户一个“您的更改已保存! ”消息。

为此,我创建了一个引导警报组件 ( )。<Alert>

出于说明目的,假设我有以下mainView组件(检查上面的路由器):

主视图.js:

render() {
  <div>
    <Menu />
    <Row>
      <Col md={3}>
        <Sidebar />
      </Col>
      <Col md={9}>
        {this.props.children}  <-- Start, TaskRoot, etc go here
      </Col>
    </Row>
    <Alert someProps={...} />  <-- Popup component
  </div>
}

通常,我会在活动组件之外创建此警报组件,然后将某种将警报显示为道具的函数传递给活动组件。类似于此处显示的内容。

但是,这仅适用于特定组件。但是因为<Alert>位于我页面的根级别(以及菜单栏、侧边栏和所有页面上的其他静态内容),我不能将其作为道具传递,因为{this.props.children}.

请允许我再次说明我想要实现的流程:

  1. 用户将对任务的编辑保存在<TaskEdit>.
  2. 编辑被保存,用户返回到<TaskList>.
  3. 紧接着,我想<Alert>触发并显示一条消息。

我见过其他解决方案,建议您可以克隆 React 元素的所有子元素并应用alert打开react-router.

我怎样才能完成上述工作?有没有办法让组件可以全局访问?请注意,我不想<Alert>在每个组件中重新创建。整个 DOM只有一个这样的组件。

4

2 回答 2

3

简单的回答:

将您的 Alert 组件放在顶层视图中,然后使用一些 pubsub 或事件总线库对其进行更新。

在您的警报组件 onComponentDidMount 函数中,您侦听特定事件和数据并相应地更新其状态(可见性和消息)

在您的任务保存功能中,您将事件发布到事件总线。

更高级的答案:

使用反应通量库https://facebook.github.io/flux/docs/overview.html

于 2016-04-28T13:06:38.777 回答
2

一个简单的解决方案是使用该context功能

const MainView = React.createClass({

  childContextTypes: {
    triggerAlert: PropTypes.func.isRequired,
  },

  getChildContext() {
    return {
      triggerAlert: alert => this.setState({ alert }),
    };
  },

  getInitialState() {
    return {
      alert: null,
    };
  },

  render() {
    return (
      <div>
        {this.props.children}
        <Alert alert={this.state.alert} />
      </div>
    };
  },

});

const AnyChild = React.createClass({

  contextTypes: {
    triggerAlert: PropTypes.func.isRequired,
  },

  handleClick() {
    this.context.triggerAlert('Warning: you clicked!');
  },

  render() {
    return <button onClick={this.handleClick} />
  },

});

但是,这与命令式 API 相关联。这也意味着产生警报的所有子组件必须重新实现一些样板。

您还可以将与警报渲染相关的所有内容移动到它们自己的组件中,将您的实现隐藏在更简单的 API 下:

const AlertRenderer = React.createClass({

  childContextTypes: {
    addAlert: PropTypes.func.isRequired,
  },

  getChildContext() {
    return {
      addAlert: this.addAlert,
    };
  },

  getInitialState() {
    return {
      alerts: [],
    };
  },

  addAlert(alert) {
    this.setState(({ alerts }) =>
      ({ alerts: alerts.concat([alert]) })
    );

    const remove = () => {
      this.setState(({ alerts }) =>
        ({ alerts: alerts.splice(alerts.indexOf(alert), 1) })
      );
    };

    const update = () => {
      this.setState(({ alerts }) =>
        ({ alerts: alerts.splice(alerts.indexOf(alert), 1, alert) })
      );
    };

    return { remove, update };
  },

  render() {
    return (
      <div>
        {this.props.children}
        {this.state.alerts.map(alert =>
          <this.props.component
            key={alert} // Or whatever uniquely describes an alert
            alert={alert}
          />
        )}
      </div>
    );
  },

});

const Alert = React.createClass({

  contextTypes: {
    addAlert: PropTypes.func.isRequired,
  },

  propTypes: {
    alert: PropTypes.any.isRequired,
  },

  componentWillMount() {
    const { update, remove } = this.context.addAlert(this.props.alert);
    this.updateAlert = update;
    this.removeAlert = remove;
  },

  componentWillUnmount() {
    this.removeAlert();
  },

  componentWillReceiveProps(nextProps) {
    this.updateAlert(nextProps.alert);
  },

  render() {
    return null;
  },

});

然后您的应用程序代码变为:

const AnyChild = React.createClass({

  getInitialState() {
    return {
      alert: null,
    };
  },

  handleClick() {
    this.setState({
      alert: 'Warning: you clicked the wrong button!',
    });
  },

  render() {
    return (
      <div>
        <button onClick={this.handleClick}>

        </button>
        {this.state.alert &&
          <Alert alert={this.state.alert} />
        }
      </div>
    );
  },

});

const MainView = React.createClass({

  render() {
    return (
      <AlertRenderer component={MyAlertComponent}>
        {this.props.children}
      </AlertRenderer>
    );
  },

});

这种特定方法的一个问题是,每当更新警报时,AlertRenderer 都会重新渲染其所有子项。当 AlertSub 的 componentWillReceiveProps 生命周期再次触发时,这可能会导致无限递归。请注意,仅当您的组件具有可变道具且未实现时才会出现此问题shouldComponentUpdate

解决此问题的一种简单方法是创建一个条件子渲染器,它仅在检测到其子渲染器已更改时才更新。

下面的示例代码为单个 child创建了这样一个条件渲染器。为了有条件地渲染多个孩子,您需要将其包装到一个额外的 DOM 组件中。

const ShouldSingleChildUpdate = React.createClass({

  shouldComponentUpdate(nextProps) {
    return nextProps.children !== this.props.children;
  },

  render() {
    return this.props.children;
  },

});

const AlertRenderer = React.createClass({

  /* ... */

  render() {
    return (
      <div>
        <ShouldSingleChildUpdate>
          {this.props.children}
        </ShouldSingleChildUpdate>
        {this.state.alerts.map(alert =>
          <this.props.component
            key={alert} // Or whatever uniquely describes an alert
            alert={alert}
          />
        )}
      </div>
    );
  },

});

请注意,这种方法适用于任何类型的模态,其中可以随时在屏幕上显示多个模态。在您的情况下,由于您在任何时候都应该只有一个Alert组件在屏幕上,您可以AlertRenderer通过仅在状态中存储一个警报来简化代码。

于 2016-04-28T13:29:49.340 回答