Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/reactjs/25.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Javascript 将Apollo客户端与NextJS一起使用时服务器端渲染数据?_Javascript_Reactjs_Graphql_Next.js_Apollo Client - Fatal编程技术网

Javascript 将Apollo客户端与NextJS一起使用时服务器端渲染数据?

Javascript 将Apollo客户端与NextJS一起使用时服务器端渲染数据?,javascript,reactjs,graphql,next.js,apollo-client,Javascript,Reactjs,Graphql,Next.js,Apollo Client,我的组件目前在浏览器上运行,这是我希望避免的。当您访问该链接时,我希望它预先添加所有需要显示的数据,即在服务器上呈现的数据。当前,该组件的外观如下所示: import { graphql } from "react-apollo"; import gql from 'graphql-tag'; import withData from "../../apollo/with-data"; import getPostsQuery from '../../apollo/schemas/getPost

我的组件目前在浏览器上运行,这是我希望避免的。当您访问该链接时,我希望它预先添加所有需要显示的数据,即在服务器上呈现的数据。当前,该组件的外观如下所示:

import { graphql } from "react-apollo";
import gql from 'graphql-tag';
import withData from "../../apollo/with-data";
import getPostsQuery from '../../apollo/schemas/getPostsQuery.graphql';


const renderers = {
  paragraph: (props) => <Typography variant="body2" gutterBottom {...props} />,
};

const GET_POSTS = gql`${getPostsQuery}`;

const PostList = ({data: {error, loading, posts}}) => {
  let payload;
  if(error) {
    payload = (<div>There was an error!</div>);
  } else if(loading) {
    payload = (<div>Loading...</div>);
  } else {
    payload = (
      <>
        {posts.map((post) => (
          <div>
            <div>{post.title}</div>
            <div>{post.body}</div>
          </div>
        ))}
      </>
    );
  }
    return payload;
};

export default withData(graphql(GET_POSTS)(PostList));
// apollo/with-data.js

import React from "react";
import PropTypes from "prop-types";
import { ApolloProvider, getDataFromTree } from "react-apollo";
import initApollo from "./init-apollo";

export default ComposedComponent => {
  return class WithData extends React.Component {
    static displayName = `WithData(${ComposedComponent.displayName})`;
    static propTypes = {
      serverState: PropTypes.object.isRequired
    };

    static async getInitialProps(ctx) {
      const headers = ctx.req ? ctx.req.headers : {};
      let serverState = {};

      // Evaluate the composed component's getInitialProps()
      let composedInitialProps = {};
      if (ComposedComponent.getInitialProps) {
        composedInitialProps = await ComposedComponent.getInitialProps(ctx);
      }

      // Run all graphql queries in the component tree
      // and extract the resulting data
      if (!process.browser) {
        const apollo = initApollo(headers);
        // Provide the `url` prop data in case a graphql query uses it
        const url = { query: ctx.query, pathname: ctx.pathname };

        // Run all graphql queries
        const app = (
          <ApolloProvider client={apollo}>
            <ComposedComponent url={url} {...composedInitialProps} />
          </ApolloProvider>
        );
        await getDataFromTree(app);

        // Extract query data from the Apollo's store
        const state = apollo.getInitialState();

        serverState = {
          apollo: {
            // Make sure to only include Apollo's data state
            data: state.data
          }
        };
      }

      return {
        serverState,
        headers,
        ...composedInitialProps
      };
    }

    constructor(props) {
      super(props);
      this.apollo = initApollo(this.props.headers, this.props.serverState);
    }

    render() {
      return (
        <ApolloProvider client={this.apollo}>
          <ComposedComponent {...this.props} />
        </ApolloProvider>
      );
    }
  };
};
更新:我尝试将官方withApollo示例合并到我的项目中,但它在
getDataFromTree()上抛出了一个不变的错误。

元素类型无效:需要字符串(对于内置组件)或类/函数(对于复合组件),但得到:未定义

我对
/init/apollo.js
/components/blog/PostList.jsx
/pages/blog/jsx
文件使用了与示例repo中完全相同的代码。我的具体案例中唯一的区别是,我有一个明确的
\u app.jsx
,内容如下:

/* eslint-disable max-len */

import '../static/styles/fonts.scss';
import '../static/styles/style.scss';
import '../static/styles/some.css';

import CssBaseline from '@material-ui/core/CssBaseline';
import { ThemeProvider } from '@material-ui/styles';
import jwt from 'jsonwebtoken';
import withRedux from 'next-redux-wrapper';
import App, {
  Container,
} from 'next/app';
import Head from 'next/head';
import React from 'react';
import { Provider } from 'react-redux';

import makeStore from '../reducers';
import mainTheme from '../themes/main-theme';
import getSessIDFromCookies from '../utils/get-sessid-from-cookies';
import getLanguageFromCookies from '../utils/get-language-from-cookies';
import getUserTokenFromCookies from '../utils/get-user-token-from-cookies';
import removeFbHash from '../utils/remove-fb-hash';

class MyApp extends App {
  static async getInitialProps({ Component, ctx }) {
    let userToken;
    let sessID;
    let language;

    if (ctx.isServer) {
      ctx.store.dispatch({ type: 'UPDATEIP', payload: ctx.req.headers['x-real-ip'] });

      userToken = getUserTokenFromCookies(ctx.req);
      sessID = getSessIDFromCookies(ctx.req);
      language = getLanguageFromCookies(ctx.req);
      const dictionary = require(`../dictionaries/${language}`);
      ctx.store.dispatch({ type: 'SETLANGUAGE', payload: dictionary });
      if(ctx.res) {
        if(ctx.res.locals) {
          if(!ctx.res.locals.authenticated) {
            userToken = null;
            sessID = null;
          }
        }
      }
      if (userToken && sessID) { // TBD: validate integrity of sessID
        const userInfo = jwt.verify(userToken, process.env.JWT_SECRET);
        ctx.store.dispatch({ type: 'ADDUSERINFO', payload: userInfo });
      }
      ctx.store.dispatch({ type: 'ADDSESSION', payload: sessID }); // component will be able to read from store's state when rendered
    }
    const pageProps = Component.getInitialProps ? await Component.getInitialProps(ctx) : {};
    return { pageProps };
  }

  componentDidMount() {
    // Remove the server-side injected CSS.
    const jssStyles = document.querySelector('#jss-server-side');
    if (jssStyles) {
      jssStyles.parentNode.removeChild(jssStyles);
    }
    // Register serviceWorker
    if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/serviceWorker.js'); }

    // Handle FB's ugly redirect URL hash
    removeFbHash(window, document);
  }

  render() {
    const { Component, pageProps, store } = this.props;

    return (
      <Container>
        <Head>
          <meta name="viewport" content="user-scalable=0, initial-scale=1, minimum-scale=1, width=device-width, height=device-height, shrink-to-fit=no" />
          <meta httpEquiv="X-UA-Compatible" content="IE=edge,chrome=1" />
          <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
          <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
          <link rel="icon" type="image/png" sizes="194x194" href="/favicon-194x194.png" />
          <link rel="icon" type="image/png" sizes="192x192" href="/android-chrome-192x192.png" />
          <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
          <link rel="manifest" href="/site.webmanifest" />
          <link rel="mask-icon" href="/safari-pinned-tab.svg" color="#663300" />
          <meta name="msapplication-TileColor" content="#da532c" />
          <meta name="msapplication-TileImage" content="/mstile-144x144.png" />
        </Head>
        <ThemeProvider theme={mainTheme}>
          {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
          <CssBaseline />
            <Provider store={store}>
              <Component {...pageProps} />
            </Provider>
        </ThemeProvider>
      </Container>
    );
  }
}

export default withRedux(makeStore)(MyApp);
/*eslint禁用最大长度*/
导入“../static/styles/fonts.scss”;
导入“../static/styles/style.scss”;
导入“../static/styles/some.css”;
从“@material ui/core/CssBaseline”导入CssBaseline;
从'@material ui/styles'导入{ThemeProvider};
从“jsonwebtoken”导入jwt;
从“下一个redux包装器”导入withRedux;
导入应用程序{
集装箱,
}来自“下一步/应用程序”;
从“下一个/头”导入头;
从“React”导入React;
从'react redux'导入{Provider};
从“../reducers”导入makeStore;
从“../themes/main-theme”导入主主题;
从“../utils/get sessiond from cookies”导入GetSessiondFromcookies;
从“../utils/get language from cookies”导入GetLanguage from cookies;
从“../utils/get user token from cookies”导入GetUserTokenfrom cookies;
从“../utils/remove fb hash”导入removeFbHash;
类MyApp扩展了应用程序{
静态异步getInitialProps({Component,ctx}){
让userToken;
让我们一起去吧;
让语言;
if(ctx.isServer){
调度({type:'UPDATEIP',payload:ctx.req.headers['x-real-ip']});
userToken=getUserTokenFromCookies(ctx.req);
sessiond=getsessiondfromcookies(ctx.req);
语言=getLanguageFromCookies(ctx.req);
const dictionary=require(`../dictionary/${language}`);
调度({type:'SETLANGUAGE',负载:dictionary});
如果(ctx.res){
如果(ctx.res.locals){
如果(!ctx.res.locals.authenticated){
userToken=null;
sessiond=null;
}
}
}
if(userToken&&sessiond){//TBD:验证sessiond的完整性
const userInfo=jwt.verify(userToken,process.env.jwt_SECRET);
调度({type:'ADDUSERINFO',负载:userInfo});
}
dispatch({type:'ADDSESSION',payload:sessiond});//组件将能够在呈现时读取存储的状态
}
const pageProps=Component.getInitialProps?等待Component.getInitialProps(ctx):{};
返回{pageProps};
}
componentDidMount(){
//删除服务器端注入的CSS。
const jssStyles=document.querySelector(“#jss服务器端”);
if(jssStyles){
jssStyles.parentNode.removeChild(jssStyles);
}
//注册服务人员
如果导航器中的('serviceWorker'){navigator.serviceWorker.register('/serviceWorker.js');}
//处理FB丑陋的重定向URL哈希
removeFbHash(窗口、文档);
}
render(){
const{Component,pageProps,store}=this.props;
返回(
{/*CssBaseline启动了一个优雅、一致且简单的基线来构建。*/}
);
}
}
使用Redux(makeStore)(MyApp)导出默认值;
摆脱这个文件不是一个选项,因为这是我处理一些预加载cookie逻辑的地方

回购协议,作为参考,在

上有一些研究可以帮助:

我相信您应该使用
getMarkupFromTree
,如本期所示,以及如何实现

如果你想使用hooks,你需要@特洛伊奥斯基的
react阿波罗hooks
软件包


有人说这个解决方案不起作用。有人认为它有一些不足之处,例如,它两次呈现整个标记,一次接一次,一次获取apollo查询。作为回应,他们建议做一些事情,比如在GetInitialProps中直接调用查询,这只是比应该做的更多的工作,因为ssr特性应该准备好开箱即用

使用Next.js和Apollo时,您需要实现两个关键目标:SSR和缓存的网络数据。 这是一个难以兼顾的平衡。但这是可能的

方法是:

  • 在getInitialProps函数中,使用Apollo客户端获取在页面加载时应该可见的数据。如果操作正确,数据将与HTML ie(SSR)一起检索,并且在初始页面加载时不会出现令人讨厌的加载程序
  • 现在,如果您有一些需要编辑、添加或删除的页面数据,并且希望在更改后更新页面,而不刷新页面,以上内容是不够的。 因为,例如,如果编辑数据,典型/推荐的阿波罗方法是什么都不做。阿波罗为你神奇地处理这一切。除此之外,初始数据必须来自apollo缓存,并且必须有一个Id字段。 现在,由于您直接从服务器加载了初始数据,很可能您没有从以前缓存的数据中读取数据

    因此,需要执行下面的步骤2,以便在数据更改时自动刷新数据

  • 您知道要编辑的任何数据都必须来自缓存。因此,不要试图使用getInitialProps中的数据来填充此类数据。相反,您可以使用useQuery或其等价物使用相同的Graphql查询来查询相同的数据
    /* eslint-disable max-len */
    
    import '../static/styles/fonts.scss';
    import '../static/styles/style.scss';
    import '../static/styles/some.css';
    
    import CssBaseline from '@material-ui/core/CssBaseline';
    import { ThemeProvider } from '@material-ui/styles';
    import jwt from 'jsonwebtoken';
    import withRedux from 'next-redux-wrapper';
    import App, {
      Container,
    } from 'next/app';
    import Head from 'next/head';
    import React from 'react';
    import { Provider } from 'react-redux';
    
    import makeStore from '../reducers';
    import mainTheme from '../themes/main-theme';
    import getSessIDFromCookies from '../utils/get-sessid-from-cookies';
    import getLanguageFromCookies from '../utils/get-language-from-cookies';
    import getUserTokenFromCookies from '../utils/get-user-token-from-cookies';
    import removeFbHash from '../utils/remove-fb-hash';
    
    class MyApp extends App {
      static async getInitialProps({ Component, ctx }) {
        let userToken;
        let sessID;
        let language;
    
        if (ctx.isServer) {
          ctx.store.dispatch({ type: 'UPDATEIP', payload: ctx.req.headers['x-real-ip'] });
    
          userToken = getUserTokenFromCookies(ctx.req);
          sessID = getSessIDFromCookies(ctx.req);
          language = getLanguageFromCookies(ctx.req);
          const dictionary = require(`../dictionaries/${language}`);
          ctx.store.dispatch({ type: 'SETLANGUAGE', payload: dictionary });
          if(ctx.res) {
            if(ctx.res.locals) {
              if(!ctx.res.locals.authenticated) {
                userToken = null;
                sessID = null;
              }
            }
          }
          if (userToken && sessID) { // TBD: validate integrity of sessID
            const userInfo = jwt.verify(userToken, process.env.JWT_SECRET);
            ctx.store.dispatch({ type: 'ADDUSERINFO', payload: userInfo });
          }
          ctx.store.dispatch({ type: 'ADDSESSION', payload: sessID }); // component will be able to read from store's state when rendered
        }
        const pageProps = Component.getInitialProps ? await Component.getInitialProps(ctx) : {};
        return { pageProps };
      }
    
      componentDidMount() {
        // Remove the server-side injected CSS.
        const jssStyles = document.querySelector('#jss-server-side');
        if (jssStyles) {
          jssStyles.parentNode.removeChild(jssStyles);
        }
        // Register serviceWorker
        if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/serviceWorker.js'); }
    
        // Handle FB's ugly redirect URL hash
        removeFbHash(window, document);
      }
    
      render() {
        const { Component, pageProps, store } = this.props;
    
        return (
          <Container>
            <Head>
              <meta name="viewport" content="user-scalable=0, initial-scale=1, minimum-scale=1, width=device-width, height=device-height, shrink-to-fit=no" />
              <meta httpEquiv="X-UA-Compatible" content="IE=edge,chrome=1" />
              <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
              <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
              <link rel="icon" type="image/png" sizes="194x194" href="/favicon-194x194.png" />
              <link rel="icon" type="image/png" sizes="192x192" href="/android-chrome-192x192.png" />
              <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
              <link rel="manifest" href="/site.webmanifest" />
              <link rel="mask-icon" href="/safari-pinned-tab.svg" color="#663300" />
              <meta name="msapplication-TileColor" content="#da532c" />
              <meta name="msapplication-TileImage" content="/mstile-144x144.png" />
            </Head>
            <ThemeProvider theme={mainTheme}>
              {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
              <CssBaseline />
                <Provider store={store}>
                  <Component {...pageProps} />
                </Provider>
            </ThemeProvider>
          </Container>
        );
      }
    }
    
    export default withRedux(makeStore)(MyApp);