response-router - передать реквизиты компоненту обработчика

315

У меня есть следующая структура для моего приложения 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компонент.

(обычно я бы сделал это так <Comments myprop="value" />)

Какой самый простой и правильный способ сделать это с React Router?

Kosmetika
источник
Проблема здесь, и в таких похожих случаях, особенно с каркасами или библиотеками, написанными на некоторых языках, в некотором отсутствии средств комбинации (MoC). Примитивы в React выглядят неплохо, они довольно хороши, определяя компоненты с помощью примитивов, в элементах React и компоненте MoC , что также хорошо в React. Но средства совмещения неполны. Нужно иметь возможность передавать реквизиты компоненту при объединении компонента с другим, не имеет значения, если поместить один компонент в другой компонент как его дочерний элемент или передать один компонент как реквизит другому.
Сельчук
С некоторым синтаксисом, подобным <ComponentA x={<ComponentB y={<ComponentC z={} />} />} /> ИЛИ. <ComponentA x={ComponentB(ComponentC()) } /> В противном случае эти проблемы комбинаций абстракций будут повторяться, и для них потребуются некоторые менее оптимальные и косвенные решения, называемые обходными путями, такими как обтекание и т. Д. И т. Д. Абстракции должны быть первоклассными гражданами в качестве примитивов, что бы ни означало восприятие первого класса.
Сельчук

Ответы:

152

ОБНОВИТЬ

Начиная с нового выпуска, можно передавать реквизиты напрямую через Routeкомпонент, без использования Wrapper. Например, используя renderопору .

Составная часть:

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);
});
Colch
источник
58
Я сталкиваюсь с той же проблемой, но разве это решение не становится многословным быстро?
captDaylight
8
Согласитесь с captDaylight, он становится многословным. Предпочел бы лучший способ справиться с этим!
Маттиас Халлстрем
6
@mattiashallstrom IMO, лучший способ в 1.0 - просто добавить свойство к маршруту. Смотрите ответ Томаса Е.
k00k
28
Вы также можете добавить туда синтаксис компонента без сохранения состояния (просто лямбда), он довольно короткий<Route path="comments" component={() => (<Comments myProp="value" />)}/>
Ciantic
33
Я никогда не соглашусь с созданием дополнительного компонента, чтобы передать свойства, являющиеся «предпочтительным способом». Это многословно, сложно, подвержено ошибкам и просто неправильно во всех мыслимых возможностях. Это может быть ЕДИНСТВЕННЫЙ способ, которым позволяет реагировать маршрутизатор, но назвать его «предпочтительным» - это натяжение. Кто предпочитает?
Щепан Холышевский
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
Это правильный ответ. В реакции-маршрутизаторе 1.0 вы можете получить routeпростой объект в вашем компоненте. Вот ответ на вопрос github: github.com/rackt/react-router/issues/615#issuecomment-100432086
ycdesu
4
Это простой ответ, который я искал. Другие методы работают, но требуют 10-кратного объема кода. Хорошо работает для v1.0.x. Единственный недостаток, который я вижу, - это если вы намереваетесь использовать один и тот же компонент как с контейнером маршрутизатора, так и без него. Но для меня все мои компоненты верхнего уровня сопоставлены 1-к-1 с маршрутами.
killthrush
12
Большое спасибо! Если кому-то интересно, свойство foo будет доступно в вашем компоненте как: this.props.route.foo
k00k
8
Можно ли установить этот ответ как правильный ответ, чтобы избежать путаницы?
Арчибальд
2
Нет! См. Ответ Раджеша Нарота для реального решения :)
Алекс
114

Копирование из комментариев ciantic в принятом ответе:

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

Это самое изящное на мой взгляд решение. Оно работает. Помог мне.

Раджеш Нарот
источник
Это в основном то же самое, что и ответ обертки выше, но это намного менее многословно. Человек, однако, этот синтаксис отстой. Попробуйте добавить_ref
EdH
11
Это похоже на анонимную оболочку, поэтому все введенные реквизиты (например, местоположение) пропущены. Нужно вручную пропустить реквизит, component={(props) => (<Comments myProp="value" location={ props.location } />)}но все снова становится грязным
Юджи
1
@JacobThomason мы не перерисовываем конфигурацию реагирующего маршрутизатора, так что это вряд ли приведет к снижению производительности.
Себастьян Лорбер
9
Начиная с React-Router 4, если вы предоставите встроенную функцию, вы получите много нежелательных перемонтирований. Для встроенного рендеринга используйте команду render. Ссылка на документы
Даниэль Рейна
1
@yuji Чтобы не делать это слишком грязно, можно сделать: component={(props) => (<Comments {...props} myProp="value" />)}сохранить впрыснутый реквизит
apelsinapa
57

Это решение от Раджеша , без неудобных комментариев Юджи , и обновленное для React Router 4.

Код будет выглядеть так:

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

Обратите внимание, что я использую renderвместо component. Причина заключается в том, чтобы избежать нежелательного повторного монтажа . Я также передаю propsэтот метод и использую те же реквизиты для компонента Comments с оператором распространения объектов (предложение 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})}/>

Я еще не тестировал это решение, поэтому любые отзывы важны.

Важно отметить, что при использовании этого метода любые реквизиты, отправляемые через маршрутизатор (например, параметры), перезаписываются / удаляются.

SigmuS
источник
1
Боб, ты знаком с замыканиями? stackoverflow.com/questions/111102/…
сигм
3
И если вам также нужны запрос и параметры от маршрутизатора, то будет работать что-то вроде этого: return React.createElement(Component, _.assign({}, this.props, props));(Этот использует _.assign для создания комбинированного объекта ... другие методы, конечно, доступны).
Малкольм Дуайер,
2
Вы можете также передать детям. var wrapComponent = function (Component, props) {return React.createClass ({render: function () {return React.createElement (Component, props, this.props.children);}}); };
Хулио Родригес
1
Это компонент высшего порядка для тех, кто не знаком с ними. Facebook.github.io/react/docs/higher-order-components.html
icc97
1
NB Это сейчас для старой версии React Router. Текущий v4 имеет render, componentи childrenметоды Route. Обратите внимание, что, как указывает ответ @dgrcode , вы должны использовать renderвместоcomponent
icc97
31

Вы можете передавать реквизиты, передавая их <RouteHandler>(в v0.13.x) или самому компоненту Route в v1.0;

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

Все дочерние обработчики получат одинаковый набор реквизитов - это может быть полезно или нет в зависимости от обстоятельств.

cachvico
источник
1
Это действительно озадачивает, React.cloneElementкогда передают несколько элементов, но сигнатура функции, кажется, занимает только один реагирующий элемент. Я думаю, что этот фрагмент легче понять.
Ман
2
Это, безусловно, лучший ответ на цель с документами, но я также согласен с Ману, что он может быть написан для лучшего использования. Код, более конкретный для вопроса, будет выглядеть так: React.cloneElement(this.props.children, {myprop: "value"})или React.cloneElement(this.props.children, {myprop: this.props.myprop})т. Д.
juanitogan
Этот ответ побеждает. Также гораздо яснее, что происходит в середине того, что маршрутизатор делает для вас. Как кто-то, читающий код, если я знаю, что Комментарии находятся внутри Индекса, я посмотрю на Индекс, чтобы увидеть, какие реквизиты отправляются в Комментарии. Если мне случится узнать, что Comments - это обработчик маршрутизатора, я посмотрю на конфигурацию маршрутизатора и выясню, что Index родительские комментарии, и все равно пойду туда.
mjohnsonengr
24

Используя ES6, вы можете просто встроить упаковщики компонентов:

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

Если вам нужно передать детей:

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

Ник
источник
Это хорошо, но это не проходит через детей, если они есть.
zpr
@zpr Я добавил пример для props.children
Ник
2
Как указывает ответ @dgrcode , вы должны использовать renderвместоcomponent
icc97
23

React-роутер v4 альфа

теперь есть новый способ сделать это, хотя и очень похожий на предыдущий метод.

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 Это работает только в альфа-версии, и были удалены после альфа-версии v4. В v4 последний, еще раз, с путем и точным реквизитом.

response-lego - пример приложения, содержащего код, который делает это точно в файле rout.js в своей ветке response-router-4.

peter.mouland
источник
21

Вот самое чистое решение, которое я придумал (React Router v4):

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

MyComponentдо сих пор props.matchи props.location, и имеет props.foo === "lol".

cgenco
источник
13

Оберните его компонентом функции без сохранения состояния:

<Router>
  <Route 
    path='/' 
    component={({children}) => 
      <MyComponent myProp={'myVal'}>{children}</MyComponent/>
    }/>
</Router>
mg74
источник
12

Вы также можете использовать миксин RouteHandler, чтобы избежать использования компонента-обертки и упростить передачу состояния родителя в качестве реквизита:

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, так что я так не думаю.
июл
Это также позволяет анимировать переходы с использованием TransitionGroup и CSSTransitionGroup, которые я не смог заставить работать с помощью метода оболочки.
июл
2
Странно, что нет упоминания об этом в официальных документах.
Косметика
Миксины больше не рекомендуются в документации React: facebook.github.io/react/blog/2016/07/13/…
icc97
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но с разными значениями.

Meistro
источник
1
Что обозначают или обозначают 3 периода / точки в этом коде:{...props}
Гигантский лось
2
Я предполагаю, что Flux избавит от необходимости посылать состояние из родительского приложения на маршрут. Я получил приведенный выше код работающим, но он не является явным, поэтому скрытая мажика уродлива, ее нелегко отследить и отследить, что происходит.
Гигантский Лось
2
Распространение объяснения оператора . Это не явно, но это не самое страшное, так как вы передаете реквизиты, которые являются неизменными.
Meistro
Вот документы ReactJS об операторе распространения: facebook.github.io/react/docs/jsx-spread.html, а также facebook.github.io/react/docs/transferring-props.html
Лось-гигант
это сработало для меня, но правильный синтаксис: {... 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 и stateless для получения более чистого результата:

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качестве аргумента, см. Документацию React по компонентам и процедурам
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.х.

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

и в твоих маршрутах ...

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

убедитесь , что третьи пары является объектом , как: { checked: false }.

holyxiaoxin
источник
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

Используйте компонент с или без маршрутизатора, основанный на ответе Раджеша Нарота.

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

для реакции-маршрутизатора 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

это, вероятно, лучший способ использовать response-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}))
       }
      />
    )
}
Snivio
источник
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;
nik.ss
источник
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>
illvart
источник