react-router-将道具传递给处理程序组件


315

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

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组件。

(通常我会这样做<Comments myprop="value" />

用React Router做到这一点最简单,正确的方法是什么?


这里的问题,以及在类似情况下,尤其是用某些lang编写的框架或库,显然缺乏组合手段(MoC)。在React中,原始函数似乎还不错,它们在React元素中使用原始函数定义了组件,而在React中,组件MoC似乎也可以。但是结合的手段是不完整的。一个人必须能够将道具传递给一个组件,同时将一个组件与另一个组件组合在一起,这并不重要,只要将一个组件作为另一个孩子的组件放置在另一个组件中,或者将一个组件作为道具传递给另一个组件就可以了。
塞尔丘克

使用“ <ComponentA x={<ComponentB y={<ComponentC z={} />} />} /> 或”或“ <ComponentA x={ComponentB(ComponentC()) } /> 否则”这样的语法 ,这种抽象组合的问题将再次出现,并且将需要一些不尽如人意的最优解和间接解决方案(如包装等)。
塞尔丘克

Answers:


152

更新

自新版本发布以来Route,无需使用包装器就可以直接通过组件传递道具。例如,通过使用renderprop

零件:

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示例


旧版

我的首选方式是包装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);
});

58
我遇到了同样的问题,但是这种解决方案不是很快变得冗长吗?
captDaylight 2015年

8
同意captDaylight,它变得冗长。宁愿有更好的方法来解决这个问题!
MattiasHallström'3

6
@mattiashallstrom IMO,1.0中更好的方法是简单地将属性添加到路由。请参阅Thomas E的答案。
k00k

28
您也可以在其中添加无状态组件语法(只是lambda)<Route path="comments" component={() => (<Comments myProp="value" />)}/>
Ciantic

33
我永远不会同意仅通过传递“首选方式”属性来创建其他组件。它是冗长,复杂,容易出错的,并且以各种可以想象的方式都是明显错误的。这可能是React Router允许的唯一方式,但是将其称为“ preferred”是很麻烦的。被谁偏爱?
SzczepanHołyszewski16年

260

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

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}/>
);

11
这是正确的答案。在react-router 1.0中,您可以route在组件中获取普通对象。这是github问题的答案:github.com/rackt/react-router/issues/615#issuecomment-100432086
ycdesu 2015年

4
这是我一直在寻找的简单答案。其他技术可以工作,但需要的代码量是其10倍。适用于v1.0.x。我唯一可以看到的缺点是,无论您打算在路由器容器中使用还是不使用路由器容器,都希望使用相同的组件。但是对我来说,我所有的顶级组件都通过路由一对一映射。
killthrush

12
十分感谢!如果有人想知道,则foo属性将在您的组件中提供,如下所示:this.props.route.foo
k00k 2016年

8
可以将此设置为避免混淆的正确答案吗?
Archibald

2
不!真正的解决方案请参见Rajesh Naroth的答案:)
Alex

114

复制ciantic在接受的回复中的评论:

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

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


这基本上与上面的包装器答案相同,但是它的罗word性降低了。不过,那句语法很烂。尝试在_ref
2016年

11
这就像一个匿名包装,因此所有注入的道具(例如位置)都将丢失。人们必须像这样手动传递道具,component={(props) => (<Comments myProp="value" location={ props.location } />)}但一切都会再次变得混乱
yuji 2016年

1
@JacobThomason我们不会重新渲染路由器配置,因此不太可能会降低性能。
Sebastien Lorber

9
从React-Router 4开始,如果您提供一个内联函数,您将获得许多不希望的重新安装。对于内联渲染,请使用渲染道具。链接到文档
Daniel Reina

1
@yuji要避免太凌乱,可以做到:component={(props) => (<Comments {...props} myProp="value" />)}保持注入的道具
apelsinapa

57

这是Rajesh提供解决方案,没有yuji的不便之处,并已针对React Router 4进行了更新。

代码如下:

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

请注意,我使用render代替component。原因是为了避免不必要的重新安装。我还将传递props给该方法,并且在Comment组件上使用与对象散布运算符相同的道具(ES7提案)。


真的也可以解决不必要的安装问题!+1
GLindqvist

44

只是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})}/>

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

重要的是要注意,使用此方法,通过路由器发送的所有道具(例如params)都将被覆盖/删除。


1
鲍勃,您熟悉闭包吗?stackoverflow.com/questions/111102/...
sigmus

3
而且,如果您还需要路由器的查询和参数,那么类似的方法将起作用:(return React.createElement(Component, _.assign({}, this.props, props));该方法使用_.assign组成组合对象...当然也可以使用其他方法)。
Malcolm Dwyer

2
您可能还想通过孩子。var wrapComponent = function(Component,props){return React.createClass({render:function(){return React.createElement(Component,props,this.props.children);}}); };
Julio Rodrigues 2015年

1
这是一个高阶组件对于那些谁不熟悉它们facebook.github.io/react/docs/higher-order-components.html
icc97

1
注意:这是针对旧版本的React Router。目前V4拥有rendercomponentchildren为方法Route。请注意,正如@dgrcode答案所指出的那样,您应该使用render而不是component
icc97 '17

31

您可以通过将道具传递到<RouteHandler>v0.13.x中或在v1.0中的Route组件本身来传递道具。

// 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的升级指南)

所有子处理程序都会收到相同的道具集-视情况而定是否有用。


1
看到React.cloneElement传递了多个元素确实令人感到困惑,但是函数签名似乎只包含一个react元素。我认为此片段可以更容易理解。
manu 2015年

2
显然,这是对文档目标的最佳答案,但我也同意手册,可以写成更好的用法。代码更具体的问题看起来像:React.cloneElement(this.props.children, {myprop: "value"})React.cloneElement(this.props.children, {myprop: this.props.myprop})
juanitogan

这个答案胜出。Router会为您执行的操作中发生的事情也更加清楚了。当有人在阅读代码时,如果我知道Comment包含在Index中,那么我将查看Index以查看将什么道具发送到Comments。如果我碰巧知道Comment是一个路由器处理程序,那么我将查看路由器配置,并找出Index父注释,然后继续查看。
mjohnsonengr

24

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

<Route path="/" component={() => <App myProp={someValue}/>} >

如果您需要让孩子通过:

<Route path="/" component={(props) => <App myProp={someValue}>{props.children}</App>} >


很好,但是如果有的话它不会通过孩子。
zpr

@zpr我为props.children添加了一个示例
Nick

2
正如@dgrcode答案所指出的,您应该使用render而不是component
icc97

23

React-Router 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} />} />

PS这仅在alpha版本中有效,并且在v4 alpha版本之后已删除。在v4最新版本中,再次是,带有路径和确切的道具。

react-lego一个示例应用程序包含在其react-router-4分支上的routes.js中精确执行此操作的代码


21

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

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

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


13

用无状态功能组件包装它:

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

12

您还可以使用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);
});

1
它作为ReactRouter.RouteHandlerMixin在全球凉亭版本中公开公开,所以我不这么认为。
2015年

这也允许使用TransitionGroup和CSSTransitionGroup对过渡进行动画处理,而我使用wrapper方法无法工作。
2015年7

2
奇怪的是,官方文档中没有提到这一点。
Kosmetika

React文档不再推荐使用
Mixins

12

您可以通过以下方式传递道具<RouterHandler/>

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但具有不同值的prop,则可能会出现问题。


1
该代码中的3个句点/点是做什么或意味着什么:{...props}
Giant Elk 2015年

2
我想Flux可以避免将父App的状态发送到路由的需要。我使上面的代码正常工作,但是它不是很明确,因此隐藏的代码很丑陋,不易跟踪和跟踪正在发生的事情。
巨麋

2
传播操作员说明。这不是很明确,但这并不是最坏的事情,因为您传递的是不可变的道具。
Meistro 2015年


这对我有用,但是正确的语法是{... this.props}
Win

10

在1.0和2.0中,您可以使用createElementprop of Router来指定如何精确地创建目标元素。文件来源

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

// and then    
<Router createElement={createWithDefaultProps}>
    ...
</Router>

6

您还可以结合使用es6和无状态函数来获得更清晰的结果:

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作为参数传递,请参阅关于组件和道具
icc97

6

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} />

您会注意到我将组件的默认导出导入为驼峰式的情况,这使我可以在CamelCase中命名新的位置感知组件,因此我可以正常使用它。除了附加的导入线和分配线,该组件的行为与预期的一样,并正常添加所有路径道具,并添加了所有路径道具。因此,我可以使用this.props.history.push()从组件生命周期方法中愉快地重定向,检查位置等。

希望这可以帮助!


4

对于反应路由器2.x。

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

在你的路线上...

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

确保第三个参数是类似的对象:{ checked: false }


1

React 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();

1

根据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>
};

0

对于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'})}
  )
}
...

0

使用自定义路由组件,这可以在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>
);

至于<MyRoute>组件代码,应该是这样的:

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


0

这可能是将react-router-dom与Cookie处理程序结合使用的最佳方法

在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}))
       }
      />
    )
}

0
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;

0

使用上述解决方案,此方法在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>
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.