Когда я начал создавать Shindig, я понял, что все состояние просмотра моих компонентов было потеряно, когда я переходил на другую вкладку, а затем обратно. Это особенно ужасно иногда. Представьте, что вы указали поля поиска, прокрутили список вглубь, щелкнули элемент в этом списке, затем нажали назад, чтобы вернуться к списку, и все ваши поля поиска исчезли, и вы вернулись в начало списка. . Это просто неприемлемо для любого качественного приложения.

Я начал спрашивать на форумах и продолжал получать однострочные ответы: Flux. Хотя Flux интересен, особенно для обработки данных с сервера, меня не устроило его простое запоминание некоторого состояния и scrollTop элемента.

Вместо использования Flux я придумал действительно простой способ сохранения всего этого состояния в глобальном изменяемом объекте состояния, который передается по иерархии компонентов.

Например, Scroll — это простой компонент, который поддерживает позицию прокрутки списка. Он проверяет экземпляр, когда он монтируется, чтобы установить scrollTop, а когда он размонтируется, он мутирует экземпляр, чтобы запомнить scrollTop на время монтирования компонента.

Scroll = createView
  componentDidMount: ->
    @getDOMNode().scrollTop = @props.instance.scrollTop or 0
  componentWillUnmount: ->
    @props.instance.scrollTop = @getDOMNode().scrollTop
  render: ->
    div
      className: 'scroll'
      @props.children

Родительский компонент может передавать экземпляры своим дочерним элементам как свойство собственного экземпляра.

EventList = createView
  componentWillMount: ->
    @props.instance.scroll = @props.instance.scroll or {}
  renderEventItem: (event) ->
    EventItem({event})
  render: ->
    Scroll
      instance: @props.instance.scroll
      @props.events.map(@renderEventItem)

В конце концов, все состояние приложения поднимается до одного экземпляра верхнего уровня.

Meteor.startup ->
  instance = {}
  React.render(App({instance}), document.body)

Вот это действительно круто. Все состояние пользовательского интерфейса хранится в одной переменной. Вы могли заметить, что перезагрузка в режиме реального времени — это боль — каждый раз, когда страница перезагружается, когда вы пишете какой-то код, вы теряете все состояние своего пользовательского интерфейса. На самом деле мы можем сериализовать и сохранять эти данные в localStorage в браузере между перезагрузками в реальном времени, сохраняя состояние пользовательского интерфейса во время разработки и во время отправки горячего кода. Мы используем тот же Meteor API, что и с Session/ReactiveDict.

instance = Meteor._reload.migrationData('react-ui-instance') or {}
Meteor._reload.onMigrate 'react-ui-instance', ->
  return [true, instance]

Рефакторинг

Единственная плохая сторона этого паттерна заключается в том, что он связан с мутациями. Изменение данных — это антишаблон. Но иногда мутация неизбежна, и в этом случае лучше всего ограничить мутацию функцией с неизменяемым интерфейсом. Это именно то, что я сделал в виде миксина.

Scroll = createView
  mixins: [InstanceMixin]
  componentDidMount: ->
    @getDOMNode().scrollTop = @props.instance.scrollTop or 0
  save: ->
    scrollTop: @getDOMNode().scrollTop
  render: ->
    div
      className: 'scroll'
      @props.children
EventList = createView
  mixins: [InstanceMixin]
  renderEventItem: (event) ->
    EventItem({event, key:event.id})
  render: ->
    Scroll
      instance: @childInstance('scroll')
      @props.events.map(@renderEventItem)

Используя InstanceMixin, мы также можем сохранять снимок всего пользовательского интерфейса каждый раз, когда вызывается сохранение, чтобы мы могли перематывать историю! Это довольно круто в теории, но, честно говоря, я не нашел это ужасно полезным.

Это всего лишь одна из функций моего пакета Meteor, ccorcos:react-ui.

Однако я должен признать, что если бы мне пришлось делать это снова, я бы, вероятно, просто использовал Redux. Его не было, когда я начал этот проект, но он очень похож и имеет гораздо большую поддержку сообщества с помощью плагинов Chrome DevTools и т. д.

Теперь, когда у нас есть средства сохранения экземпляров компонентов между монтированиями, мы можем создать контроллер представления вкладок, который поддерживает положение прокрутки каждого представления при переключении между вкладками. Это тема следующей статьи.