簡體   English   中英

React-Router v4呈現錯誤的組件但正確匹配

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

我有一個帶有兩個按鈕的側邊欄,分別是“測試”和“關於”。 測試(火箭圖標)呈現在“ / test”,“關於”(主頁圖標)呈現在“ /”。

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

當我從“ /”開始並單擊鏈接到=“ / test”時,它總是加載“關於”組件,當我檢查“關於”的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;

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:

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;

無需刷新,只需從“ /”路由中不斷單擊“ / test”路由即可:

在此處輸入圖片說明

刷新后:

在此處輸入圖片說明

編輯:

根成分:

編輯:

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;

您的問題出在LazyLoad組件上。 對於這兩種“/”或“測試”的路徑,什么AppRoutes組分最終呈現為LazyLoad組件。 因為RouteSwitch只是有條件地渲染其子級。 但是,React無法區分“ /” LazyLoad組件和“ / test” LazyLoad組件。 因此,它第一次呈現LazyLoad組件並調用componentDidMount 但是,當路線更改時,React會將其視為先前渲染的LazyLoad組件的LazyLoad更改。 所以它只是調用componentWillReceiveProps以前的LazyLoad與新道具,而不是卸載前一個和安裝新的一個組成部分。 這就是為什么它持續顯示About組件直到刷新頁面的原因。

要解決此問題,如果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;

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM