使用SSR和Apollo客户端时防止客户端重新渲染

时间:2019-05-21 18:01:19

标签: reactjs graphql apollo ssr

简而言之,问题是我在服务器端渲染了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

这还不清楚。我是否假设如果实现存储补水功能,则不需要发生Apollo客户端xhr请求以获取数据?如果是这样的话,那么问题是我尝试了文档建议的商店补水建议,但是文档有点模棱两可

    <script>
        window.__APOLLO_STATE__ = JSON.stringify(client.extract());
    </script>

在这种情况下,客户是什么?我相信这是ApolloClient。但这不是方法,而是对象,如果在这里使用它,我会收到类似

的错误消息

Warning: Failed context type: Invalid context客户端of type功能supplied to组件, expected对象.

如果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({
            ssrMode: 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({
        ssrMode: 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 个答案:

答案 0 :(得分: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>向上而不是身体向下移动。真是个傻瓜,但让我绊了一会儿