Программная навигация с использованием реагирующего маршрутизатора V4

241

Я только что заменил react-router на v3 на v4.
Но я не уверен, как программно перемещаться в функции-члене Component. В функции handleClick() я хочу перейти к /path/some/where после обработки некоторых данных. Я использовал это:

import { browserHistory } from 'react-router'
browserHistory.push('/path/some/where')

Но я не могу найти такие интерфейсы в v4.
Как я могу перемещаться с помощью v4?

  • 3
    Вы можете получить доступ к объекту истории в v4 через props: this.props.history.push ('/')
  • 3
    Что делать, если вы хотите получить к нему доступ из другого места, чем Component ? Например, внутри редукционных действий.
Показать ещё 4 комментария
Теги:
ecmascript-6
react-router-v4

14 ответов

310
Лучший ответ

Если вы ориентируетесь на среды браузера, вам нужно использовать пакет react-router-dom вместо react-router. Они следуют тому же подходу, что и React, чтобы отделить ядро (react) и код платформы (react-dom, react-native) с тонким отличием в том, что вам не нужно устанавливать два отдельных пакета. Таким образом, пакеты среды содержат все, что вам нужно. Вы можете добавить его в свой проект как:

yarn add react-router-dom

или же

npm я react-router-dom

Первое, что вам нужно сделать, это предоставить <BrowserRouter> как самый верхний родительский компонент в вашем приложении. <BrowserRouter> использует API history HTML5 и управляет им за вас, поэтому вам не нужно беспокоиться о том, чтобы создать его экземпляр самостоятельно и передать его компоненту <BrowserRouter> в качестве опоры (как это требовалось в предыдущих версиях).

В V4 для программной навигации вам необходим доступ к объекту history, который доступен через context React, при условии, что у вас есть компонент поставщика <BrowserRouter> как самый верхний родительский элемент в вашем приложении. Библиотека раскрывает через контекст объект router, который сам содержит history как свойство. Интерфейс history предлагает несколько методов навигации, таких как push, replace и goBack. Вы можете проверить весь список свойств и методов здесь.

Важное замечание для пользователей Redux/Mobx

Если вы используете redux или mobx в качестве библиотеки управления состоянием в своем приложении, вы можете столкнуться с проблемами с компонентами, которые должны учитывать местоположение, но не будут повторно отображаться после запуска обновления URL-адреса.

Это происходит потому, что react-router передает location компонентам, используя контекстную модель.

И connect, и наблюдатель создают компоненты, чьи методы shouldComponentUpdate проводят поверхностное сравнение своих текущих и следующих компонентов. Эти компоненты будут перерисовываться только тогда, когда изменилась хотя бы одна опора. Это означает, что для того, чтобы они обновлялись при изменении местоположения, им необходимо предоставить реквизит, который изменяется при изменении местоположения.

2 подхода к решению этой проблемы:

  • Оберните ваш подключенный компонент в <Route/> pathte <Route/>. Текущий объект location является одним из объектов, которые <Route> передает компоненту, который он отображает.
  • Оберните подключенный компонент компонентом высшего порядка withRouter, который на самом деле имеет тот же эффект и вводит location как и проп

Кроме этого, существует четыре способа программной навигации, упорядоченные по рекомендации:

1.- Использование компонента <Route>

Это продвигает декларативный стиль. До версии v4 компоненты <Route/> размещались в верхней части иерархии компонентов, поэтому необходимо заранее продумать структуру маршрутов. Однако теперь вы можете иметь компоненты <Route> любом месте вашего дерева, что позволяет вам лучше контролировать условный рендеринг в зависимости от URL. Route внедряет match, location и history как реквизиты в ваш компонент. Методы навигации (такие как push, replace , goBack...) доступны в качестве свойств объекта history.

Существует 3 способа визуализации чего-либо с помощью Route, используя component, render или children реквизиты, но не используйте более одного в одном Route. Выбор зависит от варианта использования, но в основном первые два параметра будут отображать ваш компонент только в том случае, если path соответствует расположению URL-адреса, тогда как с children элементами компонент будет отображаться независимо от того, соответствует ли путь местоположению или нет (полезно для настройки на основе пользовательского интерфейса на соответствие URL).

Если вы хотите настроить вывод рендеринга вашего компонента, вам нужно обернуть ваш компонент в функцию и использовать опцию render, чтобы передать вашему компоненту любые другие реквизиты, которые вы хотите, кроме match, location и history. Пример для иллюстрации:

import { BrowserRouter as Router } from 'react-router-dom'

const ButtonToNavigate = ({ title, history }) => (
  <button
    type="button"
    onClick={() => history.push('/my-new-location')}
  >
    {title}
  </button>
);

const SomeComponent = () => (
  <Route path="/" render={(props) => <ButtonToNavigate {...props} title="Navigate elsewhere" />} />
)    

const App = () => (
  <Router>
    <SomeComponent /> // Notice how in v4 we can have any other component interleaved
    <AnotherComponent />
  </Router>
);

2.- Использование withRouter HoC

Этот компонент более высокого порядка будет вводить те же реквизиты, что и Route. Тем не менее, это ограничивает то, что вы можете иметь только 1 HoC на файл.

import { withRouter } from 'react-router-dom'

const ButtonToNavigate = ({ history }) => (
  <button
    type="button"
    onClick={() => history.push('/my-new-location')}
  >
    Navigate
  </button>
);


ButtonToNavigate.propTypes = {
  history: React.PropTypes.shape({
    push: React.PropTypes.func.isRequired,
  }),
};

export default withRouter(ButtonToNavigate);

3.- Использование компонента Redirect

Рендеринг <Redirect> приведет к переходу на новое место. Но имейте в виду, что по умолчанию текущее местоположение заменяется новым, как перенаправления на стороне сервера (HTTP 3xx). Новое местоположение предоставляется to prop, который может быть строкой (URL-адрес для перенаправления) или объектом location. Если вместо этого вы хотите вставить новую запись в историю, передайте также опору push и установите для нее значение true
<Redirect to="/your-new-location" push />

4.- Доступ к router вручную через контекст

Немного обескуражен, потому что контекст все еще является экспериментальным API, и он может сломаться/измениться в будущих версиях React
const ButtonToNavigate = (props, context) => (
  <button
    type="button"
    onClick={() => context.router.history.push('/my-new-location')}
  >
    Navigate to a new location
  </button>
);

ButtonToNavigate.contextTypes = {
  router: React.PropTypes.shape({
    history: React.PropTypes.object.isRequired,
  }),
};

Само собой разумеется, есть и другие компоненты Router, предназначенные для не браузерных экосистем, такие как <NativeRouter> который реплицирует стек памяти в памяти и нацеливается на платформу React Native, доступную через react-router-native пакет <NativeRouter> react-router-native.

Для любой дальнейшей ссылки, не стесняйтесь взглянуть на официальные документы. Также есть видео, сделанное одним из соавторов библиотеки, которое представляет довольно интересное введение в Reaction-router v4, выделяя некоторые основные изменения.

  • 0
    Похоже, что v4 не имеет с withRouter
  • 2
    Даже 2. не работает.
Показать ещё 17 комментариев
103

Самый простой способ сделать это:

this.props.history.push("/new/url")

Примечание:

  • Вы можете передать history prop из родительского компонента до компонента, который вы хотите вызвать, если его недоступно.
  • 1
    спасибо это был действительно самый простой способ
  • 9
    Я использую 4.0 роутер, но на реквизите нет ключа истории. Как я могу это исправить?
Показать ещё 4 комментария
50

У меня была аналогичная проблема при переносе на React-Router v4, поэтому я попытаюсь объяснить свое решение ниже.

Пожалуйста, не рассматривайте этот ответ как правильный способ решить проблему, я полагаю, что есть хороший шанс, что лучше будет возникать, поскольку React Router v4 становится более зрелым и оставляет бета-версию (он может даже уже существовать, и я просто не сделал этого узнайте об этом).

Для контекста у меня была эта проблема, потому что я иногда использовал Redux-Saga для программного изменения объекта истории (скажем, когда пользователь успешно проходит проверку подлинности).

В документах React Router обратите внимание на компонент <Router>

  • 5
    Это изменение сработало для меня, но только потому, что я перемещаюсь за пределы компонента. Если бы я перемещался внутри компонента, такого как OP, я бы использовал подход, предложенный @rauliyohmc, используя реквизиты, передаваемые компонентом Route .
  • 2
    это рекомендуемый подход с 17.08.
Показать ещё 7 комментариев
31

TL; ДР:

if (navigate) {
  return <Redirect to="/" push={true} />
}

Простой и декларативный ответ заключается в том, что вам нужно использовать <Redirect to={URL} push={boolean} /> в сочетании с setState()

push: boolean -, когда true, перенаправление переместит новую запись в историю вместо замены текущего.


import { Redirect } from 'react-router'

class FooBar extends React.Component {
  state = {
    navigate: false
  }

  render() {
    const { navigate } = this.state

    // here is the important part
    if (navigate) {
      return <Redirect to="/" push={true} />
    }
   // ^^^^^^^^^^^^^^^^^^^^^^^

    return (
      <div>
        <button onClick={() => this.setState({ navigate: true })}>
          Home
        </button>
      </div>
    )
  }
}

Полный пример здесь. Подробнее здесь.

PS. В примере для инициализации состояния используется ES7 + Инициализаторы свойств. Посмотрите здесь, если вы заинтересованы.

  • 5
    Это должен быть принят ответ. Самое простое элегантное решение! +1 для @lustoykov
  • 0
    спасибо, я ценю это :)
Показать ещё 4 комментария
7

Шаг 1: импортировать только одну вещь:

    import {Route} from 'react-router-dom';

Шаг 2: В своем маршруте пройдите историю:

    <Route exact path='/posts/add' render={({history})  => (
      <PostAdd
        history={history}
      />
    .)}/>

Шаг 3: история принимается как часть реквизита в следующем компоненте, поэтому вы можете просто:

    this.props.history.push('/');

Это было легко и очень мощно.

6

Это работает:

import { withRouter } from 'react-router-dom';

const SomeComponent = withRouter(({ history }) => (
    <div onClick={() => history.push('/path/some/where')}>
        some clickable element
    </div>); 
);

export default SomeComponent;
6

также можно просто использовать реквизит: this.props.history.push('new_url')

  • 5
    Полезно только тогда, когда в компоненте непосредственно спускаются с маршрутизатора. Чтобы вы не передавали реквизиты истории каждому компоненту, в котором вам нужна эта функциональность.
  • 1
    Если у вас нет this.props.history в вашем компоненте, вы можете import {withRouter} from 'react-router-dom' а затем export default withRouter(MyComponent) (или const MyComponent = withRouter(...) ) и он вставит элемент history в реквизит вашего компонента.
5

Мой ответ похож на Alex's. Я не уверен, почему React-Router сделал это настолько бесполезно сложным. Почему я должен обернуть свой компонент с помощью HoC только для того, чтобы получить доступ к тому, что по существу является глобальным?

В любом случае, если вы посмотрите, как они реализовали <BrowserRouter>, это всего лишь небольшая оболочка вокруг history.

Мы можем вытащить эту историю, чтобы мы могли импортировать ее из любого места. Однако трюк заключается в том, что если вы выполняете рендеринг на стороне сервера, и вы пытаетесь выполнить import модуль истории, он не будет работать, потому что он использует API-интерфейсы только для браузера. Но это нормально, потому что мы обычно перенаправляем только в ответ на клик или какое-то другое событие на стороне клиента. Таким образом, возможно, ОК, чтобы подделать его:

// history.js
if(__SERVER__) {
    module.exports = {};
} else {
    module.exports = require('history').createBrowserHistory();
}

С помощью webpack мы можем определить некоторые вары, чтобы мы знали, в какой среде мы находимся:

plugins: [
    new DefinePlugin({
        '__SERVER__': 'false',
        '__BROWSER__': 'true', // you really only need one of these, but I like to have both
    }),

И теперь вы можете

import history from './history';

Из любого места. Он просто вернет пустой модуль на сервере.

Если вы не хотите использовать эти волшебные вары, вам просто нужно require в глобальном объекте, где он нужен (внутри обработчика событий). import не будет работать, потому что он работает только на верхнем уровне.

  • 5
    Черт, они сделали это так сложно.
  • 4
    Я полностью с тобой согласен. это так сложно просто для навигации
4

Я тестировал v4 в течение нескольких дней и... Я люблю его до сих пор! Это просто имеет смысл через некоторое время.

У меня также был тот же вопрос, и я нашел обращение к нему, как если бы он работал лучше всего (и может даже быть тем, как он предназначен). Он использует состояние, тернарный оператор и <Redirect>.

В конструкторе()

this.state = {
    redirectTo: null
} 
this.clickhandler = this.clickhandler.bind(this);

В render()

render(){
    return (
        <div>
        { this.state.redirectTo ?
            <Redirect to={{ pathname: this.state.redirectTo }} /> : 
            (
             <div>
               ..
             <button onClick={ this.clickhandler } />
              ..
             </div>
             )
         }

В clickhandler()

 this.setState({ redirectTo: '/path/some/where' });

Надеюсь, это поможет. Дайте мне знать.

  • 0
    Есть ли подводные камни с этим методом? В моем проекте с работает только когда я устанавливаю состояние в конструкторе, оттуда я могу перенаправить на любую страницу, которую я хочу. Но когда я устанавливаю состояние для события (например, изменился реквизит), я вижу метод рендеринга, вызванный с новым состоянием, но перенаправление не происходит, я вижу ту же страницу
  • 0
    Подводный камень в том, что он заменит историю, поэтому вы не сможете нанести ответный удар - поэтому, по сути, это не очень хорошее решение.
3

Поскольку нет другого способа справиться с этим ужасным дизайном, я написал общий компонент, который использует withRouter HOC. Ниже приведен пример обтекания элемента button, но вы можете изменить любой требуемый элемент:

import React from 'react';
import PropTypes from 'prop-types';
import { withRouter } from 'react-router-dom';

const NavButton = (props) => (
  <Button onClick={() => props.history.push(props.to)}>
    {props.children}
  </Button>
);

NavButton.propTypes = {
  history: PropTypes.shape({
    push: PropTypes.func.isRequired
  }),
  to: PropTypes.string.isRequired
};

export default withRouter(NavButton);

Использование:

<NavButton to="/somewhere">Click me</NavButton>
2

Я боролся с этим некоторое время - что-то настолько простое, но все же сложное, потому что ReactJS - это совсем другой способ писать веб-приложения, это очень чуждо нам пожилым людям!

Я создал отдельный компонент, чтобы абстрагировать беспорядок:

// LinkButton.js

import React from "react";
import PropTypes from "prop-types";
import {Route} from 'react-router-dom';

export default class LinkButton extends React.Component {

    render() {
        return (
            <Route render={({history}) => (
                <button {...this.props}
                       onClick={() => {
                           history.push(this.props.to)
                       }}>
                    {this.props.children}
                </button>
            )}/>
        );
    }
}

LinkButton.propTypes = {
    to: PropTypes.string.isRequired
};

Затем добавьте его к вашему методу render():

<LinkButton className="btn btn-primary" to="/location">
    Button Text
</LinkButton>
  • 0
    Я считаю это решение весьма полезным. Я копирую код. Дайте мне знать, что вы положили его на github - я вам напрямую приписываю.
1

Я думаю, что @rgommezz покрывает большинство случаев, за исключением одного, который я считаю очень важным.

// history is already a dependency or React Router, but if don't have it then try npm install save-dev history

import createHistory from "history/createBrowserHistory"

// in your function then call add the below 
const history = createHistory();
// Use push, replace, and go to navigate around.
history.push("/home");

Это позволяет мне написать простой сервис с действиями/вызовами, которые я могу вызвать для навигации по любому компоненту, который я хочу, не делая много HoC на моих компонентах...

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

1

Как иногда я предпочитаю переключать маршруты с помощью приложения, а затем кнопками, это минимальный рабочий пример, который работает для меня:

import { Component } from 'react'
import { BrowserRouter as Router, Link } from 'react-router-dom'

class App extends Component {
  constructor(props) {
    super(props)

    /** @type BrowserRouter */
    this.router = undefined
  }

  async handleSignFormSubmit() {
    await magic()
    this.router.history.push('/')
  }

  render() {
    return (
      <Router ref={ el => this.router = el }>
        <Link to="/signin">Sign in</Link>
        <Route path="/signin" exact={true} render={() => (
          <SignPage onFormSubmit={ this.handleSignFormSubmit } />
        )} />
      </Router>
    )
  }
}
0

Для тех из вас, кому требуется перенаправление перед полной инициализацией маршрутизатора с использованием React Router или React Router Dom вы можете предоставить перенаправление, просто получив доступ к объекту истории и вставив новое состояние в него в вашем конструкторе app.js Учтите следующее:

function getSubdomain(hostname) {
    let regexParse = new RegExp('[a-z\-0-9]{2,63}\.[a-z\.]{2,5}$');
    let urlParts = regexParse.exec(hostname);
    return hostname.replace(urlParts[0], '').slice(0, -1);
}

class App extends Component {

    constructor(props) {
        super(props);


        this.state = {
            hostState: true
        };

        if (getSubdomain(window.location.hostname).length > 0) {
            this.state.hostState = false;
            window.history.pushState('', '', './login');
        } else {
            console.log(getSubdomain(window.location.hostname));
        }

    }


    render() {
        return (

            <BrowserRouter>
                {this.state.hostState ? (
                    <div>
                        <Route path="/login" component={LoginContainer}/>
                        <Route path="/" component={PublicContainer}/>
                    </div>
                ) : (
                    <div>
                        <Route path="/login" component={LoginContainer}/>
                    </div>
                )

                }
            </BrowserRouter>)
    }


}

Здесь мы хотим изменить выходные Маршруты, зависящие от субдомена, взаимодействуя с объектом истории до того, как компонент отобразит, мы можем эффективно перенаправить, оставляя наши маршруты в такте.

window.history.pushState('', '', './login');

Ещё вопросы

Сообщество Overcoder
Наверх
Меню