简而言之,我如何在服务器上完成这项工作?
import React from 'react';
export default class RemoteText extends React.PureComponent {
constructor(props) {
super(props);
this.state = {text: null};
fetch(props.src).then(res => res.text()).then(text => {
this.setState({text});
})
}
render() {
if(this.state.text) {
return <span>{this.state.text}</span>;
}
return null;
}
}
即使我使用isomorphic-fetch,我也会收到此警告:
警告:setState(...):只能更新安装组件。这通常意味着您在服务器上的componentWillMount()之外调用了setState()。这是一个无操作。请检查RemoteText组件的代码。
哪个有道理。我相信我的组件在获取结束时已经被渲染为一个字符串。我需要以某种方式推迟渲染,直到完成,但我不知道如何。
Apollo以某种方式用getDataFromTree
管理这个,所以我确定它是可能的。
我怎么能做同样的事情?即,遍历我的组件树,提取所有待处理的提取,然后在ReactDOMServer.renderToString
之前等待它们解决?
由于它似乎不清楚,我想重申我希望抓取发生服务器端并且所有数据都应该在之前加载> strong>第一个渲染。提取应与组件共存(可能通过HOC)。
如果我可以在renderToString
之前将数据存入我的Redux商店,我已经有了一些代码来序列化它并将其传递给客户端。作为参考,我正在渲染我的应用程序:
export default async (req,res) => {
const rrCtx = {};
const store = createStore(combineReducers({
apollo: gqlClient.reducer(),
}), {}, applyMiddleware(gqlClient.middleware()));
const Chain = (
<ApolloProvider store={store} client={gqlClient}>
<StaticRouter location={req.url} context={rrCtx}>
<App/>
</StaticRouter>
</ApolloProvider>
);
await getDataFromTree(Chain); // <--- waits for GraphQL data to resolve before first render -- I wan the same but for fetch()
const html = ReactDOMServer.renderToString(Chain);
if(rrCtx.url) {
ctx.redirect(rrCtx.url);
} else {
const stats = await readJson(`${__dirname}/../webpack/stats.json`);
const styles = stats.assetsByChunkName.main.filter(f => f.endsWith('.css'));
const scripts = stats.assetsByChunkName.main.filter(f => f.endsWith('.js'));
let manifest = {};
try {
manifest = await readJson(`${__dirname}/../webpack/manifest.json`);
} catch(_){}
if(stats.assetsByChunkName.vendor) {
styles.unshift(...stats.assetsByChunkName.vendor.filter(f => f.endsWith('.css')));
scripts.unshift(...stats.assetsByChunkName.vendor.filter(f => f.endsWith('.js')));
}
res.send(`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Content-Language" content="en" />
<title>RedSpider</title>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
${styles.map(f => `<link rel="stylesheet" href="${stats.publicPath}${f}">`).join('\n')}
<link rel="icon" type="image/png" href="/spider.png">
${scripts.map(f => `<script src="${stats.publicPath}${f}" defer></script>`).join('\n')}
</head>
<body>
<div id="react-root">${html}</div>
<script>
window.__STATE__=${jsSerialize(store.getState())};
window.__MANIFEST__=${jsSerialize(manifest)};
</script>
</body>
</html>`);
}
}
我正在寻找类似于react-apollo的内容,但适用于任意提取/ GET请求,而不仅仅是GraphQL查询。
每个人都说&#34;只是await
它&#34;。等什么?提取是组件中的,我需要等待的位置在另一个文件中。问题是我如何收集fetch-promises,以便我可以等待它们,然后将这些数据恢复到组件中?
但是没关系,我会尝试逆向工程getDataFromTree并看看我是否可以开始工作。
答案 0 :(得分:2)
当您从服务器调用renderToString
时,react将调用构造函数,componentWillMount并呈现,然后从呈现的元素创建一个字符串。由于异步操作将在另一个tick上解析,因此不能使用componentWillMount或构造函数中的fetch。
更简单的方法(如果你不使用任何处理它的框架/ lib)就是在调用renderToString之前解析fetch 。
答案 1 :(得分:2)
我相信我的组件在获取结束时已经被渲染为一个字符串。我需要以某种方式推迟渲染,直到完成,但我不知道如何。 [...]我可以在我调用
renderToString
的地方获取数据(不是我想要的),但是我如何将其线程回到组件中?
AFAIK,您必须await
任何异步调用完成,然后才能以某种方式调用renderToString
。
这个问题并不常见,虽然这只是一个通用的答案,因为我使用自定义的flux实现而不是Redux。此外,此问题并不是React组件树的服务器端呈现所特有的,因为您可能需要等待客户端才能继续呈现到DOM中。
基本上,我如何解决这个特定问题(无论是在客户端还是服务器上)如下:
await
之前只需fetch
renderToString
(正如我所提到的,这几乎是不可避免的,因为您的数据必须存在才能继续响应) fetch
结果存储在您的商店fetch
在渲染器入口点,简单地说(虽然我相信这应该是显而易见的):
export default async (req, res) => {
await fetch().then(res => res.text()).then(text => {
this.setState({text});
});
// Store your data in your store here and instantiate your components.
const html = ReactDOMServer.renderToString(Chain);
}
在您的组件中:
export default class RemoteText extends React.PureComponent {
constructor(props, context) {
super(props, context);
// If your store has the data you need, set it directly.
// Otherwise, call your fetch so that it works on client seamlessly.
}
}
总的来说,这将确保您的组件在客户端上也能正常工作,并且在服务器上预呈现时具有所需的数据呈现。我担心你的取消电话awaiting
无法替代。
答案 2 :(得分:0)
如上所述,请在componentWillMount
componentWillMount=()=>{
fetch(props.src).then(res => res.text()).then(text => {
this.setState({text});
})
}
答案 3 :(得分:-1)
夫妻俩。
将fetch
请求移出构造函数,它不属于那里。 setState()
也不属于构造函数。
fetch
请求和后续setState()
调用属于componentDidMount()
,而不是构造函数。
此外,当您拨打setState()
电话时,您需要setState({text: text})
而不是setState({text})
。作为旁注,放弃具有单行的箭头函数的块是惯用的,尤其是在链接获取请求时。同样,将链式语句放在单独的行上可以提高可读性。即:
fetch(props.src)
.then(res => res.text())
.then(text => this.setState({text: text}))
如果您愿意,可以在最后链接.catch(error => console.log(error));
。同样重要的是,将fetch
请求和setState()
电话移至componentDidMount()
。
定义通用的获取方法是合适的(特别是如果其他地方会有更多);它可以接受requestObj,并将回调作为参数。您可以在componentDidMount()
内以及在您的应用中进行抓取请求的任何其他位置调用它。
在render()
来电中,将您返回的所有内容封装在顶级div中。在渲染调用中不需要返回null,即使this.state.text
为null,它仍然是不必要的。相反,当你在开始时学习React时,你最好总是返回一个div。如果您在使用内联运算符进行条件渲染之前没有看到它,它看起来像这样:
render(){
const text = this.state.text;
return (
<div>
{text &&
<span>{text}</span>
}
</div>
);
}
编辑:我第一眼就误解了你的目标。如果您想要的是在渲染之前加载所有数据,请通过props将数据传递给组件。我不确定组件树的结构是什么样的,或者你在服务器端做什么,但如果你不能单独使用props传递数据,则通过上下文将它传递给组件。为此,您需要一个更高阶的组件,将您的数据设置为上下文。 您现有的构造函数将如下所示:
constructor(props, context) {
super(props, context);
this.state = this.context.text || window.__INITIAL_STATE__ || {text: null};
}