У людей постоянно складывается впечатление, что мы не поддерживаем React Router, или что для его работы нужно что-то особенное, например, redux-react-router , или даже что он не работает до React 0.14.
Вы можете использовать Redux с React Router 0.13 или 1.0, как сегодня.
(Кстати, это было верно с момента первого выпуска Redux.)
Некоторые функции, такие как путешествия во времени, должны будут подождать поддержки со стороны RR, но это не имеет отношения к основной проблеме. Люди сбиваются с толку, думая, что сегодня они не могут использовать маршрутизацию, что просто неправильно.
В реальном примере , включенном в этот репозиторий, используется React Router. Все, что вам нужно, это обернуть <Router>
в <Provider>
, точно так же, как вы обернули бы свой компонент верхнего уровня в приложении без маршрутизатора.
Если вы хотите перейти от создателей действий, передайте экземпляр router
в качестве параметра создателям действий и вызовите его методы, когда захотите. Если вы хотите прочитать состояние маршрутизатора из хранилища Redux, запустите действие при изменении маршрута и напишите редюсер для его обработки. Вот и все!
react-redux-router — это эксперимент по созданию более естественного API, в котором вы просто отправляете действия и читаете состояние из хранилища, которое автоматически подключается к экземпляру маршрутизатора, но вам не нужно его использовать, или ждать, пока он станет стабильным! Это только эксперимент.
Нам нужно это в документах.
Первоначально я написал пример рендеринга на стороне сервера, чтобы использовать реагирующий маршрутизатор в универсальном контексте. Было высказано предположение, что использование с реактивным маршрутизатором может стать отдельным документом. Возможно, мы могли бы добавить раздел в документацию?
Кроме того, как вы относитесь к добавлению примера, который показывает повсеместное использование маршрутизатора React?
Я думаю, было бы неплохо изменить пример real-world
, сделав его универсальным. Таким образом, вам не нужно ничего создавать с нуля.
Если вы хотите перейти от создателей действий, передайте экземпляр маршрутизатора в качестве параметра создателям действий.
Просто хотел уточнить, это работает только в 0.13, так как 1.0beta не имеет концепции createRouter
(пока?), верно?
Вы можете сделать это с помощью 1.0.0-beta3 из ваших компонентов React.
class Thing extends Component {
static contextTypes = {
router: PropTypes.object
}
handleThing() {
this.props.actionCreator(this.context.router);
}
}
@timdorr Безопасно ли использовать this.context
? У меня сложилось впечатление, что он не предназначен для наружного применения.
Да, это просто недокументировано, а не небезопасно. В 0.14 он немного изменился, но не так, чтобы это сломалось. Я полагаю, что это будет задокументировано в какой-то момент в ближайшее время.
@timdorr Означает ли это также, что в 1.0.0-beta3 можно перейти на другой URL-адрес от создателя действия?
Да, если вы передаете экземпляр маршрутизатора создателю действия, вы можете делать с ним все, что хотите, включая переходы.
Я собрал это:
const loginProps = {
handleLogin: ({email, password}) => store.dispatch(userLogin({email, password})),
};
const routes = (
<Route path="/" handler={App}>
<Route path="login" handler={wrapper(Login, loginProps)} />
<Route handler={authSection}>
<Route path="" handler={Index} />
<Route path="users" handler={wrapper(Users, (() => store.dispatch(getUsers())))} />
<Route path="logout" handler={wrapper(Login, (() => store.dispatch(userLogout())))} />
</Route>
</Route>
);
const router = createRouter({
location: HistoryLocation,
routes,
});
store.dispatch(receiveRouter({router}));
Warning: Failed Context Types: Required context `store` was not specified in `SmartComponent(TodoApp)`. Check the render method of `Router`.
Что может быть не так?
PS: RR 1.0.0-beta3
@gyzerok Убедитесь, что вы обернули весь () => <Router>stuff</Router>
в <Provider>
, как это делает пример real-world
.
@gaearon да, конечно. Я оборачиваю его не в Provider, а в свой собственный компонент, который по большей части является копипастом вашего Provider. Разница в том, что я не передаю ему магазин, а создаю магазин внутри него.
@gyzerok Трудно сказать, что не так, не видя кода. (И, пожалуйста, создайте отдельную проблему, react-redux
— хорошее место.)
Спасибо за пример real-wolrd
! Но как быть с AsyncProps
? Похоже, манипуляции с двойным контекстом не будут работать вместе.
import React from 'react';
import {createStore} from 'redux';
import {Provider} from 'react-redux';
import {Router, Route} from 'react-router';
import BrowserHistory from 'react-router/lib/BrowserHistory';
import AsyncProps from 'react-router/lib/experimental/AsyncProps';
import App from './containers/App';
import reducers from './reducers';
const store = createStoreWithMiddleware(reducers);
const history = new BrowserHistory();
React.render(
<Provider store={store}>
{() =>
<Router history={history} createElement={AsyncProps.createElement}>
<Route component={AsyncProps}>
<Route path="/" component={App} />
</Route>
</Router>
}
</Provider>,
document.body
);
и App.js
import React from 'react';
import {connect} from 'react-redux';
let App = React.createClass({
statics: {
loadProps(params, cb) {
// have to call this with AsyncProps
}
},
displayName: 'App',
render() {
return <div children="this is app" />
}
});
export default connect(state => state)(App);
Он будет работать без обёртки connect
, но и redux
тоже не будет. Кто-нибудь сталкивался с этой проблемой?
Или может быть какой-то другой способ приостановить навигацию до загрузки данных?
Статика - это просто статика. Ставить их можно на что угодно, включая результат connect()
.
let App = React.createClass({
displayName: 'App',
render() {
return <div children="this is app" />
}
});
App = connect(state => state)(App);
App.loadProps = function loadProps(params, cb) {
// have to call this with AsyncProps
}
export default App;
@gaearon извините, пример неполный. Я попытался расширить результат connect
с отсутствующими статическими реквизитами, но настоящая проблема с контекстом. Дайте мне немного времени, я отправлю весь пример в отдельный репозиторий.
Еще одна вещь, которую я сейчас исследую, — это хранение параметров в хранилище избыточности четким и ненавязчивым способом. Попробовав несколько подходов, я написал:
<Route
component={OrderDetails}
path='/orders/:orderId'
onEnter={({params}) => store.dispatch(setCurrentOrder(params.orderId))}
/>
поэтому можно ссылаться на параметры из селекторов, как показано ниже:
export const OrderDetails = state => {
const {order} = state;
return {
order: order.details.get(order.currentOrderId),
orderId: order.currentOrderId,
isLoading: order.isLoadingDetails,
error: order.detailsLoadingError
};
};
Это, вероятно, изменится, как только будет выпущена более стабильная версия react-redux-router
.
Хорошие новости: React Router 1.0 RC теперь предоставляет нужные нам хуки.
Посетите https://github.com/acdlite/redux-react-router и сообщите нам, нравится ли вам это прямо сейчас!
@gaearon я обнаружил проблему с react-router
и экспериментальным AsyncProps
. Обновление реакции решает проблему
@wtfil Полезно знать!
При интеграции с реактивным маршрутизатором я понимаю из этого потока, что мы можем передать экземпляр маршрутизатора создателю действия и вызвать на нем методы. Однако я также понимаю, что разработчики экшенов не должны иметь побочных эффектов в чистом виде. Единственный случай, когда я вижу, что создателям действий разрешено иметь побочные эффекты, — это когда они являются асинхронными действиями, обрабатываемыми промежуточным программным обеспечением. Ожидается ли тогда, что создатели действий, которые выполняют переходы, должны быть асинхронными, а не чистыми функциями?
Ожидается ли тогда, что создатели действий, которые выполняют переходы, должны быть асинхронными, а не чистыми функциями?
Создателям действий разрешено иметь побочные эффекты. Лучше избегать их, когда это возможно, но, конечно, в какой-то момент они вам понадобятся, и, поскольку редукторы в Redux чисты, и у нас нет явного механизма эффектов, как в Elm (см. # 569 для обсуждения), создатели действий - это место для их размещения.
Проверьте редукс-маршрутизатор . Он работает поверх React Router, но позволяет отправлять действия и заботится о синхронизации маршрутизатора.
ждем, когда React Router и Redux Router достигнут версии 1.0...
Ага. Мы добавим рецепт после того, как это произойдет.
Поэтому я следил за этим обсуждением, а также немного думал о том, как маршрутизация в целом вписывается в избыточность (см. https://github.com/rackt/redux/issues/805). Основываясь на некоторых обсуждениях в этой теме и некоторых экспериментах, я нашел подход, который лично я предпочитаю клею react-router/react-redux-router.
По сути, я стараюсь не давать react-router или redux каких-либо знаний друг о друге, а вместо этого соединяю их через собственную реализацию истории. При таком подходе маршрутизация обрабатывается следующим образом:
route
.createHistory
, но вы можете легко использовать createHashHistory
или что-то еще) создается и прослушивается. Когда текущее местоположение в браузере изменяется, отправляется действие ROUTE, в конечном итоге помещающее местоположение в хранилище.route
хранилища. Он также определяет createHref
и pushState
, делегируя их стандартной истории, созданной на шаге 2.Вот и все. Я думаю, что это обеспечивает довольно четкое разделение обязанностей между redux и react-router и устраняет необходимость в использовании еще одной «клеевой» библиотеки. Я вставляю свой код ниже, мне было бы очень интересно услышать любые отзывы:
// please pay attention to library versions, this strategy is only tested with the indicated versions
import React from 'react'; // v0.13.3
import { Provider } from 'react-redux'; // v3.1.0
import { Router, Route, IndexRoute, Link } from 'react-router'; // v1.0.0-rc3
import { createHistory } from 'history'; // v1.12.3
import { createStore } from 'redux'; // v3.0.2
// define some components
class About extends React.Component {
render () {
return (
<div><h1>About</h1></div>
)
}
}
class Home extends React.Component {
render () {
return (
<div>
<h1>Home</h1>
<Link to="/about">Go to about</Link>
</div>
)
}
}
// create a standard history object
var history = createHistory();
// set up 'route' action and action creator
const ROUTE = 'ROUTE';
function createRouteAction (location) {
return {
type: ROUTE,
payload: location
};
}
// set up reducer. here we only define behavior for the route action
function reducer (state = {}, action) {
if (action.type === ROUTE) {
return Object.assign({}, state, {
route: action.payload
});
}
else {
return state;
// whatever other logic you need
}
}
// create store
const store = createStore(reducer);
// this factory returns a history implementation which reads the current state
// from the redux store and delegates push state to a different history.
function createStoreHistory () {
return {
listen: function (callback) {
// subscribe to the redux store. when `route` changes, notify the listener
const unsubscribe = store.subscribe(function () {
const route = store.getState().route;
callback(route);
});
return unsubscribe;
},
createHref: history.createHref,
pushState: history.pushState
}
}
React.render(
<Provider store={store}>
{() =>
<Router history={createStoreHistory()}>
<Route path="/about" component={About} />
<Route path="/" component={Home} />
</Router>
}
</Provider>,
document.getElementById('root') // or whatever
);
// when the url changes, dispatch a route action. this is placed at the bottom so that the first route triggers the initial render
const unlisten = history.listen(function (location) {
store.dispatch(createRouteAction(location));
});
Кстати, я упоминаю об этом, потому что я думаю, что этот пример может помочь некоторым людям вроде меня, которые боролись с использованием react-router с redux, и поможет прояснить ваше утверждение, что redux и react-router сегодня можно использовать вместе.
@cappslock Мне это нравится, но есть одна мелочь. В вашей реализации изменение маршрута - это действие, это как бы нарушает подход "Действие - это событие, а не команда", что в конечном итоге может привести к некоторым неприятным практикам. Давайте перевернем мышление, потенциально любое действие может привести к побочному эффекту, который изменяет адресную строку (будь то компонент или адресная строка реального браузера....), а побочный эффект приводит к отправке нового действия ( ROUTE_CHANGED
). Это в основном тот же шаблон, что и запуск вызова API.
@tomkis1 спасибо за отзыв. Если я вас правильно понял, это уже так работает. Действие ROUTE
отправляется как побочный эффект изменения URL-адреса. Может, имя ROUTE_CHANGED
было бы лучше?
Ой! Я только что перепроверил, и вы были правы. Да, ROUTE_CHANGED
было бы лучше.
Я тоже так думаю. Я бы вернулся и изменил его, но тогда эти комментарии были бы действительно запутанными :)
Поток такой, чтобы уточнить:
Изменение URL-адреса -> действие ROUTE
(или ROUTE_CHANGED
) -> хранилище обновлений редуктора -> история хранения (ранее подписанная на хранилище) уведомляет слушателей -> обновления реакции-маршрутизатора
Я предпочитаю это, потому что мне не нужно ничего, кроме хранилища, управляющего наблюдаемым пользователем состоянием пользовательского интерфейса. Это также кажется предпочтительным для тестирования.
Недостатком этого подхода является то, что URL-адрес не обновляется в ответ на действие ROUTE_CHANGED
. Я даже не уверен, желательно ли это, если мы говорим, что не хотим, чтобы действия рассматривались как команды, но я полагаю, что это может быть выполнено либо как побочный эффект создателя действия ROUTE_CHANGED
, либо с помощью отдельный подписчик магазина.
Кстати, если это обсуждение выходит за рамки этого вопроса, пожалуйста, дайте мне знать, и я перенесу его.
@cappslock Мне это очень нравится! Я определенно не думаю, что проблема в том, что отправка ROUTE_CHANGED
не меняет маршрут. Обработка действия как события, а не триггера, кажется мне чище/более понятным (поскольку оно отвечает на взаимодействие с пользователем; вы не ожидаете, что действие BUTTON_CLICKED
фактически вызовет нажатие кнопки). Однако есть одна часть вашего кода, которую я не понимаю. Можете ли вы уточнить этот момент для меня?
это размещено внизу, так что первый маршрут запускает начальный рендеринг
@elliotdickison Спасибо! Я попытаюсь прояснить это, но, пожалуйста, отнеситесь к тому, что я говорю, с недоверием, так как это основано на пробах, ошибках и предположениях, а не на глубоком анализе. На данный момент это скорее доказательство концепции/наброска.
Когда я поместил этот код над экземпляром ReactRouter
, компонент, соответствующий маршруту /
, не отобразился. Маршрутизатор по-прежнему будет работать, если действия будут отправляться вручную или состояние будет передаваться вручную, поэтому я решил, что это проблема жизненного цикла с историей. Перемещение ниже экземпляра ReactRouter
решило эту проблему. Я подозреваю, что библиотека истории откладывает уведомление о начальном маршруте, пока не появится хотя бы один подписчик. Если этот подписчик настроен до ReactRouter, ни одно из уведомлений не доходит до него.
Пытаясь объяснить это, я понимаю, что немного не понимаю; Я посмотрю на это больше и постараюсь дать лучший ответ.
Недостатком этого подхода является то, что URL-адрес не обновляется в ответ на действие ROUTE_CHANGED. Я даже не уверен, желательно ли это, если мы говорим, что не хотим, чтобы действия рассматривались как команды, но я полагаю, что это может быть выполнено либо как побочный эффект создателя действия ROUTE_CHANGED, либо отдельным подписчиком хранилища.
Я бы сказал, что это желательно, ROUTE_CHANGED
обязательно должен быть запущен внешним источником (например, onhashchange...) Изменение URL-адреса IMO должно привести к ROUTE_CHANGED
, а не наоборот.
Я вроде согласен. Я подумал, что было бы неплохо просто иметь что-то, подписывающееся на магазин и синхронизирующее URL-адрес на случай, если отправленное действие ROUTE_CHANGED
возникло из кода, а не из фактического события истории, но вы могли бы утверждать, что этот случай представляет собой ошибка программирования.
@cappslock ваш подход действительно очень хорош. Не могли бы вы сделать об этом пост в блоге?
@vojtaranta Просто загляните на https://github.com/rackt/redux/issues/805 . Думаю, это послужило источником вдохновения для реализации.
@vojtaranta Спасибо! У меня нет блога, поэтому почти вся информация, которая у меня есть, находится в этой теме и #805. О чем конкретно вы хотели узнать?
1.0 вышла.
Пришло время:
real-world
:хлопок:
@gaearon , можете ли вы сослаться на эту проблему, когда пример _Usage with Router_ находится в PR? Многие люди, которых я знаю (включая меня), ищут разъяснений о том, как эти двое хорошо сочетаются друг с другом.
Да, конечно. На этом вопрос будет закрыт. :-)
Может быть, теперь стоит рассмотреть redux-simple-router ?
редукс-простой-маршрутизатор +1
Я только что преобразовал универсальный пример + react-router (+redux-simple-router)
https://github.com/eriknyk/редукс-универсальное-приложение
привет всем, какой вывод из этого обсуждения? я вижу, что документы не обновлены для использования с реактивным маршрутизатором
копия @gaearon
@gaearon после того, как я привязал свое реагирующее приложение к избыточности, я просто использую состояние для управления отображением/скрытием моих компонентов. Поэтому я не думаю, что первоначальная роль «маршрутизатора», такая как RR, теперь подходит для моего приложения.
Единственное, что, я думаю, нужно сделать «новому маршрутизатору», это сопоставить URL-адрес с состоянием (через действия?) И также переназначить состояние обратно на URL-адрес.
Если мы позволим URL-адресу решать, как отображать компонент приложения (возможно, некоторые из них), это означает, что у нас есть два источника состояния, один — URL-адрес, другой — хранилище редуксов, что усложнит задачу...
Что вы скажете об этом? мы должны просто позволить адресной строке быть еще одним компонентом нашего приложения.
Спасибо
Я официально обязуюсь написать этот документ после того, как мы отправим https://github.com/rackt/react-router-redux/pull/259. Это будет благословенный способ связать React Router и Redux. В документе мы сначала покажем, как использовать их без этого пакета, и постепенно представим два удобства, которые предоставляет пакет: промежуточное ПО и перемещение источника достоверной информации о маршрутизации в хранилище. Оба являются необязательными, поэтому мы обязательно объясним, в каком случае вы хотите их использовать, и что они дают вам по сравнению с обычным RR.
Вот мысль для размышления: если что-то, что вы объясняете о промежуточном программном обеспечении, связанном с маршрутизацией и историей, также может стать конкретным фрагментом реалистичного приложения в рамках общего объяснения http://rackt.org/redux/docs/advanced/Middleware.html ( например, в примерах в конце)
@gaearon есть прогресс в документе React Router / следующих шагах? Я читаю документацию Redux и мне она нравится, но эти поддельные ссылки меня расстраивают :(
Я только начал переписывать документы по реагирующим маршрутизаторам в своем личном репозитории, и я также планирую добавить туда раздел редукции. Я мог бы что-то сделать в ближайшее время, зависит от моего свободного времени. Я буду держать вас в курсе. https://github.com/knowbody/react-router-docs
Честно говоря, вам не нужно ничего на стороне Redux, чтобы заставить работать реактивный маршрутизатор. Но да, мы должны это сделать.
Обратите внимание: React Router 3.0 будет лучше работать с оптимизацией React Redux connect()
, а новый HOC withRouter()
означает, что вам не нужно напрямую использовать контекст.
@gaearon , @timdorr , не могли бы вы прояснить компромисс между передачей экземпляра маршрутизатора в качестве аргумента создателям действий и прямым импортом истории браузера в создателей действий (как предлагается здесь https://github.com/reactjs/react-router/blob /master/docs/guides/NavigatingOutsideOfComponents.md?
router
просто оборачивает ваш экземпляр истории некоторыми дополнительными элементами, но это те же самые методы push
и replace
между двумя экземплярами.
Спасибо, @timdorr.
Я предполагаю, что тогда возникает вопрос, зачем нам нужна композиция withRouter()
, если router
в основном обертывает синглтон истории (это синглтон, верно)?
Это сделано для того, чтобы разрешить более слабую связь между компонентом и экземпляром истории (т. е. предотвратить прямой доступ компонента к объекту-одиночке)? Если да, то не будет ли применяться та же логика при доступе к экземпляру истории из создателя действия?
Да, и если вы предоставляете свой собственный экземпляр истории и не хотите (или не можете) создавать свой собственный одноэлементный модуль (что может сбивать с толку, если вы не очень хорошо знакомы с модульными системами JS). Если вы хотите сделать это самостоятельно, вы можете следовать нашему образцу.
Я не уверен, стоит ли документировать withRouter
о том, как его можно использовать для нескольких компонентов более высокого порядка. Я все еще пытаюсь найти лучший способ избежать этого:
connect(mapStateToProps, mapDispatchToProps)(withRouter(withAnalytics(withLanguage(TestForm))));
.
Я также мог бы использовать что-то вроде compose
?
const enhance = compose(
connect(mapStateToProps, mapDispatchToProps),
withRouter,
withAnalytics,
withLanguage
);
export default enhance(TestForm);
Тем не менее, в моем случае использования контекст будет иметь зарегистрированного пользователя, текущий язык, информацию о теме и аналитику, что усложняет задачу с большим количеством контекста и множеством связанных компонентов.
Другая идея состоит в том, чтобы дублировать логику withRouter
и connect
в одном пространстве имен контекста: withAppContext() => props.app = { user, lang, theme, analytics, router, connect? }
?
Будет ли это полезно для документации или примера использования withRouter
с connect
?
@gaearon есть ли какие-либо обновления для этого сейчас, когда React Router 3.0.0 и ваши новые видео Egghead уже давно вышли, а эта ветка открыта уже год?
Совершено в № 1929
Самый полезный комментарий
Кроме того, как вы относитесь к добавлению примера, который показывает повсеместное использование маршрутизатора React?