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

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

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

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

Ответ 1

Если вы ориентируетесь на среды браузера, вам нужно использовать пакет 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, выделяя некоторые основные изменения.

Ответ 2

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

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

Примечание:

  • Вы можете передать history prop из родительского компонента до компонента, который вы хотите вызвать, если его недоступно.

Ответ 3

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

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

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

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

Ответ 4

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

Вы также можете просто использовать реквизит для доступа к объекту истории: this.props.history.push('new_url')

Ответ 6

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

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

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

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

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

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

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

Ответ 7

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

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

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

export default SomeComponent;

Ответ 8

Мой ответ похож на 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 не будет работать, потому что он работает только на верхнем уровне.

Ответ 9

Я тестировал 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' });

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

Ответ 10

Я боролся с этим некоторое время - что-то настолько простое, но все же сложное, потому что 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>

Ответ 11

Я думаю, что @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 на моих компонентах...

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

Ответ 12

Поскольку нет другого способа справиться с этим ужасным дизайном, я написал общий компонент, который использует 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>

Ответ 13

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

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

Ответ 14

Для тех из вас, кому требуется перенаправление перед полной инициализацией маршрутизатора с использованием 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');

Ответ 15

Вы можете использовать ловушку useHistory, чтобы получить экземпляр history, а затем перемещаться с помощью history.push('...').

...
import {useHistory} from 'react-router-dom';

const MyComponent = () => {
  var history = useHistory();

  return (
    <button onClick={() => history.push('/about')}>
      press me
    </button>
  );
}

Хук useHistory предоставляет вам доступ к экземпляру истории, который вы можете использовать для навигации.
👉 https://reacttraining.com/react-router/web/api/Hooks/usehistory