reaktor-router - przekazuje rekwizyty do komponentu modułu obsługi

315

Mam następującą strukturę dla mojej aplikacji React.js używającej 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);
});

Chcę przekazać niektóre właściwości do Commentskomponentu.

(normalnie zrobiłbym to tak <Comments myprop="value" />)

Jaki jest najłatwiejszy i właściwy sposób, aby to zrobić za pomocą React Router?

Kosmetika
źródło
Problem tutaj, aw takich podobnych przypadkach, zwłaszcza z frameworkami lub bibliotekami napisanymi w niektórych językach, pewien brak środków kombinacji (MoC). Prymitywy wydają się być poprawne w React, są całkiem dobre, definiują komponenty z prymitywami, w React elementach i komponencie, MoC , co wydaje się również w React. Ale sposoby łączenia są niepełne. Trzeba być w stanie przekazać rekwizyty do komponentu podczas łączenia komponentu z innym, nie ma znaczenia, czy umieszczając jeden komponent w innym komponencie jako jego potomku, czy przekazując jeden komponent jako rekwizyty do drugiego.
Selçuk
Przy pewnej składni, takiej jak <ComponentA x={<ComponentB y={<ComponentC z={} />} />} /> OR <ComponentA x={ComponentB(ComponentC()) } /> W przeciwnym razie problemy z kombinacjami abstrakcji powrócą i będą wymagały mniej niż optymalnych i pośrednich rozwiązań zwanych obejściami, takimi jak zawijanie itp. Itp. Abstrakcje muszą być obywatelami pierwszej klasy jako prymitywy, niezależnie od tego, co oznacza percepcja pierwszej klasy.
Selçuk

Odpowiedzi:

152

AKTUALIZACJA

Od nowej wersji możliwe jest przekazywanie rekwizytów bezpośrednio przez Routekomponent, bez użycia Wrappera. Na przykład za pomocą renderprop .

Składnik:

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

Stosowanie:

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

Przykład Codesandbox


STARA WERSJA

Moim preferowanym sposobem jest owijanie Commentskomponentu i przekazywanie opakowania jako procedury obsługi trasy.

To jest twój przykład z zastosowanymi zmianami:

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
źródło
58
Napotykam na ten sam problem, ale czy to rozwiązanie nie jest zbyt szczegółowe?
captDaylight
8
Zgadzam się z captDaylight, staje się gadatliwy. Wolałby lepszy sposób na poradzenie sobie z tym!
Mattias Hallström,
6
@mattiashallstrom IMO, lepszym sposobem w wersji 1.0 jest po prostu dodanie właściwości do trasy. Zobacz odpowiedź Thomasa E.
k00k
28
można tam również dodać składnię komponentu bezstanowego (tylko lambda), jest ona dość krótka<Route path="comments" component={() => (<Comments myProp="value" />)}/>
Ciantic
33
Nigdy nie zgodzę się na utworzenie dodatkowego komponentu tylko po to, aby przekazać właściwości będące „preferowanym sposobem”. Jest pełny, skomplikowany, podatny na błędy i po prostu zły pod każdym względem, jaki można sobie wyobrazić. Może to być JEDYNY sposób, w jaki router reaguje, ale nazywanie go „preferowanym” to odcinek. Preferowany przez kogo?
Szczepan Hołyszewski
260

Jeśli wolisz nie pisać opakowań, myślę, że możesz to zrobić:

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}/>
);
Thomas E.
źródło
11
To poprawna odpowiedź. W React-Router 1.0 możesz uzyskać routeprosty obiekt w swoim komponencie. Oto odpowiedź na problem github: github.com/rackt/react-router/issues/615#issuecomment-100432086
ycdesu
4
Oto prosta odpowiedź, której szukałem. Inne techniki działają, ale wymagają 10-krotnej ilości kodu. Działa dobrze w wersji 1.0.x. Jedynym minusem, jaki widzę, jest to, że zamierzasz używać tego samego komponentu zarówno z kontenerem routera, jak i bez niego. Ale dla mnie wszystkie moje komponenty najwyższego poziomu zmapowane 1-do-1 z trasami.
killthrush
12
Wielkie dzięki! Jeśli ktoś się zastanawia, właściwość foo będzie dostępna w twoim komponencie jako: this.props.route.foo
k00k
8
Czy można ustawić prawidłową odpowiedź, aby uniknąć nieporozumień?
Archibald
2
Nie! Zobacz prawdziwe rozwiązanie Rajesha Narotha :)
Alex
114

Kopiowanie z komentarzy ciantic w przyjętej odpowiedzi:

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

To moim zdaniem najbardziej wdzięczne rozwiązanie. To działa. Pomogło mi.

Rajesh Naroth
źródło
Jest to w zasadzie to samo co powyżej odpowiedź otoki, ale jest znacznie mniej pracochłonne. Człowieku, ta składnia jest do bani. Spróbuj wrzucić_ref
EdH
11
To jest jak anonimowe opakowanie, więc wszystkie wstrzyknięte rekwizyty (np. Lokalizacja) są pomijane. Trzeba ręcznie przekazywać rekwizyty jak, component={(props) => (<Comments myProp="value" location={ props.location } />)}ale znów robi się bałagan
yuji,
1
@JacobThomason nie renderujemy ponownie konfiguracji routera, więc jest mało prawdopodobne, aby obniżyła wydajność.
Sebastien Lorber,
9
Począwszy od React-Router 4, jeśli zapewnisz funkcję wbudowaną, dostaniesz dużo niepożądanego ponownego montażu. Do renderowania wbudowanego użyj prop renderowania. Link do dokumentacji
Daniel Reina
1
@yuji Aby nie robić z tego bałaganu, można: component={(props) => (<Comments {...props} myProp="value" />)}utrzymywać wstrzyknięte rekwizyty
apelsinapa
57

Jest to rozwiązanie od Rajesh , bez niewygodnego komentarza yuji , i zaktualizowane do React Router 4.

Kod wyglądałby tak:

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

Zauważ, że używam renderzamiast component. Powodem jest uniknięcie niepożądanego ponownego montażu . Przekazuję również propstę metodę i używam tych samych rekwizytów w komponencie Komentarze z operatorem rozkładania obiektów (propozycja ES7).

Daniel Reina
źródło
Naprawdę świetnie sprawdza się również w rozwiązaniu niepożądanego montażu! +1
GLindqvist
44

Tylko kontynuacja odpowiedzi ColCh. Wyodrębnienie opakowania komponentu jest dość łatwe:

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

Nie przetestowałem jeszcze tego rozwiązania, więc każda opinia jest ważna.

Należy zauważyć, że dzięki tej metodzie wszelkie rekwizyty wysyłane przez router (takie jak parametry) są nadpisywane / usuwane.

Sigmus
źródło
1
Bob, czy znasz zamknięcia? stackoverflow.com/questions/111102/…
sigmus
3
A jeśli potrzebujesz również zapytania i parametrów z routera, wtedy coś takiego będzie działać: return React.createElement(Component, _.assign({}, this.props, props));(Ten używa _.ignign do komponowania połączonego obiektu ... oczywiście dostępne są inne metody).
Malcolm Dwyer
2
Możesz także przekazać dzieci var wrapComponent = function (Component, rekwizyty) {return React.createClass ({render: function () {return React.createElement (Component, rekwizyty, this.props.children);}}); };
Julio Rodrigues,
1
Jest to składnik wyższego rzędu dla tych, którzy nie są z nim zaznajomieni facebook.github.io/react/docs/higher-order-components.html
icc97
1
NB To jest teraz dla starej wersji React Router. Aktualny v4 ma render, componenti childrenmetody Route. Zauważ, że jak wskazuje odpowiedź @dgrcode , powinieneś użyć renderzamiastcomponent
icc97
31

Możesz przekazywać rekwizyty, przekazując je do <RouteHandler>(w wersji 0.13.x) lub samego komponentu trasy w wersji 1.0;

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

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

(z przewodnika aktualizacji na https://github.com/rackt/react-router/releases/tag/v1.0.0 )

Wszystkie osoby zajmujące się dziećmi otrzymają ten sam zestaw rekwizytów - może to być przydatne lub nie, zależnie od okoliczności.

Cachvico
źródło
1
To naprawdę zastanawia React.cloneElement, czy przekazano wiele elementów, ale podpis funkcji wydaje się przyjmować tylko jeden element reagujący. Myślę, że ten fragment kodu można łatwiej zrozumieć.
manu
2
Jest to zdecydowanie najlepsza odpowiedź na cele z dokumentami, ale zgadzam się również z manu, że można by napisać, aby lepiej używać. Kod bardziej szczegółowy dla pytania wyglądałby następująco: React.cloneElement(this.props.children, {myprop: "value"})lub React.cloneElement(this.props.children, {myprop: this.props.myprop})itp.
juanitogan
Ta odpowiedź wygrywa. Jest również o wiele bardziej jasne, co dzieje się w środku rzeczy, które robi dla ciebie Router. Jako ktoś czytający kod, jeśli wiem, że Komentarze znajdują się w Indeksie, spojrzę na Indeks, aby zobaczyć, jakie rekwizyty są wysyłane do Komentarze. Jeśli zdarzy mi się wiedzieć, że Komentarze to moduł obsługi routera, przejrzę konfigurację routera i stwierdzę, że komentarze rodziców z indeksu nadal tam szukam.
mjohnsonengr
24

Za pomocą ES6 możesz po prostu łączyć opakowania komponentów:

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

Jeśli potrzebujesz przekazać dzieci:

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

Nacięcie
źródło
To miłe, ale nie przechodzi przez dzieci, na wypadek, gdyby takie były.
zpr
@zpr Dodałem przykład dla props.children
Nick
2
Jak wskazuje odpowiedź @dgrcode , powinieneś użyć renderzamiastcomponent
icc97
23

React-router v4 alpha

teraz jest na to nowy sposób, choć bardzo podobny do poprzedniej metody.

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 Działa to tylko w wersji alfa i zostały usunięte po wydaniu wersji v4 alpha. W v4 najpóźniej jest ponownie, ze ścieżką i dokładnymi rekwizytami.

reag -lego przykładowa aplikacja zawiera kod, który robi to dokładnie w route.js w swojej gałęzi React-router-4

peter.mouland
źródło
21

Oto najczystsze rozwiązanie, jakie wymyśliłem (React Router v4):

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

MyComponentwciąż ma props.matchi props.location, i ma props.foo === "lol".

cgenco
źródło
13

Zawiń go składnikiem funkcji bezstanowej:

<Router>
  <Route 
    path='/' 
    component={({children}) => 
      <MyComponent myProp={'myVal'}>{children}</MyComponent/>
    }/>
</Router>
mg74
źródło
12

Możesz także użyć mixinu RouteHandler, aby uniknąć komponentu otoki i łatwiej przekazać stan rodzica jako rekwizyty:

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);
});
Jul
źródło
1
Jest publicznie ujawniony w globalnej wersji altany jako ReactRouter.RouteHandlerMixin, więc nie sądzę.
lip
Pozwala to również na animowanie przejść za pomocą TransitionGroup i CSSTransitionGroup, których nie mogłem zabrać do pracy metodą otoki.
lip
2
Dziwne, że nie ma o tym wzmianki w oficjalnych dokumentach.
Kosmetika
Dokumenty React nie zalecają już miksów: facebook.github.io/react/blog/2016/07/13/…
icc97
12

Możesz przekazać rekwizyty w <RouterHandler/>następujący sposób:

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

Minusem tego jest to, że bezkrytycznie podajesz rekwizyty. CommentsMoże więc otrzymać rekwizyty, które naprawdę są przeznaczone dla innego komponentu, w zależności od konfiguracji tras. Nie jest to wielka sprawa, ponieważ propsjest niezmienna, ale może to być problematyczne, jeśli dwa różne komponenty oczekują rekwizytu o nazwie, fooale o różnych wartościach.

Meistro
źródło
1
Co oznaczają 3 kropki / kropki w tym kodzie:{...props}
Olbrzymi łoś
2
Myślę, że Flux uniknąłby potrzeby wysyłania stanu z aplikacji nadrzędnej na trasę. Mam powyższy kod działający, ale nie jest jawny, więc ukryta majika jest brzydka, niełatwo jest prześledzić i śledzić, co się dzieje.
Giant Elk
2
Rozwiń wyjaśnienie operatora . Nie jest to jednoznaczne, ale nie jest najgorsze, ponieważ przekazujesz rekwizyty, które są niezmienne.
Meistro
Oto dokumenty ReactJS dotyczące operatora rozprzestrzeniania: facebook.github.io/react/docs/jsx-spread.html, a także facebook.github.io/react/docs/transferring-props.html
Giant Elk
to zadziałało dla mnie, ale poprawna składnia to {... this.props}
Wygraj
10

W wersjach 1.0 i 2.0 możesz użyć createElementprop, Routeraby określić, jak dokładnie utworzyć element docelowy. Źródło dokumentacji

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

// and then    
<Router createElement={createWithDefaultProps}>
    ...
</Router>
Andrew Khmylov
źródło
6

Możesz także łączyć funkcje es6 i bezstanowe, aby uzyskać znacznie czystszy wynik:

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
      }
    ]
}
Zhiwei Huang
źródło
Nie jestem pewien, jak dokładnie to działa - ale wygląda to źle. Używasz this.propsfunkcji, która na pewno nie zadziała. Jeśli używasz czystych funkcji zamiast rozszerzać React.Component, musisz propspodać jako argument, zobacz React docs on Components and Props
icc97
6

React Router v 4 solution

Natknąłem się dziś na to pytanie i oto wzór, którego używam. Mamy nadzieję, że jest to przydatne dla każdego, kto szuka bardziej aktualnego rozwiązania.

Nie jestem pewien, czy jest to najlepsze rozwiązanie, ale taki jest mój obecny wzorzec. Zazwyczaj mam katalog Core, w którym przechowuję często używane komponenty wraz z ich odpowiednimi konfiguracjami (moduły ładujące, moduły itp.) I dołączam taki plik:

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

Następnie w danym pliku wykonaj następujące czynności:

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

Zauważysz, że importuję domyślny eksport mojego komponentu jako pokornego wielbłąda, co pozwala mi nazwać nowy komponent rozpoznający lokalizację w CamelCase, dzięki czemu mogę go normalnie używać. Oprócz dodatkowej linii importu i linii przypisania, komponent zachowuje się zgodnie z oczekiwaniami i odbiera wszystkie swoje rekwizyty normalnie, z dodaniem wszystkich rekwizytów trasy. W ten sposób mogę z przyjemnością przekierowywać metody cyklu życia komponentów za pomocą this.props.history.push (), sprawdzać lokalizację itp.

Mam nadzieję że to pomoże!

Chris
źródło
4

Dla reagowania router 2.x.

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

i na twoich trasach ...

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

upewnić 3rd param jest przedmiotem takich jak: { checked: false }.

Holyxiaoxin
źródło
1

Problem z React Router polega na tym, że renderuje on twoje komponenty, a więc zatrzymuje przekazywanie w rekwizytach. Router Nawigacja , z drugiej strony, pozwala na renderowanie własnych komponentów. Oznacza to, że nie musisz przeskakiwać żadnych obręczy, aby przekazać rekwizyty jako poniższy kod i towarzyszący program 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();
Graham Mendick
źródło
1

Użyj komponentu z routerem lub bez, w oparciu o odpowiedź 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}/>
);

Lub możesz to zrobić w ten sposób:

export const Index = ({foo, route}) => {
  const content = (foo) ? foo : (route) ? route.foo : 'No content found!';
  return <h1>{content}</h1>
};
Michael Hobbs
źródło
0

w przypadku routera reagującego 2.5.2 rozwiązanie jest takie proste:

    //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'})}
  )
}
...
min maja
źródło
0

Przy użyciu niestandardowego komponentu trasy jest to możliwe w 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>
);

Jeśli chodzi o <MyRoute>kod komponentu, powinno to być coś takiego:

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;

Aby uzyskać więcej informacji na temat niestandardowego komponentu trasy, sprawdź mój post na blogu na ten temat: http://marmelab.com/blog/2016/09/20/custom-react-router-component.html

François Zaninotto
źródło
0

jest to prawdopodobnie najlepszy sposób na użycie domagania-router-dom z programem obsługi plików cookie

w 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>
  )
}

i użyj 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
źródło
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
źródło
0

Użyj powyższego rozwiązania, a to działa w wersji 3.2.5.

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

lub

<Route path="/foo">
  <Content
    lang="foo"
    meta={{
      description: lang_foo.description
    }}
  />
</Route>
illvart
źródło