React -Router-v3 - 动态路由和ssr初始渲染问题

时间:2018-02-23 20:18:07

标签: reactjs react-router serverside-rendering react-boilerplate

对于几个月的经验,我有点新意,我正在研究这个需要实现动态路由的项目。为此我使用这个route.js文件。

// These are the pages you can go to.
// They are all wrapped in the App component, which should contain the navbar etc
// See http://blog.mxstbr.com/2016/01/react-apps-with-pages for more information
// about the code splitting business
import { getAsyncInjectors } from 'utils/asyncInjectors';
import App from 'containers/App';

const errorLoading = (err) => {
  console.error('Dynamic page loading failed', err); // eslint-disable-line no-console
};

const loadModule = (cb) => (componentModule) => {
  cb(null, componentModule.default);
};

function createChildRoutes(store) {
  // create reusable async injectors using getAsyncInjectors factory
  const { injectReducer, injectSagas } = getAsyncInjectors(store); // eslint-disable-line no-unused-vars
  let previousPath = null;
  return [
    {
      path: '/',
      getComponent(nextState, cb) {
        if (nextState.location.pathname === previousPath) {
          return;
        }
        const importModules = Promise.all([
          import('containers/HomePage/reducer'),
          import('containers/App/sagas'),
          import('containers/HomePage/sagas'),
          import('containers/HomePage'),
        ]);

        const renderRoute = loadModule(cb);

        importModules.then(([reducer, appSagas, sagas, component]) => {
          injectReducer('homePage', reducer.default);
          injectSagas([...appSagas.default, ...sagas.default]);

          renderRoute(component);
        });

        importModules.catch(errorLoading);
        previousPath = nextState.location.pathname;
      },
    },
    {
      path: '/:urlKey',
      getComponent(nextState, cb) {
        if (nextState.location.pathname === previousPath) {
          return;
        }
        const importModules = Promise.all([
          import('containers/CatalogPage/actions'),
          import('containers/CatalogPage/reducer'),
          import('containers/App/sagas'),
          import('containers/CatalogPage/sagas'),
          import('containers/CatalogPage'),
        ]);

        const renderRoute = loadModule(cb);

        importModules.then(([actions, reducer, appSagas, sagas, component]) => {
          injectReducer('catalogPage', reducer.default);
          injectSagas([...appSagas.default, ...sagas.default]);
          renderRoute(component);
          store.dispatch(actions.loadPageData(nextState.params.urlKey));
        });

        importModules.catch(errorLoading);
        previousPath = nextState.location.pathname;
      },
    }, {
      path: '*',
      getComponent(nextState, cb) {
        import('containers/NotFoundPage')
          .then(loadModule(cb))
          .catch(errorLoading);
      },
    },
  ];
}

// Set up the router, wrapping all Routes in the App component
export default function createRootRoute(store) {
  return {
    component: App,
    childRoutes: createChildRoutes(store),
  };
}

path: '/:urlKey'导入此组件并使用this.props.loadPageData(this.props.params.urlKey);我在componentWillMount()中调度操作以启用路由的ssr。为了从目录组件启用路由,我还需要从routes.js发送一个操作。

我的组件文件coatalog.js

/* eslint no-tabs: "off", no-trailing-spaces: "off", indent: "off", react/jsx-indent: "off", react/jsx-indent-props: "off"  */
/*
 *
 * CatalogPage - this will handle conditions for page type and route the data to their specific components.
 *
 */

import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { Helmet } from 'react-helmet';
import { createStructuredSelector } from 'reselect';

// import PageWrapperContainer from 'components/PageWrapperContainer';
import CategoryPages from 'components/CategoryPages';
import ProductDetailsPages from 'components/ProductDetailsPages';

import { deviceTypeSelect, isMobileSelect, atcLoaderStateSelect } from 'containers/App/selectors';
import { addToCart } from 'containers/App/actions';

import { loadPageData, getCurrentCategoryState } from './actions';

import { pageDataSelect, 
        currentCategoryStateSelect, 
        metaTitleSelect,
        metaDescriptionSelect,
        pageTypeSelect,
        contentTypeSelect,
        pageHeadingSelect,
        pageSubheadingSelect,
        selectAppLoading } from './selectors';

export class CatalogPage extends React.PureComponent { // eslint-disable-line react/prefer-stateless-function
    componentWillMount() {
    // dispatching the urlKey 
        this.props.loadPageData(this.props.params.urlKey);
    }

    render() {
        const { metaTitle,
                metaDescription,
                pageType,
                contentType,
                categoryPageHeading,
                categoryPageSubHeading,
                pageData,
                appLoading } = this.props;
        // console.log(this.props);
        return (
            <div>
                <Helmet>
                    <title>{metaTitle}</title>
                    <meta name="description" content={metaDescription} />
                </Helmet>

                {(appLoading !== false) ? <p>Loading.....</p> : null}

                { ((appLoading !== true) && (pageType === 'category')) ? 
                    <CategoryPages
                        pageData={pageData}
                        contentType={contentType}
                        categoryPageHeading={categoryPageHeading}
                        categoryPageSubHeading={categoryPageSubHeading}
                        currentCategoryState={this.props.currentCategoryState}
                        getCurrentCategoryState={this.props.getCurrentCategoryState}
                        deviceTypeSelect={this.props.deviceTypeSelect}
                        isMobileSelect={this.props.isMobileSelect}
                        atcLoaderStateSelect={this.props.atcLoaderStateSelect}
                        addToCart={this.props.addToCart}
                    />
                    : null
                }
                { ((appLoading !== true) && (pageType === 'product')) ? 
                    <ProductDetailsPages
                        pageData={pageData}
                        contentType={contentType}
                        deviceTypeSelect={this.props.deviceTypeSelect}
                        isMobileSelect={this.props.isMobileSelect}
                        atcLoaderStateSelect={this.props.atcLoaderStateSelect}
                        addToCart={this.props.addToCart}
                    />
                    : null
                }

            </div>
        );
    }
}

CatalogPage.propTypes = {
    params: PropTypes.object,
    urlKey: PropTypes.string,
    loadPageData: PropTypes.func,
    pageData: PropTypes.oneOfType([
        PropTypes.object,
        PropTypes.any,
    ]),
    currentCategoryState: PropTypes.string,
    getCurrentCategoryState: PropTypes.func,
    metaTitle: PropTypes.string,
    metaDescription: PropTypes.string,
    pageType: PropTypes.string,
    contentType: PropTypes.string,
    categoryPageHeading: PropTypes.string,
    categoryPageSubHeading: PropTypes.string,
    appLoading: PropTypes.bool,
    deviceTypeSelect: PropTypes.string,
    isMobileSelect: PropTypes.bool,
    atcLoaderStateSelect: PropTypes.any,
    addToCart: PropTypes.func,
};

const mapStateToProps = createStructuredSelector({
    pageData: pageDataSelect(),
    currentCategoryState: currentCategoryStateSelect(),
    metaTitle: metaTitleSelect(),
    metaDescription: metaDescriptionSelect(),
    pageType: pageTypeSelect(),
    contentType: contentTypeSelect(),
    categoryPageHeading: pageHeadingSelect(),
    categoryPageSubHeading: pageSubheadingSelect(),
    appLoading: selectAppLoading(),
    deviceTypeSelect: deviceTypeSelect(),
    isMobileSelect: isMobileSelect(),
    atcLoaderStateSelect: atcLoaderStateSelect(),
});

function mapDispatchToProps(dispatch) {
    return {
        loadPageData: bindActionCreators(loadPageData, dispatch),
        getCurrentCategoryState: bindActionCreators(getCurrentCategoryState, dispatch),
        addToCart: bindActionCreators(addToCart, dispatch),
    };
}

export default connect(mapStateToProps, mapDispatchToProps)(CatalogPage);

我的问题是,在路径localhost:3000/:urlKey的第一次渲染中,由于我在componentWillMount()routes.js两次调度动作,因此该组件会重新发送两次。我需要帮助来改进此代码,以阻止在首次加载应用时从routes.js发送操作来停止重新呈现组件。

1 个答案:

答案 0 :(得分:0)

如果您想要在服务器上以及仅在客户端上发生某些事情,您可以在componentWillMount而不是componentWillMount中执行此操作。

componentDidMount在服务器和客户端上执行。 componentDidMount仅限客户。

因此,在您的情况下,您可以在服务器上的routes.js和客户端的{{1}}中运行您的调度。