使用 React Router 4 预取数据

Prefetch data with React Router 4

我正在尝试迁移到 React Router 的 v4,但我受困于预取数据。我们以前的最新版本有 match 功能。

其中一个返回参数是 renderProps,但现在我不能使用 match,所以我不知道如何预取数据。 documentation shows how to load data but doesn't works for me because I'm using the new version of react-router-redux 同步商店。

这是客户端的代码:

return (
  <MuiThemeProvider muiTheme={letgoMuiTheme}>
    <Provider store={store}>
      <IntlProvider>
        { routes(history, store) }
      </IntlProvider>
  </Provider>
</MuiThemeProvider>
);

路线:

export default function routes(history) {
  return (
    <ConnectedRouter history={history}>
      <Switch>
        <Route exact path="/" component={App} />
        <Route path="/:lang/chat" component={Chat} />
        ...
        <Route path="*" component={NotFound} status={404} />
      </Switch>
    </ConnectedRouter>
  );
}

服务器:

if (context.url) {
  res.redirect(302, redirectLocation.pathname + redirectLocation.search);
} else {
  const render = () => {
    const body = renderToString(
      <StaticRouter
        location={req.url}
        context={context}
      >
        <MuiThemeProvider muiTheme={muiTheme}>
          <Provider store={store}>
            <IntlProvider>
              { routes(history, store) }
            </IntlProvider>
          </Provider>
        </MuiThemeProvider>,
      </StaticRouter>
    );

    res.setHeader('content-language', locale);
    res.render('index', {
      head,
      body,
      locale,
      state: stringState,
      ...
    });
  };

  Promise.all(
    prefetchData(renderProps, store),
  )
  .then(render)
  .catch(prefetchError => next(prefetchError));
}

这是我的旧服务器文件:

    match({ history, routes, location: req.url }, (error, redirectLocation, renderProps) => {
      if (error) {
        throw error;
      } else if (redirectLocation) {
        res.redirect(302, redirectLocation.pathname + redirectLocation.search);
      } else if (renderProps) {
        // Render react components once store is initializaed and return HTTP response
        const render = () => {
          const body = renderToString(
            <MuiThemeProvider muiTheme={muiTheme}>
              <Provider store={store}>
                <IntlProvider>
                  <RouterContext {...renderProps} />
                </IntlProvider>
              </Provider>
            </MuiThemeProvider>,
          );

          res.setHeader('content-language', locale);
          res.render('index', {
            head,
            newrelicScript,
            body,
            locale,
            state: stringState,
            ...
          });
        };

        // Fetch components data and render HTML (no matter the fetch results
        // we need to render something)
        Promise.all(
          prefetchData(renderProps, store),
        )
        .then(render)
        .catch(prefetchError => next(prefetchError));
      } else {
        logger.warning('Ops !!! We should never arrive here :(');
        next();
      }
    });

并预取数据:

export const prefetchData = (renderProps, store) => {
  const { components, params } = renderProps;
  const { dispatch } = store;
  const state = store.getState();
  const extractWrappedComponent = (component) => {
    if (component.WrappedComponent) {
      return extractWrappedComponent(component.WrappedComponent);
    }
    return component;
  };

  return components
    .filter(component => component !== undefined)
    // Get component, be aware if they are wrapped by redux connect or intl.
    .map(component => extractWrappedComponent(component))
    // Get the fetchData method of each component
    .map(component => component.fetchData)
    .filter(fetchData => fetchData !== undefined)
    // Invoke each fetchData to initialize the redux state.
    .map(fetchData => fetchData(state, dispatch, params));
};

在研究之后,我找到了适合我的解决方案。这是助手(之前是 prefetchData)。 matchPath 来自 react-router-dom.

export const getRouteData = (routesArray, url, store) => {
  const { dispatch } = store;
  const state = store.getState();
  const extractWrappedComponent = (component) => {
    if (component.WrappedComponent) {
      return extractWrappedComponent(component.WrappedComponent);
    }
    return component;
  };

  return routesArray.map((route) => {
    const component = route.component;
    const extractedComponent = extractWrappedComponent(component);
    const match = matchPath(url, { path: route.path, exact: route.exact, strict: false });

    if (match) {
      if (extractedComponent.fetchData !== undefined) {
        extractedComponent.fetchData(state, dispatch, match.params);
      }
    }
  });
}

那我就叫它:

getRouteData(routes, req.url, store);

路线是 static routes:

export const routes = [
  {
    path: '/',
    component: App
  },
  {
    path: '/:lang/chat',
    component: Chat
  }
  ...

我的服务器文件现在没有匹配项。相反:

if (context.url) {
  res.redirect(302, context.url);
} else {
  const render = () => {
    const body = renderApp(req, muiTheme, store, history, context);

    res.render('index', {
      head,
      body,
      locale,
      ...
    });
  };

  Promise.all(
    getRouteData(routes, req.url, store),
  )
  .then(render)
  .catch(prefetchError => next(prefetchError));

RenderApp 文件(客户端必须与 StaticRouter 相同):

const renderApp = (req, muiTheme, store, history, context) => {
  const app = (
    <MuiThemeProvider muiTheme={muiTheme}>
      <Provider store={store}>
        <IntlProvider>
          <StaticRouter
            location={req.url}
            context={context}
          >
            { appRoutes(history) }
          </StaticRouter>
        </IntlProvider>
      </Provider>
    </MuiThemeProvider>
  );

  return renderToString(app);
};

appRoutes 文件:

export default function appRoutes(history) {
  return (
    <ConnectedRouter history={history}>
      <div>
        {routes.map((route, key) => (
          <Route key={key} {...route} />
        ))}
      </div>
    </ConnectedRouter>
  );
}