使用 react-router 从 this.props.children 访问 React 组件

Posted

技术标签:

【中文标题】使用 react-router 从 this.props.children 访问 React 组件【英文标题】:Access React component from this.props.children with react-router 【发布时间】:2016-08-18 10:11:09 【问题描述】:

我有一个带有以下react-routerreact-router-relay 设置的网站:

main.jsx:

<Router history=browserHistory render=applyRouterMiddleware(useRelay)>
  <Route path="/:clas-s-room_id" component=mainView queries=Clas-s-roomQueries>
    <IndexRoute component=Start queries=Clas-s-roomQueries />
    <Route path="tasks" component=TaskRoot queries=Clas-s-roomQueries>
      <IndexRoute component=TaskList queries=Clas-s-roomQueries />
      <Route path="task/:task_id" component=TaskView queries=Clas-s-roomTaskQueries />
      <Route path="task/:task_id/edit" component=TaskEdit queries=Clas-s-roomTaskQueries />
    </Route>
  </Route>
</Router>

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

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

为此,我创建了一个 bootstrap alert 组件 (&lt;Alert&gt;)。

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

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>

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

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

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

    用户将编辑保存到&lt;TaskEdit&gt; 中的任务。 编辑被保存,用户返回&lt;TaskList&gt;。 紧接着,我希望&lt;Alert&gt; 触发并显示一条消息。

我看到其他解决方案建议您可以克隆 React 元素的所有子元素并应用打开 alert 所需的道具,但由于 react-router 的性质,这似乎并不真正起作用。

我怎样才能完成上述任务?有没有办法让组件可以全局访问?请注意,我不想在每个组件中重新创建 &lt;Alert&gt;。对于整个 DOM,只有 一个 这样的组件。

【问题讨论】:

你听说过 Redux 吗? (github.com/reactjs/react-redux) 它可以帮助您以良好的模式组织代码 【参考方案1】:

简单回答:

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

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

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

更高级的回答:

使用 React 助焊剂库https://facebook.github.io/flux/docs/overview.html

【讨论】:

一开始我其实是在考虑做这样的事情。不是最漂亮的解决方案,但我想除非你使用某种库,否则没有一个。非常感谢!【参考方案2】:

一个简单的解决方案是使用context feature:

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 时才会出现此问题。

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

下面的示例代码为单个孩子创建了这样一个条件渲染器。为了有条件地渲染多个孩子,您需要将其包装到一个额外的 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 代码。

【讨论】:

感谢您的意见!我很欣赏许多不同的方法,虽然它们是完全合法的解决方案并且解释得很好,但我觉得它们有点太复杂和/或有很多缺点。我想这就是 React 的样子。此外,context 似乎将来可能会轮换,所以我想也是这样。

以上是关于使用 react-router 从 this.props.children 访问 React 组件的主要内容,如果未能解决你的问题,请参考以下文章

从 URL 中删除 '%20' - React-Router

React-Router:无法从 / 重定向到 /home

如何使用链接(react-router)将道具从一个页面传递到类组件

从共享组件库导出`react-router`重定向

react-router:从组件或存储更改路由

React-Router:将我的 url 从 hashHistory 重定向到 browserHistory