Компоненты NextJS для трека в SSR - PullRequest
0 голосов
/ 11 июня 2018

Следующее относится к SSR через NextJS.

Я использую контекст React для отслеживания идентификаторов определенных подключенных компонентов.Суть

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 всегда [] на сервере.Есть ли другой способ отследить эти компоненты на стороне сервера ?По сути, мне нужно, чтобы идентификаторы предоставлялись сценарию для запуска в head документа - ожидание, пока клиентское приложение монтируется, запускается и добавляется в заголовок вручную, слишком медленно.

update

Вот быстрый пример репо: https://github.com/tills13/nextjs-ssr-context

this.context - это undefined в конструкторе Child, если я переместу его в componentDidMount (в настоящее время настраивается таким образом в репозитории), это работает, но я бы хотел, чтобы это было решено на стороне сервера.Я не зациклен на context, если есть другой способ сделать это, я весь в ушах.

Ответы [ 2 ]

0 голосов
/ 24 июня 2018

Я копался в этом, и я чувствую, что это невозможно по нескольким причинам.

Обновление 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.Но поскольку в случае рендеринга на стороне сервера этого не происходит, вы не получаете данные.

Я продолжаю копаться, чтобы посмотреть, доступны ли какие-либо хаки, но на данный момент я не уверен

0 голосов
/ 23 июня 2018

Использование API context внутри конструктора возможно, вам просто нужно передать его как параметр как в конструктор , так и в метод 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 /> не даст никакой раздачи, поэтому нам нужен обходной путь, который в любом случае будет взломом.

Решение, которое я получил, заключается в наличии нового window.ids с каждым отслеживаемым компонентом рендеринга (с использованием Head, предоставленного nextjs):

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.
здесь есть ссылка на репозиторий для проверки.

еще одно решение может использовать глобальную переменную на сервере, а затем в каждом отслеживаемомкомпонент, который мы могли бы изменить это глобальное изменение, используя componentWillMount ловушка жизненного цикла, поскольку она будет выполняться только на сервере, а затем внедрить эти идентификаторы в HTML-шаблон <head />, но это возможно, если мы выполняем метод renderToString.

Второе решение : использование страниц / _document вместо страниц / _app , чтобы мы получили доступ к серверу before it renders to string!:
это ветвь репо: origin / обходной путь-документ

-детский компонент:

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%, так как мы можем перехватить отслеживаемые идентификаторы до того, как произойдет повторная отрисовка сервера.enter image description here

, но <NextScript /> выдает предупреждение (не знаю почему, может быть ошибка из nextjs):
enter image description here

...