2017-06-04 91 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

再次感谢您,期待它。 –