我有一个使用服务器端渲染的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响应的情况下不能呈现,我该怎么做才能解决此问题?那可能吗?谢谢大家!
答案 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));
}