使用 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 contextclientof typefunctionsupplied toComponent, expectedobject.

如果 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);

需要由&lt;head&gt; 上升而不是由身体下降。这样的不,现在让我绊倒了一段时间

【讨论】:

以上是关于使用 s-s-r 和 Apollo 客户端时防止客户端重新渲染的主要内容,如果未能解决你的问题,请参考以下文章

Apollo Client + Next.js 不发送非 s-s-r 请求

next-with-apollo,s-s-r 不起作用,请求在客户端完成

在 s-s-r 和动态 URL 中使用 Apollo 进行查询 (Next.js)

Apollo 中 NextJs s-s-r 和 cookie 的问题

Next Js 自定义路由和 s-s-r

作为道具传递与提取缓存 Apollo 客户端 Nextjs