如何使组件仅在API调用完成后才能呈现?

时间:2020-02-06 18:26:11

标签: reactjs server-side-rendering

我有一个使用服务器端渲染的1年React应用程序,现在我们正在开发一个页面,该页面将被Googlebot索引。

问题是:我们需要一个异步api调用的响应,以在该数据中呈现页面以用于SEO。 Googlebot(view-page-source)不得包含Loading...组件或其他任何组件。它的内容必须是该api数据。

但是,我发现的所有示例/解决方案都要求使用componentDidMount,componentWillMount(不建议使用)甚至构造函数,但是所有这些示例/解决方案都将首先显示没有数据的页面,而Googlebot不会等待它完成。


示例:

API响应:

{ trip: { description: 'It was great', title: 'The Trip! 2.0' } } 

组件:


class HomePage extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      data: null,
    }
  }

  render() {
    return (
      <div>
        {
          this.state.data ? <h1>{this.state.data.title}</h1> : <p>none</p>
        }
      </div>
    );
  }
};

export default HomePage;

所有渲染所需的源代码:

<h1>The Trip! 2.0</h1>

从不:<p>none</p>


PS:此示例未模拟api调用,导致idk放在= x

考虑到该组件必须在没有api响应的情况下不能呈现,我该怎么做才能解决此问题?那可能吗?谢谢大家!

1 个答案:

答案 0 :(得分:0)

您必须在每个组件内定义一个函数,将其命名为loadData。此功能将完成组件的异步工作。 当服务器收到请求时,您必须查看该网址,然后决定要呈现的组件。

对于每个需要渲染的组件,我们将调用附加到每个组件的那个loadData函数来启动数据加载过程。这里的关键是我们不对应用程序进行一些初始渲染。我们只有一组组件。每个人都说“这是我需要的资源”。然后,无论何时有人提出请求,我们都会查看为显示页面而需要呈现的一组组件。然后,我们将使用所有这些组件,并使用这些附加的小数据加载需求函数,并分别调用它们。所有这些dataLoad函数都返回promise,因此我们必须在渲染应用程序之前检测所有已解析的函数。 假设我们有 Users.js ,因此我们将函数定义如下:

const loadData = store => {
  return store.dispatch(fetchUsers());
};

当我们导入组件时,我们会在对象内部导入。

export default {
  loadData:loadData,
  component: connect(mapStateToProps, { fetchUsers })(UsersList)
};

我们必须借助react-router-config来设置Routes.js文件。

Routes.js

import React from "react";
import Home from "./pages/Home";
import Users from "./pages/UsersList";
import App from "./App";
import NotFoundPage from "./pages/NotFoundPage";

export default [
  {
    ...App,
    //App component will be shown inside each component.
    //array of routes will be used inside the App.js down below
    routes: [
      {
        path: "/",
        ...Home,
        exact: true
      },
      { ...UsersList, path: "/users" },
      { ...AdminsListPage, path: "/admins" },
      { ...NotFoundPage }
    ]
  }
];

这是App.js

import React from "react";
import Header from "./components/Header";
import { renderRoutes } from "react-router-config";
import { fetchCurrentUser } from "./actions";

//this component is to render components that each component uses in common
//props.route is the child components that are passed from Routes.js
const App = ({ route }) => {
  return (
    <div>
      <Header />
      {renderRoutes(route.routes)}
    </div>
  );
};

export default {
  component: App,
  //this function is get called by redux so store is passed in
  //as you might know Header should always know about authentication status
  //we populate the store with the authentication info so Header can use it.
  loadData: ({ dispatch }) => dispatch(fetchCurrentUser())
};

现在我们设置了所有loadData函数,现在是时候在服务器收到请求时调用它们了。为此,我们将使用react-router-config中的matchRoutes函数。

app.get("*", (req, res) => {
  const store = createStore(req);

//express does not touch routing. it delegates everything to react.
//I will just place the logic behind invoking loadData functions here.
//matchRoutes will look at the path and will return the array of components to be loaded. 
//this is what matchRoutes function show `{ route: { loadData: [Function: loadData], path: '/users', component: [Object] },//component that we show
match: { path: '/users', url: '/users', isExact: true, params: {} } }
`
//
const promises = matchRoutes(Routes, req.path)
    .map(({ route }) => {
      return route.loadData ? route.loadData(store) : null;
    }) //we got the array of loadData functions now we are invoking them
    .map(promise => {
      if (promise) {
        return new Promise((resolve, reject) => {
          promise.then(resolve).catch(resolve);
        });
      }
    });
//Promise.all takes an array of promises and resolves when all of the items resolve
Promise.all(promises).then(()=>{
//here is when it is time to render your server-side code.

}).catch(e => console.log(e.message));


}
相关问题