简体   繁体   English

React-Router v4呈现错误的组件但正确匹配

[英]React-Router v4 rendering wrong component but matching correctly

I've got a sidebar with two buttons, 'test' and 'about'. 我有一个带有两个按钮的侧边栏,分别是“测试”和“关于”。 Test (rocket icon) is rendered at '/test', and About (home icon) is rendered at '/'. 测试(火箭图标)呈现在“ / test”,“关于”(主页图标)呈现在“ /”。

They're both located at the root of the app and are nested within a component. 它们都位于应用程序的根目录,并且嵌套在组件中。

When I start at '/' and click the Link to="/test" it always loads the 'About' component, and when I check the props for the componentDidMount of 'About', the match object contains match data for "/test". 当我从“ /”开始并单击链接到=“ / test”时,它总是加载“关于”组件,当我检查“关于”的componentDidMount的道具时,匹配对象包含“ / test”的匹配数据”。

Only when I refresh does it render the proper component, 'Test', again. 只有刷新后,它才会再次渲染正确的组件“测试”。 Any idea why this is happening? 知道为什么会这样吗?

AppRoutes.js: 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 (duplicates except for component name): 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: 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;

Sidebar.js: Sidebar.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: 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: 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;

No refresh, just constant clicking on the '/test' route from the '/' route: 无需刷新,只需从“ /”路由中不断单击“ / test”路由即可:

在此处输入图片说明

after refresh: 刷新后:

在此处输入图片说明

Edit: 编辑:

Root components: 根成分:

Edit: 编辑:

store.js: 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 (root): 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: 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));

Edit 2 for LazyLoad: 编辑2以获取LazyLoad:

services/LazyLoad/index.js: 服务/ 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;

Your problem lies with LazyLoad component. 您的问题出在LazyLoad组件上。 For both "/" or "test" paths, what AppRoutes component ultimately renders is a LazyLoad component. 对于这两种“/”或“测试”的路径,什么AppRoutes组分最终呈现为LazyLoad组件。 Because Route and Switch just conditionally render their children. 因为RouteSwitch只是有条件地渲染其子级。 However, React can't differentiate "/" LazyLoad component and "/test" LazyLoad component. 但是,React无法区分“ /” LazyLoad组件和“ / test” LazyLoad组件。 So the first time it renders LazyLoad component and invokes the componentDidMount . 因此,它第一次呈现LazyLoad组件并调用componentDidMount But when route changes, React consider it as a prop change of previously rendered LazyLoad component. 但是,当路线更改时,React会将其视为先前渲染的LazyLoad组件的LazyLoad更改。 So it just invokes componentWillReceiveProps of previous LazyLoad component with new props instead of unmounting previous one and mount a new one. 所以它只是调用componentWillReceiveProps以前的LazyLoad与新道具,而不是卸载前一个和安装新的一个组成部分。 That's why it continuously show About component until refresh the page. 这就是为什么它持续显示About组件直到刷新页面的原因。

To solve this problem, if the getComponent prop has changed, we have to load the new module with new getComponent inside the componentWillReceiveProps . 要解决此问题,如果getComponent属性已更改,则必须在componentWillReceiveProps内部使用新的getComponent加载新模块。 So we can modify the LazyLoad as follows which have a common method to load module and invoke it from both componentDidMount and componentWillReceiveProps with correct props. 因此,我们可以按以下方式修改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;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM