Redux: 文档:与 React 路由器一起使用

创建于 2015-08-27  ·  61评论  ·  资料来源: reduxjs/redux

人们总是觉得我们不支持 React Router,或者你需要一些特殊的东西,比如redux-react-router才能工作,甚至直到 React 0.14 才工作。

您可以像现在一样将 Redux 与 React Router 0.13 或 1.0 一起使用。
(顺便说一下,自从 Redux 最初发布以来,情况就是如此。)

像时间旅行这样的一些功能需要等待 RR 方面的一些支持,但这与主要问题无关。 人们感到困惑,认为他们今天不能使用路由,这是错误的。

此 repo 中包含的真实示例使用 React Router。 您只需将<Router>包装到<Provider>中,就像您将顶级组件包装在无路由器的应用程序中一样。

如果您想从动作创建者转换,请将router实例作为参数传递给动作创建者,并在您喜欢时调用其方法。 如果您想从 Redux 存储中读取路由器状态,请在路由更改时触发一个操作并编写一个 reducer 来处理它。 而已!

react-redux-router是一个尝试提出更自然的 API 的实验,您只需调度操作并从自动连接到路由器实例的存储中读取状态,但您不必使用它,或者等待它变得稳定! 这只是一个实验。

我们在文档中需要这个..

最有用的评论

另外,您对添加一个显示普遍使用的反应路由器的示例有何感想?

所有61条评论

我最初编写了服务器端渲染示例以在通用上下文中使用反应路由器。 有人建议使用 react router 可以成为它自己的文档。 也许我们可以在文档中添加一个部分?

另外,您对添加一个显示普遍使用的反应路由器的示例有何感想?

我认为将real-world示例修改为通用的将是一个好主意。 这样你就不必从头开始构建任何东西。

如果您想从动作创建者转换,请将路由器实例作为参数传递给动作创建者

只是想澄清一下,这仅适用于 0.13,因为 1.0beta 没有createRouter概念(还没有?),对吗?

你可以在你的 React 组件中使用 1.0.0-beta3 来做到这一点。

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 中的动作创建者转换到不同的网址?

是的,如果你将路由器实例传递给动作创建者,你可以用它做任何你想做的事情,包括转换。

我把这个放在一起:

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是的,ofc。 我不是将它包装在 Provider 中,而是在我自己的组件中,该组件主要是您的 Provider 的复制粘贴。 不同之处在于我没有将 store 传递给它,而是在其中创建 store。

@gyzerok如果没有看到代码,很难说出了什么问题。 (请提交一个单独的问题, react-redux repo 是一个好地方。)

感谢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结果,但上下文确实存在问题。 给我一些时间,我会把整个例子放到单独的仓库中

我目前正在调查的另一件事是以清晰、不显眼的方式将参数存储在 redux 存储中。 在尝试了几种方法后,我最终写了:

<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很高兴知道!

当与 react-router 集成时,我从这个线程中了解到,我们可以将路由器实例传递给动作创建者并在其上调用方法。 但是,我也理解动作创建者的目的是不以最纯粹的形式产生副作用。 我看到的唯一允许动作创建者产生副作用的情况是它们是由中间件处理的异步动作。 那么是否期望执行转换的动作创建者应该是异步的,而不是纯函数?

那么是否期望执行转换的动作创建者应该是异步的,而不是纯函数?

动作创建者可以有副作用。 最好尽可能避免使用它们,但当然在某些时候你需要它们,并且由于 reducer 在 Redux 中是纯的,而且我们没有像 Elm 那样明确的效果机制(参见 #569 进行讨论),动作创建者是放置它们的地方。

查看redux-router 。 它在 React Router 之上工作,但允许您调度操作并负责同步路由器。

等待 React Router 和 Redux Router 都达到 1.0...

是的。 发生这种情况后,我们将添加一个食谱。

所以我一直在关注这个讨论,并思考了路由如何适应一般的 redux(参见 https://github.com/rackt/redux/issues/805)。 基于该线程中的一些讨论和一些实验,我发现了一种我个人更喜欢 react-router/react-redux-router 胶水的方法。

基本上,我尽量不让 react-router 或 redux 相互了解,而是通过自定义历史实现来连接它们。 使用这种方法,路由处理如下:

  1. route创建了一个 action、一个 reducer 和一个 store 中的 key。
  2. 创建并收听标准历史实现(此示例使用首选的createHistory ,但您可以轻松使用createHashHistory或其他任何内容)。 当浏览器中的当前位置发生变化时,将调度一个 ROUTE 操作,最终将位置放入商店。
  3. 标准的 react-router 实例是使用第二个自定义历史实现创建的,该实现会在商店的route键发生更改时通知订阅者(react-router)。 它还定义了createHrefpushState ,将两者都委托给第 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 )操作 -> reducer 更新存储 -> 存储历史记录(以前订阅存储)通知侦听器 -> 反应路由器更新

我更喜欢这个,因为除了驱动 UI 的用户观察状态的商店之外,我什么都不想要。 它似乎也更适合测试。

这种方法的一个缺点是 URL 不会更新以响应ROUTE_CHANGED操作。 如果我们说我们不希望将操作视为命令,我什至不确定这是否可取,但我想它可以作为ROUTE_CHANGED操作创建器的副作用或通过单独的商店订阅者。

顺便说一句,如果这个讨论超出了这个问题的范围,请告诉我,我会移动它。

@cappslock我非常喜欢! 我绝对不认为调度ROUTE_CHANGED不会改变路线是个问题。 将动作视为事件而不是触发器对我来说似乎更清晰/更容易理解(因为它响应用户交互;您不会期望BUTTON_CLICKED动作实际触发对按钮的单击)。 您的代码中有一部分我不明白。 你能为我详细说明一下吗?

这放置在底部,以便第一条路线触发初始渲染

@elliotdickison谢谢! 我将尝试澄清这一点,但请对我所说的持保留态度,因为它是基于反复试验和假设,而不是深入分析。 在这一点上,这更像是概念证明/草图。

当我将该代码放在ReactRouter的实例化之上时,与/路由对应的组件未呈现。 如果手动调度操作或手动推送状态,路由器仍然可以工作,所以我认为这是历史的生命周期问题。 将它移到ReactRouter的实例下方解决了这个问题。 我怀疑历史库将初始路线的通知推迟到至少有一个订阅者。 如果该订阅者是在 ReactRouter 之前设置的,则不会有任何通知到达它。

试图解释这一点,我意识到我的理解有点缺乏; 我将对此进行更多研究并尝试提供更好的答案。

这种方法的一个缺点是 URL 不会响应 ROUTE_CHANGED 操作而更新。 如果我们说我们不希望将操作视为命令,我什至不确定这是否可取,但我想它可以作为 ROUTE_CHANGED 操作创建者的副作用或由单独的商店订阅者完成。

我想说这是需要的, ROUTE_CHANGED肯定应该由外部源触发(例如 onhashchange ...) IMO URL 更改应该导致ROUTE_CHANGED反之亦然。

我有点同意。 我认为订阅商店并保持 URL 同步可能会很好,以防调度的ROUTE_CHANGED操作源自代码而不是实际的历史事件,但您可以争辩说这种情况代表了编程错误。

@cappslock你的方法真的很好。 你能写一些关于它的博文吗?

@vojtatranta只需查看https://github.com/rackt/redux/issues/805我想这是实施背后的灵感。

@vojtatranta谢谢! 我没有博客,所以我所拥有的几乎所有信息都在这个线程和#805 中。 您特别想了解更多信息吗?

1.0 出来了。

是时候了:

  • 使用它的端口路由示例
  • 根据real-world示例添加“使用路由器”配方

:拍:

@gaearon当 _Usage with Router_ 示例在 PR 中时,您可以参考这个问题吗? 我认识的许多人(包括我自己)都在寻找关于这两者如何配合得很好的说明。

是的,当然。 这是问题将被关闭的时候。 :-)

也许现在应该考虑redux-simple-router

redux 简单路由器 +1

我刚刚转换了通用示例 + react-router (+redux-simple-router)
https://github.com/eriknyk/redux-universal-app

大家好,这次讨论的结论是什么? 我看到文档没有更新为与 react-router 一起使用
抄送@gaearon

@gaearon在我将我的反应应用程序绑定到 redux 之后,我只是使用状态来控制我的组件的显示/隐藏。 所以我不认为像 RR 这样的原始“路由器”角色现在适合我的应用程序。
我认为“新路由器”唯一需要做的就是将 url 映射到状态(通过操作?)并将状态也重新映射回 url。
如果我们让 url 决定应用程序组件(可能是其中的一些)如何显示,这意味着我们有两个状态源,一个是 url,另一个是 redux 的 store,这会让事情变得更难......
你对此有什么看法? 我们是否应该让地址栏成为我们应用程序的另一个组件。

谢谢

在我们发布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 文档并喜欢它,但被那些虚假链接弄得一头雾水:(

我才刚刚开始在我的个人仓库中重写 React Router 文档,并且我计划在那里也有 redux 部分。 我可能很快就会完成一些事情,这取决于我的空闲时间。 我会及时通知你的。 https://github.com/knowbody/react-router-docs

公平地说,你不需要 Redux 方面的任何东西来让反应路由器工作。 但是,是的,我们需要这样做。

注意:React Router 3.0 将与 React Redux connect()优化一起更好地工作,新的withRouter() HOC 意味着您不需要直接使用上下文。

https://twitter.com/dan_abramov/status/729768048417251328

@gaearon@timdorr您能否澄清将路由器实例作为参数传递给动作创建者和直接在动作创建者中导入 browserHistory 之间的权衡(如此处建议的https://github.com/reactjs/react-router/blob /master/docs/guides/NavigatingOutsideOfComponents.md?

router只是用一些额外的好东西来包装你的历史实例,但这两个实例之间的pushreplace方法是相同的。

谢谢,@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);

但是,我的用例上下文将登录用户、当前语言、主题信息和分析,这使得这对大量上下文和大量连接组件具有挑战性。

另一种想法是在一个上下文命名空间下复制withRouterconnect逻辑: withAppContext() => props.app = { user, lang, theme, analytics, router, connect? }

这对文档或withRouterconnect $ 的使用示例是否有益?

@gaearon既然 React Router 3.0.0 和你的新 Egghead 视频已经发布了一段时间,并且这个线程已经打开了一年,那么是否有任何更新?

完成于 #1929

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

timdorr picture timdorr  ·  3评论

jimbolla picture jimbolla  ·  3评论

CellOcean picture CellOcean  ·  3评论

mickeyreiss-visor picture mickeyreiss-visor  ·  3评论

parallelthought picture parallelthought  ·  3评论