使用 s-s-r 和 Apollo 客户端时防止客户端重新渲染
Posted
技术标签:
【中文标题】使用 s-s-r 和 Apollo 客户端时防止客户端重新渲染【英文标题】:Prevent client side re-render when using s-s-r and Apollo client 【发布时间】:2019-10-08 04:29:21 【问题描述】:简而言之,问题是我在服务器端渲染一个 html 文档,然后 React 应用程序水合并重新渲染已经存在的内容。在那之后,应用程序在客户端工作得很好。 我正在使用 React、Apollo Client (Boost 0.3.1)、Node、Express 和我们内部的 graphql 服务器。
在此处查看实际操作:https://www.slowdownshow.org/
我主要尝试过文档中的建议: https://www.apollographql.com/docs/react/features/server-side-rendering
这里有什么不清楚的。我是否假设如果我实施 Store Rehydration Apollo Client xhr 请求来获取数据将不需要发生?如果是这样,问题是我已经尝试了文档建议的商店补液,但文档有点模棱两可
<script>
window.__APOLLO_STATE__ = JSON.stringify(client.extract());
</script>
在这种情况下,客户是什么?我相信它是 ApolloClient。但它是一种方法而不是对象,如果我在这里使用它,我会收到类似
的错误消息Warning: Failed context type: Invalid context
clientof type
functionsupplied to
Component, expected
object.
如果 Store Rehydration 技术不是防止不必要的客户端重新渲染的方法 - 我不清楚是什么。
这里是相关的服务器代码:
import React from 'react';
import ReactDOM from 'react-dom/server';
import ApolloProvider, renderToStringWithData from 'react-apollo';
import ApolloClient from 'apollo-client';
import createHttpLink from 'apollo-link-http';
import InMemoryCache from 'apollo-cache-inmemory';
import FragmentMatcher from '../shared/graphql/FragmentMatcher';
import HelmetProvider from 'react-helmet-async';
import ServerLocation from 'apm-titan';
import App from '../shared/App';
import fs from 'fs';
import os from 'os';
import
globalHostFunc,
replaceTemplateStrings,
isFresh,
apm_etag,
siteConfigFunc
from './utils';
export default function ReactAppSsr(app)
app.use((req, res) =>
const helmetContext = ;
const filepath =
process.env.APP_PATH === 'relative' ? 'build' : 'current/build';
const forwarded = globalHostFunc(req).split(':')[0];
const siteConfig = siteConfigFunc(forwarded);
const hostname = os.hostname();
const context = ;
const cache = new InMemoryCache( fragmentMatcher: FragmentMatcher );
let graphqlEnv = hostname.match(/dev/) ? '-dev' : '';
graphqlEnv = process.env.NODE_ENV === 'development' ? '-dev' : graphqlEnv;
const graphqlClient = (graphqlEnv) =>
return new ApolloClient(
s-s-rMode: false,
cache,
link: createHttpLink(
uri: `https://xxx$graphqlEnv.xxx.org/api/v1/graphql`,
fetch: fetch
)
);
;
let template = fs.readFileSync(`$filepath/index.html`).toString();
const component = (
<ApolloProvider client=graphqlClient>
<HelmetProvider context=helmetContext>
<ServerLocation url=req.url context=context>
<App forward=forwarded />
</ServerLocation>
</HelmetProvider>
</ApolloProvider>
);
renderToStringWithData(component).then(() =>
const helmet = helmetContext;
let str = ReactDOM.renderToString(component);
const is404 = str.match(/Not Found\. 404/);
if (is404?.length > 0)
str = 'Not Found 404.';
template = replaceTemplateStrings(template, '', '', '', '');
res.status(404);
res.send(template);
return;
template = replaceTemplateStrings(
template,
helmet.title.toString(),
helmet.meta.toString(),
helmet.link.toString(),
str
);
template = template.replace(/__GTMID__/g, `$siteConfig.gtm`);
const apollo_state = ` <script>
window.__APOLLO_STATE__ = JSON.stringify($graphqlClient.extract());
</script>
</body>`;
template = template.replace(/<\/body>/, apollo_state);
res.set('Cache-Control', 'public, max-age=120');
res.set('ETag', apm_etag(str));
if (isFresh(req, res))
res.status(304);
res.send();
return;
res.send(template);
res.status(200);
);
);
客户端:
import App from '../shared/App';
import React from 'react';
import hydrate from 'react-dom';
import ApolloProvider from 'react-apollo';
import HelmetProvider from 'react-helmet-async';
import client from '../shared/graphql/graphqlClient';
import '@babel/polyfill';
const graphqlEnv = window.location.href.match(/local|dev/) ? '-dev' : '';
const graphqlClient = client(graphqlEnv);
const Wrapped = () =>
const helmetContext = ;
return (
<HelmetProvider context=helmetContext>
<ApolloProvider client=graphqlClient>
<App />
</ApolloProvider>
</HelmetProvider>
);
;
hydrate(<Wrapped />, document.getElementById('root'));
if (module.hot)
module.hot.accept();
graphqlCLinet.js:
import fetch from 'cross-fetch';
import ApolloClient from 'apollo-client';
import createHttpLink from 'apollo-link-http';
import InMemoryCache from 'apollo-cache-inmemory';
import FragmentMatcher from './FragmentMatcher';
const cache = new InMemoryCache( fragmentMatcher: FragmentMatcher );
export const client = (graphqlEnv) =>
return new ApolloClient(
s-s-rMode: true,
cache,
link: createHttpLink(
uri: `https://xxx$graphqlEnv.xxx.org/api/v1/graphql`,
fetch: fetch
)
);
;
FragmentMatcher.js:
import IntrospectionFragmentMatcher from 'apollo-cache-inmemory';
const FragmentMatcher = new IntrospectionFragmentMatcher(
introspectionQueryResultData:
__schema:
types: [
kind: 'INTERFACE',
name: 'resourceType',
possibleTypes: [
name: 'Episode' ,
name: 'Link' ,
name: 'Page' ,
name: 'Profile' ,
name: 'Story'
]
]
);
export default FragmentMatcher;
查看客户端重新渲染的实际效果https://www.slowdownshow.org/
在上述代码的生产版本中,
我跳过状态补液window.__APOLLO_STATE__ = JSON.stringify($graphqlClient.extract());
,因为我没有它工作
【问题讨论】:
【参考方案1】:所以一旦我意识到自己犯了错误,答案就很简单了。我需要放
window.__APOLLO_STATE__ = JSON.stringify(client.extract());
</script>
在一切之前,以便阅读和使用它。
这个const apollo_state = ` <script>
window.__APOLLO_STATE__ = JSON.stringify($graphqlClient.extract());
</script>
</body>`;
template = template.replace(/<\/body>/, apollo_state);
需要由<head>
上升而不是由身体下降。这样的不,现在让我绊倒了一段时间
【讨论】:
以上是关于使用 s-s-r 和 Apollo 客户端时防止客户端重新渲染的主要内容,如果未能解决你的问题,请参考以下文章
Apollo Client + Next.js 不发送非 s-s-r 请求
next-with-apollo,s-s-r 不起作用,请求在客户端完成
在 s-s-r 和动态 URL 中使用 Apollo 进行查询 (Next.js)