React-Router v4呈现错误的组件但正确匹配 [英] React-Router v4 rendering wrong component but matching correctly

查看:75
本文介绍了React-Router v4呈现错误的组件但正确匹配的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个带有两个按钮的侧边栏,分别是测试"和关于".测试(火箭图标)呈现在"/test",关于"(主图标)呈现在"/".

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 '/'.

它们都位于应用程序的根目录,并且嵌套在组件中.

They're both located at the root of the app and are nested within a component.

当我从"/"开始并单击链接到="/test"时,它总是加载关于"组件,当我检查关于"的componentDidMount的道具时,匹配对象将包含"/测试".

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".

仅当我刷新时,它才会再次渲染正确的组件测试".知道为什么会这样吗?

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(除组件名称外,重复项):

AboutPage.js && TestPage.js (duplicates except for component name):

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;

无需刷新,只需不断地从"/"路径中单击"/test"路径即可:

No refresh, just constant clicking on the '/test' route from the '/' route:

刷新后:

根组件:

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(根):

index.js (root):

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));

为LazyLoad

services/LazyLoad/index.js:

services/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;

推荐答案

您的问题出在LazyLoad组件上.对于"/"或"test"路径,AppRoutes组件最终呈现的是一个LazyLoad组件.因为RouteSwitch只是有条件地渲染其子级.但是,React无法区分"/" LazyLoad组件和"/test" LazyLoad组件.因此,它第一次呈现LazyLoad组件并调用componentDidMount.但是,当路线更改时,React会将其视为先前渲染的LazyLoad组件的适当更改.因此,它仅使用新道具调用先前LazyLoad组件的componentWillReceiveProps而不是卸载先前的道具并装载新的道具.这就是为什么它持续显示About组件直到刷新页面的原因.

Your problem lies with LazyLoad component. For both "/" or "test" paths, what AppRoutes component ultimately renders is a LazyLoad component. Because Route and Switch just conditionally render their children. However, React can't differentiate "/" LazyLoad component and "/test" LazyLoad component. So the first time it renders LazyLoad component and invokes the componentDidMount. But when route changes, React consider it as a prop change of previously rendered LazyLoad component. So it just invokes componentWillReceiveProps of previous LazyLoad component with new props instead of unmounting previous one and mount a new one. That's why it continuously show About component until refresh the page.

要解决此问题,如果更改了getComponent属性,则必须在componentWillReceiveProps内部使用新的getComponent加载新模块.因此,我们可以按以下方式修改LazyLoad,它具有一种加载模块的通用方法,并可以使用正确的道具从componentDidMountcomponentWillReceiveProps调用该模块.

To solve this problem, if the getComponent prop has changed, we have to load the new module with new getComponent inside the componentWillReceiveProps. 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.

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;

这篇关于React-Router v4呈现错误的组件但正确匹配的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆