NextJS SSR中的轨道安装组件

时间:2018-06-11 19:32:17

标签: reactjs serverside-rendering nextjs react-context

以下内容适用于通过NextJS的SSR。

我使用React的上下文来跟踪某些已安装组件的ID。要点是

class Root extends React.Component {
  getChildContext () {
    return {
      registerComponent: this.registerComponent
    }
  }

  registerComponent = (id) => {
    this.setState(({ mountedComponents }) => {
      return { mountedComponents: [...mountedComponents, id ] }
    })
  }

  ...
}

class ChildComponent {
  static contextTypes = { registerComponent: PropTypes.func }

  constructor(props) {
    super(props)

    props.registerComponent(props.id)
  }
}

不幸的是,这只适用于客户端。服务器上的this.state.mountedComponents始终为[]。是否有另一种方法来跟踪这些组件服务器端?基本上我需要使用id来提供脚本以在文档的head中运行 - 等待客户端应用程序安装,运行并手动附加到头部有点太慢。

更新

这是一个快速示例回购:https://github.com/tills13/nextjs-ssr-context

this.contextundefined的构造函数中为Child,如果我将其移至componentDidMount(目前在回购中以这种方式设置),则可行,但我和#39; d喜欢这个在服务器端解析。我context并没有死定,如果还有其他办法,我全心全意。

2 个答案:

答案 0 :(得分:3)

我已经对此进行了深入研究,我的感觉是由于多种原因这是不可能的。

setState更新是异步的,在您的情况下将不会执行。

provider的渲染甚至会在状态更新之前发生,因此注册代码将在以后执行,并且渲染功能没有最新状态

我放入了不同的console.log,下面是我得到的

Provider constructed with { mountedComponents: [],
  name: 'name-Sun Jun 24 2018 19:19:08 GMT+0530 (IST)' }
getDerivedStateFromProps
Renderring Provider Component { mountedComponents: [],
  name: 'name-Sun Jun 24 2018 19:19:08 GMT+0530 (IST)' }
data loaded
constructor Child { id: '1' } { registerComponent: [Function: value] }
Register Component called 1 { mountedComponents: [],
  name: 'name-Sun Jun 24 2018 19:19:08 GMT+0530 (IST)' }
Registering component 1
constructor Child { id: '2' } { registerComponent: [Function: value] }
Register Component called 2 { mountedComponents: [ '1' ],
  name: 'tarun-Sun Jun 24 2018 19:19:08 GMT+0530 (IST)' }
Registering component 2
constructor Child { id: '3' } { registerComponent: [Function: value] }
Register Component called 3 { mountedComponents: [ '1', '2' ],
  name: 'name-Sun Jun 24 2018 19:19:08 GMT+0530 (IST)' }
Registering component 3
constructor Child { id: '4' } { registerComponent: [Function: value] }
Register Component called 4 { mountedComponents: [ '1', '2', '3' ],
  name: 'name-Sun Jun 24 2018 19:19:08 GMT+0530 (IST)' }
Registering component 4

该信息实际上仅在发生componentDidMount时可用。但是由于在服务器端渲染不会发生的情况下,您不会获得数据。

我正在进一步研究是否有可用的黑客手段,但是到目前为止,我不确定

答案 1 :(得分:2)

可以在构造函数内使用 context API,您只需将其作为参数传递给构造函数 super 方法如下:

export class Child extends React.Component {
  static contextTypes = { registerComponent: PropTypes.func };

  constructor(props, context) {
    super(props, context);
    context.registerComponent(props.id);
  }

  render() {
    return <div id={this.props.id} />;
  }
}

更新
因此,问题与服务器上的第一个渲染不支持任何交互性有关, 这种方法将阻止在服务器上所有setState的情况下使用,例如您的<Provider /> 不会给予任何帮助,因此我们需要一种变通办法,无论如何都将是一个hack。

我得到的解决方案是每个跟踪的组件渲染都使用一个新的window.ids(使用nextjs提供的Head):

export class Child extends React.Component {
  registerComponent = (id) => `
    if (window.ids) {
      window.ids = [...window.ids, ${id}];
    } else window.ids = [${id}];
  `;

  render() {
    return (
      <Fragment>
        <Head>
          <script
            className="track"
            dangerouslySetInnerHTML={{
              __html: `${this.registerComponent(this.props.id)}`,
            }}
          />
        </Head>
        <div id={this.props.id} />
      </Fragment>
    );
  }
}

因此window.ids变量将在<App /> renders之前可用。
这是repos link进行测试。

另一种解决方案可能是在服务器上使用全局变量,然后我们可以在每个跟踪的组件中进行突变 使用 componentWillMount 生命周期挂钩的全局变量,因为它将仅在服务器上执行,然后 将这些ID注入html模板<head />中,但是如果我们正在执行renderToString方法,则可以这样做。

第二种解决方案:使用页面/ _document 而不是页面/ _app ,这样我们就可以访问服务器before it renders to string!
这是仓库分支:origin/workaround-using-document

-子组件:

export class Child extends React.Component {
  render() {
    return (
      <Fragment>
        <Head>
          <meta className="track" id={this.props.id} />
        </Head>
        <div id={this.props.id} />
      </Fragment>
    );
  }
}

-文档组件(取代应用程序):

export default class MyDocument extends Document {
  render() {
    const { head } = this.props;
    const idsFromHead = head.reduce(
      (acc, { props }) =>
        (props.className.includes('track') && [...acc, props.id]) || acc,
      []
    );

    return (
      <html>
        <Head>
          <script
            dangerouslySetInnerHTML={{
              __html: `window.ids=[${idsFromHead}]`,
            }}
          />
        </Head>
        <body className="custom_class">
          <h3>{idsFromHead}</h3>
          <Main />
          <NextScript />
        </body>
      </html>
    );
  }
}

此方法可在服务器上100%正常运行,因为我们可以在发生服务器重新绑定之前捕获跟踪的ID。 enter image description here

但是<NextScript />正在生成警告(不知道为什么,可能是nextjs的错误):
enter image description here