将组件更改为容器似乎会破坏该组件中的路由

时间:2017-11-10 04:11:16

标签: reactjs redux react-router

我遇到了一个问题,即制作一个将Routes放入容器的组件似乎会破坏路由器的反应。如果包含我的路由的组件是容器,则单击链接会更新URL但不会更改呈现的屏幕。如果我在URL更改后刷新页面或直接转到URL,则会呈现预期的视图。将容器更改回常规组件似乎可以解决此问题,但我希望将其保留为容器,以便我可以使用必要的操作。

路线坏了:

import React, { Component } from 'react'
import { Route } from 'react-router-dom'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'

import '../styles/App.css';
import Header from '../components/Header'
import Home from './Home'
import NewPost from './NewPost'
import { getAllPosts, getAllCategories } from '../actions'

class App extends Component {
  componentDidMount() {
    this.props.getAllPosts()
    this.props.getAllCategories()
  }

  render() {
    return (
      <div className="App">
        <Header />

        <Route exact path="/" render={() => (
          <Home />
        )} />

        <Route exact path="/post" render={() => (
          <NewPost />
        )} />
        
        <Route path="/article" render={() => (
          <div>article page</div>
        )} />
      </div>
    );
  }
}

const mapDispatchToProps = dispatch => (
  bindActionCreators({
    getAllPosts, 
    getAllCategories
  }, dispatch)
)

export default connect(null, mapDispatchToProps)(App)

工作路线:

import React, { Component } from 'react'
import { Route } from 'react-router-dom'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'

import '../styles/App.css';
import Header from '../components/Header'
import Home from './Home'
import NewPost from './NewPost'
import { getAllPosts, getAllCategories } from '../actions'

export default class App extends Component {
  componentDidMount() {
    //this.props.getAllPosts()
    //this.props.getAllCategories()
  }

  render() {
    return (
      <div className="App">
        <Header />

        <Route exact path="/" render={() => (
          <Home />
        )} />

        <Route exact path="/post" render={() => (
          <NewPost />
        )} />
        
        <Route path="/article" render={() => (
          <div>article page</div>
        )} />
      </div>
    );
  }
}

const mapDispatchToProps = dispatch => (
  bindActionCreators({
    getAllPosts, 
    getAllCategories
  }, dispatch)
)

// export default connect(null, mapDispatchToProps)(App)

任何对此的帮助都会受到大力赞赏,这让我疯狂!

编辑:为了清楚起见,我的index.js包含了BrowserRouter:

import React from 'react'
import ReactDOM from 'react-dom'
import { createStore, applyMiddleware, compose } from 'redux'
import { Provider } from 'react-redux'
import { BrowserRouter } from 'react-router-dom'
import thunk from 'redux-thunk'

// Middleware
import logger from 'redux-logger'

// Martyware
import rootReducer from './reducers'
import App from './containers/App';

import './styles/index.css';

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose

const store = createStore(
  rootReducer,
  composeEnhancers(
    applyMiddleware(thunk),
    applyMiddleware(logger)
  )
)

ReactDOM.render(
  <Provider store={store}>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </Provider>
  , document.getElementById('root')
)

2 个答案:

答案 0 :(得分:1)

有几件事情需要考虑,但简而言之,我认为您的问题正在发生,因为您的组件不会重新呈现,因为它不知道位置已更改。对此的快速解决方案是使用withRouter

import { withRouter } from 'react-router';
...
export default withRouter(connect(null, mapDispatchToProps)(App));

现在,您的代码还有其他方面可以指出潜在的问题。 <Router />不可见,因此我们只能假设它将您的组件包装在更高的级别。您没有将任何Redux状态映射到props,因此也不会导致重新渲染。

为什么需要使用路由器?

根据要求,我将尝试解释withRouter的影响,但为此我需要了解React组件如何重新渲染。为了防止不断重新渲染,React会查看方法componentShouldUpdate的结果,默认情况下比较这个组件的props(和state),如果它们发生了更改,则调用render函数。如果他们没有改变它没有。

因此,当您的组件包含<Route />(示例中为<App />)时,无论位置是否发生更改,<App />道具和状态都不会更改,并且这样,render函数不再被调用。如果未再次调用,则不会要求与您的路径匹配的<Route />重新呈现。简而言之,如果您的父组件没有重新渲染,它的子组件也不会重新渲染。这通常是为什么像Redux这样的工具存在的原因,能够更新树中的组件,而不必从父项中传递所有属性。

withRouter所做的是它将属性location(以及其他属性)添加到您的<App />组件中,这样无论何时位置发生变化,道具都会有效地更改,因此强制重新渲染。您的组件是否包含在Redux的connect()中不会影响此。

那么,为什么当你没有<App />但是同一个组件中的所有东西都可以工作呢?好吧,那是因为您的主要组件重新渲染,因此render中的每个组件都被询问是否也想重新渲染。

显然,使用withRouter的效果不是很好,因为您可能会比您更喜欢重新渲染方式。但是,按照重新渲染完成最少工作量的方式组织此类组件是您的工作。您可能会使用依赖于自己的道具的子组件,而不是location本身。

还有一些方法可以将React路由器与Redux一起使用,我认为你不需要withRouter,但实际上Redux会导致你的道具发生变化,触发重新渲染。我没有看过这个,所以不要相信我的话。

答案 1 :(得分:0)

我发现这篇文章详细解释了这个问题https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/guides/blocked-updates.md

据我所知,connect阻止了组件的重新呈现,因为它以阻止重新呈现的方式调用shouldComponentUpdate。使用withRouter工作,但不是最有效的解决方案(https://github.com/ReactTraining/react-router/pull/5552#issuecomment-331502281

我最终使用的解决方案是在index.js中通过无路径路径(它将始终匹配并因此呈现App)渲染App,这里是index.js中更新的渲染方法

&#13;
&#13;
ReactDOM.render(
  <Provider store={store}>
    <BrowserRouter>
      <Route component={App} />
    </BrowserRouter>
  </Provider>
  , document.getElementById('root')
)
&#13;
&#13;
&#13;

我希望这对某人有用。很多,非常感谢FMCorz指出我正确的方向!