【问题标题】:react-router - pass props to handler componentreact-router - 将道具传递给处理程序组件
【发布时间】:2015-03-08 00:28:00
【问题描述】:

我的 React.js 应用程序使用 React Router 具有以下结构:

var Dashboard = require('./Dashboard');
var Comments = require('./Comments');

var Index = React.createClass({
  render: function () {
    return (
        <div>
            <header>Some header</header>
            <RouteHandler />
        </div>
    );
  }
});

var routes = (
  <Route path="/" handler={Index}>
    <Route path="comments" handler={Comments}/>
    <DefaultRoute handler={Dashboard}/>
  </Route>
);

ReactRouter.run(routes, function (Handler) {
  React.render(<Handler/>, document.body);
});

我想将一些属性传递给Comments 组件。

(通常我会像 &lt;Comments myprop="value" /&gt; 那样做)

使用 React Router 最简单和正确的方法是什么?

【问题讨论】:

  • 这里的问题,在类似的情况下,特别是用一些语言编写的框架或库,一定缺乏组合方式(MoC)。 Primitives 在 React 中似乎还可以,它们非常好,在 React 元素和组件中定义带有基元的组件,MoC 在 React 中似乎也可以。但是组合方式是不完整的。一个人必须能够将props传递给一个组件,同时将一个组件组合到另一个组件,无论是将一个组件作为它的子组件放在另一个组件中还是将一个组件作为props传递给另一个。
  • 使用 &lt;ComponentA x={&lt;ComponentB y={&lt;ComponentC z={} /&gt;} /&gt;} /&gt;&lt;ComponentA x={ComponentB(ComponentC()) } /&gt; 之类的语法 否则,这种抽象组合问题将再次出现,并且需要一些非最佳和间接的解决方案,称为变通方法,如包装等等,等等。抽象必须是作为原语的一等公民,无论一等感知是什么意思。

标签: javascript properties reactjs react-router


【解决方案1】:

如果您不想编写包装器,我想您可以这样做:

class Index extends React.Component { 

  constructor(props) {
    super(props);
  }
  render() {
    return (
      <h1>
        Index - {this.props.route.foo}
      </h1>
    );
  }
}

var routes = (
  <Route path="/" foo="bar" component={Index}/>
);

【讨论】:

  • 这是一个正确的答案。在 react-router 1.0 中,您可以在组件中获取 route 普通对象。这是github问题的答案:github.com/rackt/react-router/issues/615#issuecomment-100432086
  • 这是我一直在寻找的简单答案。其他技术有效,但需要 10 倍的代码量。适用于 v1.0.x。我能看到的唯一缺点是,如果您打算在有和没有路由器容器的情况下使用相同的组件。但对我来说,我所有的顶级组件都与路由一一对应。
  • 太好了,谢谢!如果有人想知道, foo 属性将在您的组件中可用:this.props.route.foo
  • 这个可以设置为正确答案,避免混淆吗?
  • 不!有关真正的解决方案,请参阅 Rajesh Naroth 的答案:)
【解决方案2】:

更新

自新版本以来,可以直接通过 Route 组件传递道具,而无需使用 Wrapper。例如,using render prop

组件:

class Greeting extends React.Component {
  render() {
    const {text, match: {params}} = this.props;

    const {name} = params;

    return (
      <React.Fragment>
        <h1>Greeting page</h1>
        <p>
          {text} {name}
        </p>
      </React.Fragment>
    );
  }
}

用法:

<Route path="/greeting/:name" render={(props) => <Greeting text="Hello, " {...props} />} />

Codesandbox Example


旧版本

我的首选方式是包装 Comments 组件并将包装器作为路由处理程序传递。

这是您应用更改的示例:

var Dashboard = require('./Dashboard');
var Comments = require('./Comments');

var CommentsWrapper = React.createClass({
  render: function () {
    return (
      <Comments myprop="myvalue"/>
    );
  }
});

var Index = React.createClass({
  render: function () {
    return (
      <div>
        <header>Some header</header>
        <RouteHandler/>
      </div>
    );
  }
});

var routes = (
  <Route path="/" handler={Index}>
    <Route path="comments" handler={CommentsWrapper}/>
    <DefaultRoute handler={Dashboard}/>
  </Route>
);

ReactRouter.run(routes, function (Handler) {
  React.render(<Handler/>, document.body);
});

【讨论】:

  • 我也遇到了同样的问题,但是这个解决方案不是很快就会变得冗长吗?
  • 同意 captDaylight,它变得冗长。希望有更好的方法来处理这个!
  • @mattiashallstrom IMO,1.0 中更好的方法是简单地将属性添加到路由中。请参阅 Thomas E 的回答。
  • 你也可以在那里添加一个无状态组件语法(只是 lambda),它很短 &lt;Route path="comments" component={() =&gt; (&lt;Comments myProp="value" /&gt;)}/&gt;
  • 我永远不会同意创建额外的组件只是为了传递属性是“首选方式”。它冗长、复杂、容易出错,而且在所有可以想象的方面都明显是错误的。这可能是反应路由器允许的唯一方式,但称其为“首选”是一种延伸。谁喜欢?
【解决方案3】:

ciantic 在接受的响应中从 cmets 复制:

<Route path="comments" component={() => (<Comments myProp="value" />)}/>

这是我认为最优雅的解决方案。有用。帮助了我。

【讨论】:

  • 这与上面的包装答案基本相同,但它不那么罗嗦。不过,伙计,这种语法很糟糕。尝试输入_ref
  • 它就像一个匿名包装器,所以所有注入的道具(例如位置)都被遗漏了。必须手动传递 component={(props) =&gt; (&lt;Comments myProp="value" location={ props.location } /&gt;)} 之类的道具,但一切又变得一团糟
  • @JacobThomason 我们不会重新渲染反应路由器配置,因此不太可能造成性能损失。
  • 从 React-Router 4 开始,如果你提供了一个内联函数,你会得到很多不需要的重新挂载。对于内联渲染,使用 render 属性。 Link to the docs
  • @yuji 为了不让它太乱,可以这样做:component={(props) =&gt; (&lt;Comments {...props} myProp="value" /&gt;)} 来维护注入的道具
【解决方案4】:

这是 solution from Rajesh,没有不方便的 commented by yuji,并针对 React Router 4 进行了更新。

代码是这样的:

<Route path="comments" render={(props) => <Comments myProp="value" {...props}/>}/>

请注意,我使用render 而不是component。原因是避免undesired remounting。我还将 props 传递给该方法,并在 Comments 组件上使用相同的道具和对象扩展运算符(ES7 提议)。

【讨论】:

  • 真的很棒,也可以解决不受欢迎的安装问题! +1
【解决方案5】:

只是对 ColCh 的回答的跟进。抽象组件的包装很容易:

var React = require('react');

var wrapComponent = function(Component, props) {
  return React.createClass({
    render: function() {
      return React.createElement(Component, props);
    }
  });
};

<Route path="comments" handler={wrapComponent(Comments, {myprop: value})}/>

我尚未测试此解决方案,因此任何反馈都很重要。

请务必注意,使用此方法,通过路由器发送的任何道具(例如参数)都会被覆盖/删除。

【讨论】:

  • Bob,你熟悉闭包吗? stackoverflow.com/questions/111102/…
  • 如果您还需要来自路由器的查询和参数,那么类似的方法将起作用:return React.createElement(Component, _.assign({}, this.props, props));(这个使用 _.assign 组合对象...其他方法是当然可用)。
  • 您可能还想通过孩子们。| var wrapComponent = function(Component, props) { return React.createClass({ render: function() { return React.createElement(Component, props, this.props.children); } }); };
  • 这是一个高阶组件,供不熟悉它们的人使用facebook.github.io/react/docs/higher-order-components.html
  • 注:这适用于旧版本的 React Router。当前的 v4 具有用于 Routerendercomponentchildren 方法。请注意,正如@dgrcode answer 指出的那样,您应该使用render 而不是component
【解决方案6】:

您可以通过将 props 传递给 &lt;RouteHandler&gt;(在 v0.13.x 中)或在 v1.0 中的 Route 组件本身来传递 props;

// v0.13.x
<RouteHandler/>
<RouteHandler someExtraProp={something}/>

// v1.0
{this.props.children}
{React.cloneElement(this.props.children, {someExtraProp: something })}

(来自https://github.com/rackt/react-router/releases/tag/v1.0.0 的升级指南)

所有子处理程序都将收到相同的一组道具 - 这可能有用或无用,具体取决于具体情况。

【讨论】:

  • 看到React.cloneElement 被传递了多个元素确实令人困惑,但function signature 似乎只需要一个反应元素。我认为这个sn-p可以更容易理解。
  • 这显然是文档中针对目标的最佳答案,但我也同意 manu 的观点,即可以编写它以更好地展示用途。更具体的问题代码如下:React.cloneElement(this.props.children, {myprop: "value"})React.cloneElement(this.props.children, {myprop: this.props.myprop}) 等。
  • 这个答案获胜。在路由器为您做的事情中发生了什么也更清楚了。作为阅读代码的人,如果我知道 Comments 在 Index 中,我将查看 Index 以查看发送给 Comments 的道具。如果我碰巧知道 Comments 是一个路由器处理程序,我会查看路由器配置并找出 Index 父 cmets 并仍然去那里查看。
【解决方案7】:

使用 ES6,您可以将组件包装器内联:

&lt;Route path="/" component={() =&gt; &lt;App myProp={someValue}/&gt;} &gt;

如果您需要传递孩子:

&lt;Route path="/" component={(props) =&gt; &lt;App myProp={someValue}&gt;{props.children}&lt;/App&gt;} &gt;

【讨论】:

  • 这很好,但它不会通过孩子,以防万一。
  • @zpr 我为 props.children 添加了一个示例
  • 正如@dgrcode answer 指出的那样,您应该使用render 而不是component
【解决方案8】:

反应路由器 v4 alpha

现在有一种新方法可以做到这一点,尽管与以前的方法非常相似。

import { Match, Link, Miss } from 'react-router';
import Homepage from './containers/Homepage';

const route = {
    exactly: true,
    pattern: '/',
    title: `${siteTitle} - homepage`,
    component: Homepage
  }

<Match { ...route } render={(props) => <route.component {...props} />} />

附:这仅适用于 alpha 版本,并在 v4 alpha 版本后被删除。在最新的 v4 中,再次使用路径和确切的道具。

react-lego 一个示例应用程序包含完全执行此操作的代码in routes.js on its react-router-4 branch

【讨论】:

    【解决方案9】:

    这是我想出的最干净的解决方案(React Router v4):

    <Route
      path="/"
      component={props => <MyComponent {...props} foo="lol" />}
    />
    

    MyComponent 仍然有 props.matchprops.location,并且有 props.foo === "lol"

    【讨论】:

      【解决方案10】:

      用无状态函数组件包装它:

      <Router>
        <Route 
          path='/' 
          component={({children}) => 
            <MyComponent myProp={'myVal'}>{children}</MyComponent/>
          }/>
      </Router>
      

      【讨论】:

        【解决方案11】:

        您还可以使用 RouteHandler mixin 来避免包装组件,并更轻松地将父级的状态作为道具传递:

        var Dashboard = require('./Dashboard');
        var Comments = require('./Comments');
        var RouteHandler = require('react-router/modules/mixins/RouteHandler');
        
        var Index = React.createClass({
              mixins: [RouteHandler],
              render: function () {
                var handler = this.getRouteHandler({ myProp: 'value'});
                return (
                    <div>
                        <header>Some header</header>
                        {handler}
                   </div>
                );
          }
        });
        
        var routes = (
          <Route path="/" handler={Index}>
            <Route path="comments" handler={Comments}/>
            <DefaultRoute handler={Dashboard}/>
          </Route>
        );
        
        ReactRouter.run(routes, function (Handler) {
          React.render(<Handler/>, document.body);
        });
        

        【讨论】:

        • 它在全球凉亭构建中公开暴露为 ReactRouter.RouteHandlerMixin 所以我不这么认为。
        • 这也允许使用 TransitionGroup 和 CSSTransitionGroup 设置动画过渡,而我无法使用 wrapper 方法。
        • 奇怪的是官方文档中没有提到这一点。
        • React 文档不再推荐 Mixin:facebook.github.io/react/blog/2016/07/13/…
        【解决方案12】:

        您可以像这样通过&lt;RouterHandler/&gt; 传递道具:

        var Dashboard = require('./Dashboard');
        var Comments = require('./Comments');
        
        var Index = React.createClass({
          render: function () {
            var props = this.props; // or possibly this.state
            return (
                <div>
                    <header>Some header</header>
                    <RouteHandler {...props} />
                </div>
            );
          }
        });
        

        这样做的缺点是你不分青红皂白地传递道具。所以Comments 最终可能会收到真正用于不同组件的道具,具体取决于您的路由配置。这并不是什么大问题,因为props 是不可变的,但是如果两个不同的组件期望一个名为foo 但具有不同值的道具,这可能会出现问题。

        【讨论】:

        【解决方案13】:

        在 1.0 和 2.0 中,您可以使用 RoutercreateElement 属性来指定如何准确地创建目标元素。 Documentation source

        function createWithDefaultProps(Component, props) {
            return <Component {...props} myprop="value" />;
        }
        
        // and then    
        <Router createElement={createWithDefaultProps}>
            ...
        </Router>
        

        【讨论】:

          【解决方案14】:

          React Router v 4 解决方案

          我今天早些时候偶然发现了这个问题,这是我使用的模式。希望这对寻求更新解决方案的人有用。

          我不确定这是否是最佳解决方案,但这是我目前的模式。我通常有一个 Core 目录,在其中保存我常用的组件及其相关配置(加载器、模态框等),并且包含这样的文件:

          import React from 'react'
          import { Route } from 'react-router-dom'
          
          const getLocationAwareComponent = (component) => (props) => (
            <Route render={(routeProps) => React.createElement(component, 
          {...routeProps, ...props})}/>
          )
          
          export default getLocationAwareComponent
          

          然后,在有问题的文件中,我将执行以下操作:

          import React from 'react'
          import someComponent from 'components/SomeComponent'
          import { getLocationAwareComponent } from 'components/Core/getLocationAwareComponent'
          const SomeComponent = getLocationAwareComponent(someComponent)
          
          // in render method:
          <SomeComponent someProp={value} />
          

          您会注意到我将组件的默认导出导入为简陋的骆驼案例,这让我可以在骆驼案例中命名新的位置感知组件,以便我可以正常使用它。除了额外的导入行和分配行之外,组件的行为与预期一样,并正常接收其所有道具,并添加了所有路由道具。因此,我可以愉快地使用 this.props.history.push() 从组件生命周期方法重定向、检查位置等。

          希望这会有所帮助!

          【讨论】:

            【解决方案15】:

            您还可以将 es6 和 stateless functions 结合使用以获得更清晰的结果:

            import Dashboard from './Dashboard';
            import Comments from './Comments';
            
            let dashboardWrapper = () => <Dashboard {...props} />,
                commentsWrapper = () => <Comments {...props} />,
                index = () => <div>
                    <header>Some header</header>
                    <RouteHandler />
                    {this.props.children}
                </div>;
            
            routes = {
                component: index,
                path: '/',
                childRoutes: [
                  {
                    path: 'comments',
                    component: dashboardWrapper
                  }, {
                    path: 'dashboard',
                    component: commentsWrapper
                  }
                ]
            }
            

            【讨论】:

            • 我不确定这是如何工作的——但它看起来不对。你在一个函数中使用this.props,我很确定它不会工作。如果您使用纯函数而不是扩展 React.Component,那么您必须传入 props 作为参数,请参阅 Components and Props 上的 React 文档
            【解决方案16】:

            对于反应路由器 2.x。

            const WrappedComponent = (Container, propsToPass, { children }) => <Container {...propsToPass}>{children}</Container>;
            

            在你的路线中......

            <Route path="/" component={WrappedComponent.bind(null, LayoutContainer, { someProp })}>
            </Route>
            

            确保第三个参数是一个对象,例如:{ checked: false }

            【讨论】:

              【解决方案17】:

              我已经回答了这个问题here

              这里有几种方法可以将 props 传递给路由组件。

              使用 react-router v5,我们可以通过包装一个组件来创建路由,这样我们就可以像这样轻松地将 props 传递给所需的组件。

              <Route path="/">
                  <Home name="Sai" />
              </Route>
              

              同样,您可以在 v5 中使用 children 道具。

              <Route path="/" children={ <Home name="Sai" />} />
              

              如果你使用 react-router v4,你可以使用 render prop 传递它。

              <Route path="/" render={() => <Home name="Sai" />} />
              

              (原发于https://reactgo.com/react-router-pass-props/

              【讨论】:

                【解决方案18】:

                React Router 的问题在于它会渲染你的组件,因此会阻止你传入 props。另一方面,Navigation router 允许您渲染自己的组件。这意味着您不必像下面的代码和随附的JsFiddle 显示那样跳过任何箍来传递道具。

                var Comments = ({myProp}) => <div>{myProp}</div>;
                
                var stateNavigator = new Navigation.StateNavigator([
                  {key:'comments', route:''}
                ]);
                
                stateNavigator.states.comments.navigated = function(data) {
                  ReactDOM.render(
                    <Comments myProp="value" />,
                    document.getElementById('content')
                  );
                }
                
                stateNavigator.start();
                

                【讨论】:

                  【解决方案19】:

                  根据 Rajesh Naroth 的回答,使用带或不带路由器的组件。

                  class Index extends React.Component {
                  
                    constructor(props) {
                      super(props);
                    }
                    render() {
                      const foo = (this.props.route) ? this.props.route.foo : this.props.foo;
                      return (
                        <h1>
                          Index - {foo}
                        </h1>
                      );
                    }
                  }
                  
                  var routes = (
                    <Route path="/" foo="bar" component={Index}/>
                  );
                  

                  或者你可以这样做:

                  export const Index = ({foo, route}) => {
                    const content = (foo) ? foo : (route) ? route.foo : 'No content found!';
                    return <h1>{content}</h1>
                  };
                  

                  【讨论】:

                    【解决方案20】:

                    React 路由器 v5.1 (React >= 16.8) 这样做的方式:

                    <Route path="/comments">
                        <Comments myprop="value" />
                    </Route>
                    

                    现在如果你想在你的组件中访问Route Props,那么你可以参考这个solution。如果是功能组件,还有另一个钩子 useParams() 在那篇文章中没有提到。

                    更多参考:React Router v5.1

                    【讨论】:

                    • ⚠ 注意:您将失去对&lt;Route props /&gt; 的访问权限。我们唯一可以同时拥有自定义道具和路线道具的是使用&lt;Route render/&gt; - 像这样渲染道具:&lt;Route path="/comments" render={(props) =&gt; &lt;Comments myProp="value" {...props}/&gt;}/&gt;
                    • @Mr.spShuvo 你错了。我已经更新了我的答案。检查出。对于类组件,您只需要withRouter,对于功能组件,您只需要hooks
                    • 好吧,我可能有一部分是错的。但是对于类组件,您不能使用钩子。感谢您的更新和更多参考链接。
                    【解决方案21】:

                    对于 react-router 2.5.2,解决方案非常简单:

                        //someConponent
                    ...
                    render:function(){
                      return (
                        <h1>This is the parent component who pass the prop to this.props.children</h1>
                        {this.props.children && React.cloneElement(this.props.children,{myProp:'value'})}
                      )
                    }
                    ...
                    

                    【讨论】:

                      【解决方案22】:

                      使用 自定义路由组件,这在 React Router v3 中是可能的。

                      var Dashboard = require('./Dashboard');
                      var Comments = require('./Comments');
                      var routes = (
                        <Route path="/" handler={Index}>
                          <MyRoute myprop="value" path="comments" handler={Comments}/>
                          <DefaultRoute handler={Dashboard}/>
                        </Route>
                      );
                      

                      至于&lt;MyRoute&gt;组件代码,应该是这样的:

                      import React from 'react';
                      import { Route } from 'react-router';
                      import { createRoutesFromReactChildren } from 'react-router/lib//RouteUtils';
                      
                      const MyRoute = () => <div>&lt;MyRoute&gt; elements are for configuration only and should not be rendered</div>;
                      
                      MyRoute.createRouteFromReactElement = (element, parentRoute) => {
                          const { path, myprop } = element.props;
                          // dynamically add crud route
                          const myRoute = createRoutesFromReactChildren(
                              <Route path={path} />,
                              parentRoute
                          )[0];
                          // higher-order component to pass myprop as resource to components
                          myRoute.component = ({ children }) => (
                              <div>
                                  {React.Children.map(children, child => React.cloneElement(child, { myprop }))}
                              </div>
                          );
                          return myRoute;
                      };
                      
                      export default MyRoute;
                      

                      有关自定义路由组件方法的更多详细信息,请查看我关于该主题的博文:http://marmelab.com/blog/2016/09/20/custom-react-router-component.html

                      【讨论】:

                        【解决方案23】:

                        这可能是使用带有 cookie 处理程序的 react-router-dom 的最佳方式

                        在 index.js 中

                        import React, { Component } from 'react'
                        import {Switch,Route,Redirect} from "react-router-dom"
                        import {RouteWithLayout} from "./cookieCheck"
                        
                        import Login from "../app/pages/login"
                        import DummyLayout from "../app/layouts/dummy"
                        import DummyPage from "../app/pages/dummy" 
                        
                        export default ({props})=>{
                        return(
                            <Switch>
                                <Route path="/login" component={Login} />
                                <RouteWithLayout path="/dummy" layout={DummyLayout} component={DummyPage} 
                                {...props}/>
                                <Redirect from="/*" to="/login" />
                            </Switch>
                          )
                        }
                        

                        并使用 cookieCheck

                        import React , {createElement} from 'react'
                        import {Route,Redirect} from "react-router-dom"
                        import {COOKIE,getCookie} from "../services/"
                        
                        export const RouteWithLayout = ({layout,component,...rest})=>{
                            if(getCookie(COOKIE)==null)return <Redirect to="/login"/>
                                return (
                                <Route {...rest} render={(props) =>
                                    createElement(layout, {...props, ...rest}, createElement(component, 
                              {...props, ...rest}))
                               }
                              />
                            )
                        }
                        

                        【讨论】:

                          【解决方案24】:
                          class App extends Component {
                            constructor(props){
                              super(props);
                          
                              this.state = {
                                data:null
                              }
                          
                          
                            }
                           componentDidMount(){
                             database.ref().on('value', (snapshot) =>{
                               this.setState({
                                 data : snapshot.val()
                                })
                             });
                           }
                          
                            render(){
                            //  const { data } = this.state
                            return (
                              <BrowserRouter>
                                <Switch>
                                  <Route exact path = "/" component = { LandingPage }  />
                                  <Route 
                                    path='/signup' 
                                    render = { () => <Signup  data = {this.state.data} />} />
                                  </Switch>
                              </BrowserRouter>
                          
                            );
                            }
                          };
                          
                          export default App;
                          

                          【讨论】:

                            【解决方案25】:

                            使用下面的解决方案,这适用于 v3.2.5。

                            <Route
                              path="/foo"
                              component={() => (
                                <Content
                                  lang="foo"
                                  meta={{
                                    description: lang_foo.description
                                  }}
                                />
                              )}
                            />
                            

                            <Route path="/foo">
                              <Content
                                lang="foo"
                                meta={{
                                  description: lang_foo.description
                                }}
                              />
                            </Route>
                            

                            【讨论】:

                              【解决方案26】:

                              在 react-router-v3 中,我没有找到任何可行的解决方案,所以我做了一个很大的权衡,使用类继承而不是 props。

                              例如:

                              class MyComments extends Comments{
                                constructor(props) {
                                  super(props);
                                  this.myProp = myValue;
                                }
                              }
                              

                              并且,您在路由器的component 中使用MyComments 而不使用道具。

                              然后,您可以使用this.myPropcomponentDidMount() 函数中获取“myValue”;

                              【讨论】:

                                猜你喜欢
                                • 2018-04-25
                                • 2018-07-17
                                • 2015-09-21
                                • 2018-12-14
                                • 2016-03-19
                                • 2018-11-04
                                • 2018-05-19
                                • 1970-01-01
                                • 2014-11-19
                                相关资源
                                最近更新 更多