Я только что заменил react-router
на v3 на v4.
Но я не уверен, как программно перемещаться в функции-члене Component
.
В функции handleClick()
я хочу перейти к /path/some/where
после обработки некоторых данных.
Я использовал это:
import { browserHistory } from 'react-router'
browserHistory.push('/path/some/where')
Но я не могу найти такие интерфейсы в v4.
Как я могу перемещаться с помощью v4?
Если вы ориентируетесь на среды браузера, вам нужно использовать пакет 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
. Вы можете проверить весь список свойств и методов здесь.
Это происходит потому, что react-router
передает location
компонентам, используя контекстную модель.
И connect, и наблюдатель создают компоненты, чьи методы shouldComponentUpdate проводят поверхностное сравнение своих текущих и следующих компонентов. Эти компоненты будут перерисовываться только тогда, когда изменилась хотя бы одна опора. Это означает, что для того, чтобы они обновлялись при изменении местоположения, им необходимо предоставить реквизит, который изменяется при изменении местоположения.
2 подхода к решению этой проблемы:
<Route/>
pathte <Route/>
. Текущий объект location
является одним из объектов, которые <Route>
передает компоненту, который он отображает.withRouter
, который на самом деле имеет тот же эффект и вводит location
как и пропКроме этого, существует четыре способа программной навигации, упорядоченные по рекомендации:
<Route>
<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>
);
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);
Redirect
<Redirect>
приведет к переходу на новое место. Но имейте в виду, что по умолчанию текущее местоположение заменяется новым, как перенаправления на стороне сервера (HTTP 3xx). Новое местоположение предоставляется to
prop, который может быть строкой (URL-адрес для перенаправления) или объектом location
. Если вместо этого вы хотите вставить новую запись в историю, передайте также опору push
и установите для нее значение true
<Redirect to="/your-new-location" push />
router
вручную через контекст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, выделяя некоторые основные изменения.
v4
не имеет с withRouter
2.
не работает.
Самый простой способ сделать это:
this.props.history.push("/new/url")
Примечание:
history
prop
из родительского компонента до компонента, который вы хотите вызвать, если его недоступно.У меня была аналогичная проблема при переносе на React-Router v4, поэтому я попытаюсь объяснить свое решение ниже.
Пожалуйста, не рассматривайте этот ответ как правильный способ решить проблему, я полагаю, что есть хороший шанс, что лучше будет возникать, поскольку React Router v4 становится более зрелым и оставляет бета-версию (он может даже уже существовать, и я просто не сделал этого узнайте об этом).
Для контекста у меня была эта проблема, потому что я иногда использовал Redux-Saga
для программного изменения объекта истории (скажем, когда пользователь успешно проходит проверку подлинности).
В документах React Router обратите внимание на компонент <Router>
Route
.
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 + Инициализаторы свойств. Посмотрите здесь, если вы заинтересованы.
Шаг 1: импортировать только одну вещь:
import {Route} from 'react-router-dom';
Шаг 2: В своем маршруте пройдите историю:
<Route exact path='/posts/add' render={({history}) => (
<PostAdd
history={history}
/>
.)}/>
Шаг 3: история принимается как часть реквизита в следующем компоненте, поэтому вы можете просто:
this.props.history.push('/');
Это было легко и очень мощно.
Это работает:
import { withRouter } from 'react-router-dom';
const SomeComponent = withRouter(({ history }) => (
<div onClick={() => history.push('/path/some/where')}>
some clickable element
</div>);
);
export default SomeComponent;
также можно просто использовать реквизит: this.props.history.push('new_url')
this.props.history
в вашем компоненте, вы можете import {withRouter} from 'react-router-dom'
а затем export default withRouter(MyComponent)
(или const MyComponent = withRouter(...)
) и он вставит элемент history
в реквизит вашего компонента.
Мой ответ похож на 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
не будет работать, потому что он работает только на верхнем уровне.
Я тестировал 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' });
Надеюсь, это поможет. Дайте мне знать.
Поскольку нет другого способа справиться с этим ужасным дизайном, я написал общий компонент, который использует 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>
Я боролся с этим некоторое время - что-то настолько простое, но все же сложное, потому что 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>
Я думаю, что @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 на моих компонентах...
Непонятно, почему никто не предоставил это решение раньше. Я надеюсь, что это поможет, и если вы обнаружите какие-либо проблемы с этим, пожалуйста, дайте мне знать.
Как иногда я предпочитаю переключать маршруты с помощью приложения, а затем кнопками, это минимальный рабочий пример, который работает для меня:
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>
)
}
}
Для тех из вас, кому требуется перенаправление перед полной инициализацией маршрутизатора с использованием 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');
Component
? Например, внутри редукционных действий.