Як перейти на історію в React Router v4?


360

У поточній версії React Router (v3) я можу прийняти відповідь сервера та використовувати його browserHistory.push для переходу на відповідну сторінку відповідей. Однак це не доступно в v4, і я не впевнений, що є правильним способом впоратися з цим.

У цьому прикладі, використовуючи Redux, компоненти / app-product-form.js викликає, this.props.addProduct(props)коли користувач подає форму. Коли сервер повертає успіх, користувач переходить на сторінку Кошик.

// actions/index.js
export function addProduct(props) {
  return dispatch =>
    axios.post(`${ROOT_URL}/cart`, props, config)
      .then(response => {
        dispatch({ type: types.AUTH_USER });
        localStorage.setItem('token', response.data.token);
        browserHistory.push('/cart'); // no longer in React Router V4
      });
}

Як я можу зробити переадресацію на сторінку кошика з функції React Router v4?


Просто додати до цього з останнього запропонованого рішення та з пропозицій у питаннях React Router на GitHub, використовуючи contextдля передачі того, що вам потрібно вручну, це "не йти". Якщо я не автор бібліотеки, не слід використовувати її. Насправді Facebook рекомендує проти цього.
Кріс

@Chris Ви знайшли рішення для цього? Мені потрібно натиснути на інший компонент у дії, те саме, що ви тут пояснили
Mr.G

@ Mr.G, на жаль, у мене немає. Останнє, що я прочитав, була команда React Training, яка підтримує, щоб React Router мав доступний пакунок для скорочення. Мені не пощастило змусити його працювати, і вони не вклали багато зусиль у його вирішення.
Кріс

Чому ми не можемо використовувати windows.location.href = URL? Чи є щось неправильне в його використанні для зміни URL-адреси та переадресації?
Шань

Я не бачу, чому ні, але варіант використання historyтакож працює для React Native як варіант, а також додаток для підтримки застарілих браузерів.
Кріс

Відповіді:


308

Ви можете використовувати history методи поза своїми компонентами. Спробуйте наступним чином.

Спочатку створимо historyоб'єкт, використовуючи пакет історії :

// src/history.js

import { createBrowserHistory } from 'history';

export default createBrowserHistory();

Потім загорніть його <Router>( будь ласка, зверніть увагу , ви повинні використовувати import { Router }замість import { BrowserRouter as Router }):

// src/index.jsx

// ...
import { Router, Route, Link } from 'react-router-dom';
import history from './history';

ReactDOM.render(
  <Provider store={store}>
    <Router history={history}>
      <div>
        <ul>
          <li><Link to="/">Home</Link></li>
          <li><Link to="/login">Login</Link></li>
        </ul>
        <Route exact path="/" component={HomePage} />
        <Route path="/login" component={LoginPage} />
      </div>
    </Router>
  </Provider>,
  document.getElementById('root'),
);

Змініть своє поточне місцезнаходження з будь-якого місця, наприклад:

// src/actions/userActionCreators.js

// ...
import history from '../history';

export function login(credentials) {
  return function (dispatch) {
    return loginRemotely(credentials)
      .then((response) => {
        // ...
        history.push('/');
      });
  };
}

UPD : Ви також можете побачити дещо інший приклад у FAQ React Router FAQ .


24
Я намагався зробити саме так, як сказав @OlegBelostotsky, але після history.push('some path')цього URL змінюється, але сторінка не змінюється. Я маю поставити window.location.reload()після цього в деяких частинах свого коду просто для того, щоб він працював. Однак, в одному випадку я повинен утримувати дерево стану redux, а перезавантаження знищує його. Будь-яке інше рішення?
sdabrutas

2
@idunno Спробуйте використовувати withRouterкомпонент вищого порядку.
Олег Білостоцький

Це призвело до помилки із зазначенням: createBrowserHistory - це не функція. Що я можу зробити?
AKJ

@AKJ Можливо, import createHistory from 'history/createBrowserHistory'; export default createHistory();буде робота.
Олег Білостоцький

1
Вибачте за голосування :). Хоча це також має працювати, правильний спосіб вирішити це, як відповів Кріс: stackoverflow.com/a/42716055/491075 .
gion_13

340

Реактивний маршрутизатор v4 принципово відрізняється від версії v3 (і раніше), і ви не можете цього зробити browserHistory.push() як раніше.

Ця дискусія здається пов’язаною, якщо ви хочете отримати більше інформації:

  • Створення нового browserHistoryне буде працювати, оскільки <BrowserRouter>створює власний екземпляр історії, і слухає зміни в цьому. Отже, інший примірник змінить URL-адресу, але не оновить<BrowserRouter> .
  • browserHistory не піддається впливу реактора-маршрутизатора в v4, тільки в v2.

Натомість у вас є кілька варіантів для цього:

  • Використовуйте withRouterкомпонент високого порядку

    Натомість вам слід використовувати withRouterкомпонент високого порядку та обернути його на той компонент, який підштовхне історію. Наприклад:

    import React from "react";
    import { withRouter } from "react-router-dom";
    
    class MyComponent extends React.Component {
      ...
      myFunction() {
        this.props.history.push("/some/Path");
      }
      ...
    }
    export default withRouter(MyComponent);

    Перегляньте офіційну документацію для отримання додаткової інформації:

    Ви можете отримати доступ до historyвластивостей об'єкта та найближчих <Route>до нього matchчерез компонент вищого порядку withRouter. withRouter буде знову зробити свій компонент кожного разу , коли маршрут змінюється з тим же реквізитом , як <Route>робить реквізит: { match, location, history }.


  • Використовуйте contextAPI

    Використання контексту може бути одним з найпростіших рішень, але, будучи експериментальним API, він нестабільний і не підтримується. Використовуйте його лише тоді, коли все інше виходить з ладу. Ось приклад:

    import React from "react";
    import PropTypes from "prop-types";
    
    class MyComponent extends React.Component {
      static contextTypes = {
        router: PropTypes.object
      }
      constructor(props, context) {
         super(props, context);
      }
      ...
      myFunction() {
        this.context.router.history.push("/some/Path");
      }
      ...
    }

    Погляньте на офіційну документацію в контексті:

    Якщо ви хочете, щоб ваша програма була стабільною, не використовуйте контекст. Це експериментальний API, і він, ймовірно, може зламатись у майбутніх випусках React.

    Якщо ви наполягаєте на використанні контексту, незважаючи на ці застереження, спробуйте ізолювати використання контексту на невеликій області та уникайте використання API контексту безпосередньо, коли це можливо, щоб було легше оновити, коли API зміниться.


9
Так, я спробував. Спасибі за запитання. :-) Отже, як ви вводите контекст у цю дію? Поки що це не визначено.
Кріс

1
Я досліджував цю тему вже кілька днів і не зміг змусити цю роботу. Навіть використовуючи приклад вище, я продовжую отримувати маршрутизатор, не визначений у контексті. В даний час я використовую react v15.5.10, react-router-dom v4.1.1, prop-type 15.5.10. Документація навколо цього є рідкою і не дуже зрозумілою.
Світ

5
@Сто це має спрацюватиthis.context.router.history.push('/path');
Тушар Хатівада

1
@Chris Дійсно, якщо ви спробуєте створити об'єкт історії та використати його самостійно, він змінить URL-адресу, але не буде відображати компонент - як ви згадували вище. Але, як використовувати "history.push" і поза компонентом, а також примусово надавати компонент?
круто

52
Це не дає відповіді на запитання, яке полягає в тому, як отримати доступ до history.push ВИХІД компонента. Використання withRouter або контексту не є варіантами, коли знаходиться поза компонентом.
SunshinyDoyle

49

Тепер за допомогою react-router v5 ви можете використовувати гак життєвого циклу useHistory таким чином:

import { useHistory } from "react-router-dom";

function HomeButton() {
  let history = useHistory();

  function handleClick() {
    history.push("/home");
  }

  return (
    <button type="button" onClick={handleClick}>
      Go home
    </button>
  );
}

читати більше на сайті: https://reacttraining.com/react-router/web/api/Hooks/usehistory


1
Це дуже вітальне оновлення! Дякую!
Кріс

Чи є мені якийсь спосіб встановити це, я називаю наступне, let history = useHistory();але отримую Object is not callableпомилку, коли я намагався бачити, яку користь використовує Історія, console.log(useHistory)вона виявляється як невизначена. користування"react-router-dom": "^5.0.1"
steff_bdh

@steff_bdh вам потрібно оновити його у файлі yout package.json, щоб "реагувати на маршрутизатор-дом": "^ 5.0.1" та запустити "npm install"
Хаді Абу,

2
Приємно, але не можна використовувати гачок у класах дій редукційних дій, оскільки вони не є компонентом / функцією React
Jay

Як би ви використовували це для перенаправлення під час входу за допомогою (async). Ось питання => stackoverflow.com/questions/62154408 / ...
theairbend3r

29

Найпростішим способом у React Router 4 є використання

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

Але для використання цього методу ваш існуючий компонент повинен мати доступ до historyоб'єкта. Ми можемо отримати доступ за допомогою

  1. Якщо ваш компонент пов'язаний Routeбезпосередньо, то ваш компонент вже має доступ до historyоб'єкта.

    наприклад:

    <Route path="/profile" component={ViewProfile}/>

    Тут ViewProfileє доступ до history.

  2. Якщо не підключено Routeбезпосередньо.

    наприклад:

    <Route path="/users" render={() => <ViewUsers/>}

    Тоді ми маємо використовувати withRouter, більш велике замовлення, для викривлення існуючого компонента.

    Всередині ViewUsers компонент

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

    • export default withRouter(ViewUsers);

    Ось тепер, ваш ViewUsersкомпонент має доступ до historyоб’єкта.

ОНОВЛЕННЯ

2- у цьому випадку пройдіть весь маршрут propsдо свого компонента, і тоді ми зможемо отримати доступ this.props.historyіз компонента навіть безHOC

наприклад:

<Route path="/users" render={props => <ViewUsers {...props} />}

1
Відмінно! Ваш другий метод також зробив для мене хитрість, оскільки мій компонент (який потребує доступу this.props.history) походить від HOC, а це означає, що він Route, як ви пояснюєте, не пов'язаний безпосередньо .
cjauvin

25

Ось як я це зробив:

import React, {Component} from 'react';

export default class Link extends Component {
    constructor(props) {
        super(props);
        this.onLogout = this.onLogout.bind(this);
    }
    onLogout() {
        this.props.history.push('/');
    }
    render() {
        return (
            <div>
                <h1>Your Links</h1>
                <button onClick={this.onLogout}>Logout</button>
            </div>
        );
    }
}

Використовуйте this.props.history.push('/cart');для переадресації на сторінку кошика, вона буде збережена в об'єкті історії.

Насолоджуйтесь, Майкл.


2
Так, це виглядає як всередині компонентів, на які ти можеш відштовхуватися. Єдиний спосіб вплинути на навігацію за межами компонента - це перенаправлення.
Кріс

14
Це не дає відповіді на запитання, яке полягає в тому, як отримати доступ до history.push ВИХІД компонента. Використання this.props.history не є варіантом, коли знаходиться поза компонентом.
SunshinyDoyle

22

Відповідно до документації React Router v4 - сеанс глибокої інтеграції Redux

Потрібна глибока інтеграція для:

"вміти орієнтуватися шляхом диспетчерських дій"

Однак вони рекомендують такий підхід як альтернативу "глибокій інтеграції":

"Замість того, щоб диспетчеризувати дії для навігації, ви можете передати об’єкт історії, який надається для маршрутизації компонентів до ваших дій, і переміститись з ним туди."

Таким чином, ви можете обернути свій компонент за допомогою компонента високого порядку withRouter:

export default withRouter(connect(null, { actionCreatorName })(ReactComponent));

який передасть API історії реквізитам. Тож ви можете назвати творця дії, який передає історію, як парам. Наприклад, всередині вашого ReactComponent:

onClick={() => {
  this.props.actionCreatorName(
    this.props.history,
    otherParams
  );
}}

Потім всередині ваших дій / index.js:

export function actionCreatorName(history, param) {
  return dispatch => {
    dispatch({
      type: SOME_ACTION,
      payload: param.data
    });
    history.push("/path");
  };
}

19

Пишне питання, зайняло у мене досить багато часу, але врешті-решт я вирішив це таким чином:

Оберніть контейнер withRouterі передайте історію вашим діям у mapDispatchToPropsфункції. У дії використовуйте history.push ('/ url') для навігації.

Дія:

export function saveData(history, data) {
  fetch.post('/save', data)
     .then((response) => {
       ...
       history.push('/url');
     })
};

Контейнер:

import { withRouter } from 'react-router-dom';
...
const mapDispatchToProps = (dispatch, ownProps) => {
  return {
    save: (data) => dispatch(saveData(ownProps.history, data))}
};
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Container));

Це справедливо для React v4.x маршрутизатора .


Спасибі, ваше рішення withRouter працює з typecript, але його досить повільно порівняно з import { createBrowserHistory } from 'history' будь-якою будь-якою ідеєю, будь ласка?
Джей

7

this.context.history.push не вийде.

Мені вдалося натиснути так:

static contextTypes = {
    router: PropTypes.object
}

handleSubmit(e) {
    e.preventDefault();

    if (this.props.auth.success) {
        this.context.router.history.push("/some/Path")
    }

}

9
Це не дає відповіді на запитання, яке полягає в тому, як отримати доступ до history.push ВИХІД компонента. Використання this.context не є варіантом, коли знаходиться поза компонентом.
SunshinyDoyle

6

У цьому випадку ви передаєте реквізит своєму груднику. Тож можна просто зателефонувати

props.history.push('/cart')

Якщо це не так, ви все одно можете передавати історію зі свого компонента

export function addProduct(data, history) {
  return dispatch => {
    axios.post('/url', data).then((response) => {
      dispatch({ type: types.AUTH_USER })
      history.push('/cart')
    })
  }
}

6

Я пропоную ще одне рішення на випадок, якщо воно варте когось іншого.

У мене є history.jsфайл, у якому я є наступним:

import createHistory from 'history/createBrowserHistory'
const history = createHistory()
history.pushLater = (...args) => setImmediate(() => history.push(...args))
export default history

Далі, в моєму Root, де я визначаю свій маршрутизатор, я використовую наступне:

import history from '../history'
import { Provider } from 'react-redux'
import { Router, Route, Switch } from 'react-router-dom'

export default class Root extends React.Component {
  render() {
    return (
     <Provider store={store}>
      <Router history={history}>
       <Switch>
        ...
       </Switch>
      </Router>
     </Provider>
    )
   }
  }

Нарешті, на моїй сторінці actions.jsя імпортую історію та використовую pushLater

import history from './history'
export const login = createAction(
...
history.pushLater({ pathname: PATH_REDIRECT_LOGIN })
...)

Таким чином, я можу підштовхнути до нових дій після дзвінків API.

Сподіваюся, це допомагає!


4

Якщо ви використовуєте Redux, то я б рекомендував використовувати npm пакет react -router-redux . Це дозволяє відправляти навігаційні дії магазину Redux.

Ви повинні створити магазин, як описано у файлі Readme .

Найпростіший випадок використання:

import { push } from 'react-router-redux'

this.props.dispatch(push('/second page'));

Випадок другого використання з контейнером / компонентом:

Контейнер:

import { connect } from 'react-redux';
import { push } from 'react-router-redux';

import Form from '../components/Form';

const mapDispatchToProps = dispatch => ({
  changeUrl: url => dispatch(push(url)),
});

export default connect(null, mapDispatchToProps)(Form);

Компонент:

import React, { Component } from 'react';
import PropTypes from 'prop-types';

export default class Form extends Component {
  handleClick = () => {
    this.props.changeUrl('/secondPage');
  };

  render() {
    return (
      <div>
        <button onClick={this.handleClick}/>
      </div>Readme file
    );
  }
}

1
Це не працює з функцією react-router-redux, якщо ви не використовуєте nextверсію, яка наразі ще розробляється!
froginvasion

4

Я зміг досягти цього за допомогою bind(). Я хотів натиснути кнопку index.jsx, розмістити деякі дані на сервері, оцінити відповідь та переспрямувати на success.jsx. Ось як я це розробив ...

index.jsx:

import React, { Component } from "react"
import { postData } from "../../scripts/request"

class Main extends Component {
    constructor(props) {
        super(props)
        this.handleClick = this.handleClick.bind(this)
        this.postData = postData.bind(this)
    }

    handleClick() {
        const data = {
            "first_name": "Test",
            "last_name": "Guy",
            "email": "test@test.com"
        }

        this.postData("person", data)
    }

    render() {
        return (
            <div className="Main">
                <button onClick={this.handleClick}>Test Post</button>
            </div>
        )
    }
}

export default Main

request.js:

import { post } from "./fetch"

export const postData = function(url, data) {
    // post is a fetch() in another script...
    post(url, data)
        .then((result) => {
            if (result.status === "ok") {
                this.props.history.push("/success")
            }
        })
}

success.jsx:

import React from "react"

const Success = () => {
    return (
        <div className="Success">
            Hey cool, got it.
        </div>
    )
}

export default Success

Таким чином , шляхом зв'язування thisз postDataв index.jsxя був в змозі отримати доступ this.props.historyв request.js... то я можу повторно використовувати цю функцію в різних компонентах, просто щоб переконатися , що я пам'ятаю , щоб включити this.postData = postData.bind(this)в constructor().


3

Ось мій злом (це мій файл на кореневому рівні, з невеликим редуксом, змішаним там - хоча я не використовую react-router-redux):

const store = configureStore()
const customHistory = createBrowserHistory({
  basename: config.urlBasename || ''
})

ReactDOM.render(
  <Provider store={store}>
    <Router history={customHistory}>
      <Route component={({history}) => {
        window.appHistory = history
        return (
          <App />
        )
      }}/>
    </Router>
  </Provider>,
  document.getElementById('root')
)

Тоді я можу використовувати window.appHistory.push() де завгодно (наприклад, у своїх функціях / магазинах скорочення / саги тощо), я сподівався, що я можу просто використати, window.customHistory.push()але чомусь react-routerніколи не здавалося оновитись, хоча змінився URL. Але таким чином я використовую EXACT react-router. Я не люблю розміщувати речі в глобальному масштабі, і це одна з небагатьох речей, з якими я б це робив. Але це краще, ніж будь-яка інша альтернатива, яку я бачив ІМО.


3

Використовуйте зворотний виклик. Це працювало для мене!

export function addProduct(props, callback) {
  return dispatch =>
    axios.post(`${ROOT_URL}/cart`, props, config)
    .then(response => {
    dispatch({ type: types.AUTH_USER });
    localStorage.setItem('token', response.data.token);
    callback();
  });
}

В компоненті вам просто потрібно додати зворотний дзвінок

this.props.addProduct(props, () => this.props.history.push('/cart'))

3

так що я це роблю: - замість перенаправлення з використанням history.push, я просто використовую Redirectкомпонент з. react-router-dom При використанні цього компонента ви можете просто пройти push=true, і він подбає про інше

import * as React from 'react';
import { Redirect } from 'react-router-dom';
class Example extends React.Component {
  componentDidMount() {
    this.setState({
      redirectTo: '/test/path'
    });
  }

  render() {
    const { redirectTo } = this.state;

    return <Redirect to={{pathname: redirectTo}} push={true}/>
  }
}

це правильне значення, яке не порушує цикл візуалізації
реакцій

1

Роутер V4 React тепер дозволяє використовувати реквізит історії, як показано нижче:

this.props.history.push("/dummy",value)

Тоді до цього значення можна отримати доступ, де не доступна опора місцеположення як state:{value}не компонентний стан.


1
Це не дає відповіді на запитання, яке полягає в тому, як отримати доступ до history.push ВИХІД компонента. Використання this.props.history не є варіантом, коли знаходиться поза компонентом.
Аркадій

0

Ви можете використовувати його так, як я це роблю для входу в систему та різноманітних речей

class Login extends Component {
  constructor(props){
    super(props);
    this.login=this.login.bind(this)
  }


  login(){
this.props.history.push('/dashboard');
  }


render() {

    return (

   <div>
    <button onClick={this.login}>login</login>
    </div>

)

0
/*Step 1*/
myFunction(){  this.props.history.push("/home"); }
/**/
 <button onClick={()=>this.myFunction()} className={'btn btn-primary'}>Go 
 Home</button>

Не потрібно жодного імпорту!
Девід Багдасарян

1
Хоча цей код може відповісти на питання, надаючи додатковий контекст щодо того, чому та / або як цей код відповідає на питання, покращує його довгострокове значення.
rollstuhlfahrer

0

крок один перегорніть додаток у маршрутизатор

import { BrowserRouter as Router } from "react-router-dom";
ReactDOM.render(<Router><App /></Router>, document.getElementById('root'));

Тепер весь мій додаток матиме доступ до BrowserRouter. Крок другий я імпортую маршрут, а потім передаю ці реквізити. Можливо, в одному з ваших основних файлів.

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

//lots of code here

//somewhere in my render function

    <Route
      exact
      path="/" //put what your file path is here
      render={props => (
      <div>
        <NameOfComponent
          {...props} //this will pass down your match, history, location objects
        />
      </div>
      )}
    />

Тепер, якщо я запускаю console.log (this.props) у своєму компоненті js-файл, я повинен отримати щось, що виглядає приблизно так

{match: {…}, location: {…}, history: {…}, //other stuff }

Крок 2 Я можу отримати доступ до об’єкта історії, щоб змінити своє місцезнаходження

//lots of code here relating to my whatever request I just ran delete, put so on

this.props.history.push("/") // then put in whatever url you want to go to

Крім того, я просто студент з кодування завантажувальної програми, тому я не фахівець, але я знаю, що ви також можете використовувати

window.location = "/" //wherever you want to go

Виправте мене, якщо я помиляюся, але коли я перевірив це, він перезавантажив всю сторінку, яка, на мою думку, перемогла всю точку використання React.


0

Створіть спеціальний Routerвласний browserHistory:

import React from 'react';
import { Router } from 'react-router-dom';
import { createBrowserHistory } from 'history';

export const history = createBrowserHistory();

const ExtBrowserRouter = ({children}) => (
  <Router history={history} >
  { children }
  </Router>
);

export default ExtBrowserRouter

Далі в корені, де ви визначаєте свій Router, використовуйте наступне:

import React from 'react';       
import { /*BrowserRouter,*/ Route, Switch, Redirect } from 'react-router-dom';

//Use 'ExtBrowserRouter' instead of 'BrowserRouter'
import ExtBrowserRouter from './ExtBrowserRouter'; 
...

export default class Root extends React.Component {
  render() {
    return (
      <Provider store={store}>
        <ExtBrowserRouter>
          <Switch>
            ...
            <Route path="/login" component={Login}  />
            ...
          </Switch>
        </ExtBrowserRouter>
      </Provider>
    )
  }
}

Нарешті, імпортуйте historyтам, де вам це потрібно, і використовуйте:

import { history } from '../routers/ExtBrowserRouter';
...

export function logout(){
  clearTokens();      
  history.push('/login'); //WORKS AS EXPECTED!
  return Promise.reject('Refresh token has expired');
}

0

Якщо ви хочете використовувати історію під час передачі функції як значення на підпору компонента, за допомогою реактора-маршрутизатора 4 ви можете просто знищити historyопору в атрибуті візуалізації <Route/>компонента, а потім використовуватиhistory.push()

    <Route path='/create' render={({history}) => (
      <YourComponent
        YourProp={() => {
          this.YourClassMethod()
          history.push('/')
        }}>
      </YourComponent>
    )} />

Примітка. Для цього вам слід обгорнути компонент BrowserRouter React Router через ваш кореневий компонент (наприклад, який може бути в index.js)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.