8

以下适用于通过 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文档中运行 - 等待客户端应用程序安装、运行和手动附加到头部有点太慢了。

更新

这是一个简单的示例 repo:https ://github.com/tills13/nextjs-ssr-context

this.contextundefined的构造函数中Child,如果我将它移到componentDidMount(当前在 repo 中以这种方式设置),它可以工作,但我希望在服务器端解决这个问题。我没有死心塌地context,如果有其他方法可以做到这一点,我会全力以赴。

4

2 回答 2

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发生时才可用。但是,由于在没有发生服务器端渲染的情况下,您不会获得数据。

我正在进一步挖掘以查看是否有任何黑客可用,但截至目前我不确定

于 2018-06-24T14:01:02.640 回答
2

在构造函数中使用上下文API 是可能的,您只需在构造函数超级方法中将其作为参数传递,如下所示:

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 />

我得到的解决方案是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 链接来测试它。

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

第二种解决方案:使用 pages/ _document而不是 pages/ _app所以我们可以访问服务器before it renders to string!
这是 repo 分支: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。 在此处输入图像描述

但是<NextScript />正在生成警告(不知道为什么,可能是来自 nextjs 的错误):
在此处输入图像描述

于 2018-06-23T02:47:14.953 回答