2017-06-04 74 views
1

我有一個側邊欄有兩個按鈕'測試'和'約'。測試(火箭圖標)在'/ test'處呈現,並且關於(主圖標)呈現在'/'處。React-Router v4渲染錯誤的組件,但匹配正確

它們都位於應用程序的根目錄並嵌套在組件中。

當我從'/'開始並點擊Link to =「/ test」時,它總是加載'About'組件,當我檢查'About'的componentDidMount的道具時,匹配對象包含匹配數據爲「/測試」。

只有當我刷新時它纔會再次呈現正確的組件'Test'。任何想法爲什麼發生這種情況?

AppRoutes.js:

export class AppRoutes extends React.Component { 

    render() { 
    return (
     <div> 
     <Switch> 
      <Route 
      exact path="/" 
      render={(matchProps) => (
       <LazyLoad getComponent={() => import('pages/appPages/About')} {...matchProps} /> 
      )} 
      /> 
      <Route 
      path="/login" 
      render={(matchProps) => (
       <LazyLoad getComponent={() => import('pages/appPages/Login')} {...matchProps} /> 
      )} 
      /> 
      <Route 
      path="/register" 
      render={(matchProps) => (
       <LazyLoad getComponent={() => import('pages/appPages/Register')} {...matchProps} /> 
      )} 
      /> 
      <Route 
      path="/test" 
      render={(matchProps) => (
       <LazyLoad getComponent={() => import('pages/appPages/Test')} {...matchProps} /> 
      )} 
      /> 
... 

AboutPage.js & & TestPage.js(除了組件名稱重複):

import React from 'react'; 

import SidebarContainer from 'containers/SidebarContainer'; 
import SidebarPageLayout from 'styles/SidebarPageLayout'; 

export const About = (props) => { 
    console.log('About Loading: ', props); 
    return (
    <SidebarPageLayout> 
     <SidebarContainer /> 
     <div>About</div> 
    </SidebarPageLayout> 
); 
} 

export default About; 

SidebarContainer.js:

import React from 'react'; 
import PropTypes from 'prop-types'; 
import _ from 'lodash'; 

import Sidebar from 'sidebar/Sidebar'; 
import HamburgerButton from 'sidebar/HamburgerButton'; 
import AboutButton from 'sidebar/AboutButton'; 
import ProfileButton from 'sidebar/ProfileButton'; 
import TestButton from 'sidebar/TestButton'; 

export class SidebarContainer extends React.Component { 
    constructor(props) { 
    super(props); 
    this.state = { 
     sidebarIsOpen: false, 
     sidebarElements: [], 
    }; 
    } 

    componentDidMount() { 
    if (!this.props.authenticated) { 
     this.setState({ 
     sidebarElements: _.concat(this.state.sidebarElements, HamburgerButton, ProfileButton, AboutButton, TestButton), 
     }); 
    } 
    } 

    toggleSidebarIsOpenState =() => { 
    this.setState({ sidebarIsOpen: !this.state.sidebarIsOpen }); 
    } 

    render() { 
    const { authenticated, sidebarIsOpen, sidebarElements} = this.state; 
    return (
     <div> 
     <Sidebar 
      authenticated={authenticated} 
      sidebarIsOpen={sidebarIsOpen} 
      sidebarElements={_.isEmpty(sidebarElements) ? undefined: sidebarElements} 
      toggleSidebarIsOpenState={this.toggleSidebarIsOpenState} 
     /> 
     </div> 
    ); 
    } 
} 

SidebarContainer.propTypes = { 
    authenticated: PropTypes.bool, 
}; 

export default SidebarContainer; 

邊欄.js:

import React from 'react'; 
import _ from 'lodash'; 
import PropTypes from 'prop-types' 

import SidebarStyles from '../styles/SidebarStyles'; 

export const Sidebar = (props) => { 
    if (props && props.sidebarElements) { 
    return (
     <SidebarStyles sidebarIsOpen={props.sidebarIsOpen}> 
     {_.map(props.sidebarElements, (value, index) => { 
      return React.createElement(
      value, 
      { 
       key: index, 
       authenticated: props.authenticated, 
       sidebarIsOpen: props.sidebarIsOpen, 
       toggleSidebarIsOpenState: props.toggleSidebarIsOpenState, 
      }, 
     ); 
     })} 
     </SidebarStyles> 
    ); 
    } 
    return (
    <div></div> 
); 
} 

Sidebar.propTypes = { 
    authenticated: PropTypes.bool, 
    sidebarIsOpen: PropTypes.bool, 
    sidebarElements: PropTypes.array, 
    toggleSidebarIsOpenState: PropTypes.func, 
}; 

export default Sidebar; 

TestButton.js:

import React from 'react'; 
import PropTypes from 'prop-types'; 
import Icon from 'react-fontawesome'; 
import { 
    Link 
} from 'react-router-dom'; 

export const TestButton = (props) => { 
    return (
    <Link to="/test"> 
     <Icon name='rocket' size='2x' /> 
    </Link> 
); 
} 

export default TestButton; 

AboutButton.js:

import React from 'react'; 
import PropTypes from 'prop-types'; 
import Icon from 'react-fontawesome'; 
import { 
    Link 
} from 'react-router-dom'; 

export const AboutButton = (props) => { 
    return (
    <Link to="/"> 
     <Icon name='home' size='2x' /> 
    </Link> 
); 
} 

export default AboutButton; 

不刷新,從 '/' 路線 '/測試' 路線上只是不斷點擊:

enter image description here

刷新後:

enter image description here

編輯:

根組件:

編輯:

store.js:

import { 
    createStore, 
    applyMiddleware, 
    compose, 
} from 'redux'; 
import createSagaMiddleware from 'redux-saga'; 

import { rootReducer } from './rootReducers'; 
import { rootSaga } from './rootSagas'; 

// sagas 
const sagaMiddleware = createSagaMiddleware(); 

// dev-tools 
const composeEnhancers = typeof window === 'object' && (
    window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? (
    window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ 
) : compose 
); 

export function configureStore() { 
    const middlewares = [ 
    sagaMiddleware, 
    ]; 
    const store = createStore(
    rootReducer, 
    {}, 
    composeEnhancers(applyMiddleware(...middlewares)) 
); 

    sagaMiddleware.run(rootSaga); 
    return store; 
} 

export const store = configureStore(); 

index.js(根):

import React from 'react'; 
import { Provider } from 'react-redux'; 
import ReactDOM from 'react-dom'; 
import { BrowserRouter } from 'react-router-dom'; 

import { store } from './store'; 
import AppContainer from 'containers/AppContainer'; 

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

AppContainer:

import React from 'react'; 
import { withRouter } from 'react-router-dom'; 
import { connect } from 'react-redux'; 

import { logout, verifyToken } from './actions'; 
import { selectAuthenticated, selectAuthenticating } from './selectors'; 
import AppRoutes from 'routes/AppRoutes'; 

export class AppContainer extends React.Component { 
    constructor(props) { 
    super(props); 
    this.state = { loaded: false }; 
    } 

    componentDidMount() { 
    const token = localStorage.getItem('jwt'); 
    if (token) { 
     this.props.verifyToken(token,() => this.setState({ loaded: true })); 
    } else { 
     this.setState({ loaded: true }); 
    } 
    } 

    render() { 
    if (this.state.loaded) { 
     return (
     <AppRoutes 
      authenticated={this.props.authenticated} 
      authenticating={this.props.authenticating} 
      logout={this.props.logout} 
     /> 
    ); 
    } else { 
     return <div>Loading ...</div> 
    } 
    } 
} 

function mapStateToProps(state) { 
    return { 
    authenticated: selectAuthenticated(state), 
    authenticating: selectAuthenticating(state), 
    }; 
} 

function mapDispatchToProps(dispatch) { 
    return { 
    verifyToken: (token = '', callback = false) => dispatch(verifyToken(token, callback)), 
    logout:() => dispatch(logout()), 
    }; 
} 

export default withRouter(connect(mapStateToProps, mapDispatchToProps)(AppContainer)); 

編輯2 LazyLoad:

服務/ LazyLoad/index.js:

import React from 'react'; 

export class LazyLoad extends React.Component { 
    constructor(props) { 
    super(props); 
    this.state = { 
     AsyncModule: null, 
    }; 
    } 

    componentDidMount() { 
    this.props.getComponent() // getComponent={() => import('./someFile.js')} 
     .then(module => module.default) 
     .then(AsyncModule => this.setState({AsyncModule})) 
    } 

    render() { 
    const { loader, ...childProps } = this.props; 
    const { AsyncModule } = this.state; 

    if (AsyncModule) { 
     return <AsyncModule {...childProps} />; 
    } 

    if (loader) { 
     const Loader = loader; 
     return <Loader />; 
    } 

    return null; 
    } 
} 

export default LazyLoad; 
+1

我昨天在我的項目上遇到類似的問題。我的有一個打包的子組件,它使用了redux,這阻止了重新渲染。通過將'connect(...)(TopLayout)'更改爲'withRouter(connect(...)(TopLayout))'來修復它,以便每次更改路線時都會重新渲染。更多信息請訪問:https://reacttraining.com/react-router/web/api/withRouter。您沒有向我們展示您的實施,所以我不確定這是同樣的問題。 –

+1

這種感覺非常像正確的答案,我希望它是真實的,但它仍然不適合我。我結束了並且包裝了與withRouter進行connect()調用的所有容器和組件,但它仍然給我提供了錯誤。 –

+1

這可能是因爲'LazyLoad'。你可以分享'LazyLoad'代碼嗎? –

回答

3

你的問題在於LazyLoad組件。對於「/」或「測試」路徑,組件最終呈現的是組件LazyLoad。因爲RouteSwitch只是有條件地呈現他們的孩子。但是,React無法區分「/」LazyLoad組件和「/ test」LazyLoad組件。所以它第一次呈現LazyLoad組件並調用componentDidMount。但是當路線變化時,React認爲它是先前渲染組件的支柱變化。所以它只是調用以前的LazyLoad組件的componentWillReceiveProps新的道具,而不是卸載前一個並掛載一個新的道具。這就是爲什麼它不斷顯示關於組件,直到刷新頁面。

要解決此問題,如果getComponent支柱已更改,我們必須在componentWillReceiveProps的內部加載新模塊,並添加新的getComponent。所以我們可以修改LazyLoad,它們有一個通用的方法來加載模塊,並使用正確的道具從componentDidMountcomponentWillReceiveProps中調用它。

import React from 'react'; 

export class LazyLoad extends React.Component { 
    constructor(props) { 
    super(props); 
    this.state = { 
     AsyncModule: null, 
    }; 
    } 

    componentDidMount() { 
    this.load(this.props); 
    } 

    load(props){ 
    this.setState({AsyncModule: null} 
    props.getComponent() // getComponent={() => import('./someFile.js')} 
     .then(module => module.default) 
     .then(AsyncModule => this.setState({AsyncModule})) 
    } 

    componentWillReceiveProps(nextProps) { 
    if (nextProps.getComponent !== this.props.getComponent) { 
     this.load(nextProps) 
    } 
    } 

    render() { 
    const { loader, ...childProps } = this.props; 
    const { AsyncModule } = this.state; 

    if (AsyncModule) { 
     return <AsyncModule {...childProps} />; 
    } 

    if (loader) { 
     const Loader = loader; 
     return <Loader />; 
    } 

    return null; 
    } 
} 

export default LazyLoad; 
+1

聖潔的廢話它的工作,謝謝! –

+2

@KyleTruong我很高興它幫助你。我會解釋爲什麼當我抽時間。 –

+0

再次感謝您,期待它。 –