React: Как реализовать shouldComponentUpdate с this.context?

Созданный на 13 нояб. 2014  ·  126Комментарии  ·  Источник: facebook/react

Я знаю, что this.context официально не существует, но на него полагается довольно много библиотек, и, похоже, он обретает форму с # 2509.

Я пытаюсь понять, как именно shouldComponentUpdate должен быть реализован с учетом context . Я заметил, что он принимает третий аргумент ( nextContext ), и я могу расширить PureRenderMixin чтобы также его проверить:

  shouldComponentUpdate: function(nextProps, nextState, nextContext) {
    return !shallowEqual(this.props, nextProps) ||
           !shallowEqual(this.state, nextState) ||
           !shallowEqual(this.context, nextContext); // this will throw without context, read on
  }

Компоненты, которые не выбирают this.context , не опуская contextTypes , не получат этот третий аргумент, что понятно.

Однако это представляет проблему, когда у нас есть компонент <Middle /> между <Top /> context owner и <Bottom /> context consumer. Если <Middle /> реализует ограничительное shouldComponentUpdate , <Bottom /> вообще не может реагировать на обновления контекста <Top /> :

( скрипка )

var Bottom = React.createClass({
  contextTypes: {
    number: React.PropTypes.number.isRequired
  },

  render: function () {
    return <h1>{this.context.number}</h1>
  }
});

var Middle = React.createClass({
  shouldComponentUpdate: function (nextProps, nextState, nextContext) {
    return false;
  },

  render: function () {
    return <Bottom />;
  }
});

var Top = React.createClass({
  childContextTypes: {
    number: React.PropTypes.number.isRequired
  },

  getInitialState: function () {
    return { number: 0 };
  },

  getChildContext: function () {
    return { number: this.state.number };
  },

  componentDidMount: function () {
    setInterval(function () {
      this.setState({
        number: this.state.number + 1
      });
    }.bind(this), 1000);
  },

  render: function() {
    return <Middle />;    
  }
});

React.render(<Top />, document.body);

Та же проблема возникла бы, если бы я попытался дать Middle общий контекстно-зависимый shouldComponentUpdate как я писал выше, потому что Middle не имеет this.context если он не выбрал в.

Это можно обойти, добавив contextTypes к Middle , но это не похоже на хорошее решение. Вам нужно будет явно добавить необходимые contextTypes на каждом уровне с помощью smart shouldComponentUpdate чтобы было слишком легко ошибиться.

Решит ли это # ​​2112? А пока есть другое решение? Какой рекомендуемый способ?

Component API Bug

Самый полезный комментарий

Интересно, может ли распространение контекста происходить при обходе отдельного дерева без блокировки ложным shouldComponentUpdate в середине?

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

Все 126 Комментарий

Это очень хороший вопрос. Спасибо, что подняли его!

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

@sebmarkbage - мысли?

Да, проблема в том, что средние компоненты не знают, какой контекст может понадобиться отдаленному ребенку. Они не могут узнать, какой contextTypes объявить, чтобы даже _согласно_ реализовать shouldComponentUpdate .

Интересно, может ли распространение контекста происходить при обходе отдельного дерева без блокировки ложным shouldComponentUpdate в середине?

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

@gaearon Я думаю, что в этом случае вам нужно все заново отрендерить, и поэтому shouldComponentUpdate не будет иметь эффекта обрезки поддеревьев. В противном случае контекст будет в несовместимом состоянии с деревом элементов.

@andreypopp

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

http://jsbin.com/geseduneso/2/edit?js , вывод

(Если бы это было так, оба числа увеличивались бы)

В чем непоследовательность?

Другими словами, я думаю, что context должно работать точно так же, как если бы владелец контекста имел что-то вроде «хранилища», в которое результат его getChildContext() записывается в componentWillUpdate , и все потребители контекста-потомка, которые объявили ключи из этого контекста в contextTypes , должны получить этот контекст, как если бы они были подписаны на это «хранилище» и обновили свое собственное состояние.

Я не имею в виду реальную реализацию - но я хочу показать, что эта модель не более противоречива, чем любое приложение Flux с shouldComponentUpdate посередине. Контекст должен действовать как «побочное хранилище», но ограничиваться поддеревом.

Изменить: это не сработает, потому что родитель может измениться

Я разговаривал с Гленджамином по IRC, и он убедил меня, что изменение контекста само по себе - плохая идея. Вы теряете возможность рассуждать о том, почему что-то было обновлено, если какое-то корневое обновление неявно вызывает разные дочерние обновления.

Но тогда я вижу единственное разумное решение - полностью запретить изменение контекста. То есть сделайте getChildContext() похожим на getInitialState() , которое вызывается только один раз перед монтированием компонента.

Это сделало бы контекст намного проще. Мы можем удалить третий параметр из shouldComponentUpdate поскольку контекст никогда не обновляется.

В случае, если вам _ нужны_ обновления от владельца контекста (например, как response-router хочет, чтобы activeRoutes передавался сверху вниз для использования в ActiveState mixin cc @mjackson), ничто не мешает вам передать { addChangeListener, removeChangeListener, getActiveRoutes } в context . Потомки теперь могут подписаться на изменения и поместить их в state .

Это разумное решение?

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

В каких сценариях передача _data_ через context предпочтительнее, чем передача данных через props или запуск события, заставляющего компонент вызывать свой собственный setState .

Я довольно успешно использую контекст для передачи ссылок на объекты, поскольку я всегда пишу в эти объекты, а не читаю. например. this.context.triggerAction("something")

Вариант использования контекста был для параметров, которые применимы к потенциально большому поддереву, но которые вы не хотите передавать через более общие узлы контейнера. Примером может быть цветовая тема, где большое количество узлов может прослушивать, чтобы увидеть, должен ли цвет фона быть белым или черным; Вы бы не хотели передавать их повсюду в качестве параметров.

Заставить getChildContext () вести себя как getInitialState () на самом деле не решит проблему, потому что вы всегда можете заменить родительский узел, который предоставляет заданное значение контекста, другим родительским узлом, который предоставляет другое значение контекста. Для затронутого поддерева это неотличимо от изменения значения контекстной переменной.

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

Думаю, @andreypopp прав. Или, по крайней мере, нам нужно предоставить способ shouldComponentUpdate знать, изменилось ли что-нибудь в контексте, чтобы он мог всегда возвращать истину, если произошло изменение переменной контекста.

Я поболтаю с

Заставить getChildContext () вести себя как getInitialState () на самом деле не решит проблему, потому что вы всегда можете заменить родительский узел, который предоставляет заданное значение контекста, другим родительским узлом, который предоставляет другое значение контекста. Для затронутого поддерева это неотличимо от изменения значения контекстной переменной.

Ой. Вы совершенно правы, я не учел этого.

@jsfb Если подумать, я не совсем понимаю ваш комментарий. Если вы замените родительский узел, все дочернее поддерево все равно будет повторно смонтировано, не так ли?

В настоящее время да, он будет перемонтирован, но отчасти это детали реализации. Вы можете себе представить (и мы обсуждали реализации и разветвления) повторное родительство поддеревьев без потери состояния узла.

Мы с @sebmarkbage поболтали и пришли к следующим выводам:

  • shouldComponentUpdate - это сложный аварийный выход, реализация которого требует от вас четкого понимания того, что происходит под компонентом. По этой причине небезосновательно предположить, что вы знаете, какие переменные контекста используются, поскольку вам уже нужно знать, какие свойства используются и как они используются.
  • Это изменение, вероятно, не делает ситуацию намного хуже, чем это было раньше, и это улучшение по сравнению с использованием старых отношений «собственник». Поэтому этот вопрос все еще стоит обсудить, но, вероятно, не является блокирующим.
  • На данный момент контексты - это сложная функция / эксперимент, и нам, вероятно, нужно будет больше подумать, прежде чем мы официально поддержим / задокументируем их. По мере того, как мы узнаем больше, мы, вероятно, продолжим итерацию по этой теме.

@sebmarkbage , дайте мне знать, если я что-то пропустил!

Хорошие обсуждения! Спасибо всем за отзывы!

Спасибо, что нашли время обсудить это!

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

Если компонент верхнего уровня Feed реализует shouldComponentUpdate , используя PureRenderMixin чтобы избежать дополнительных обновлений, это не означает, что Feed знает, что где-то внутри него есть - это Cell которое содержит Link , зависящее от контекста маршрутизатора.

Это становится еще хуже, когда фреймворки (такие как самая популярная структура маршрутизации React ) используют контекст, а вы можете даже не знать об этом. Где-то есть приложения, которые не меняют активное состояние ссылки, потому что кто-то оптимизировал компоненты верхнего уровня и буквально _не представлял_, что им нужно было объявить соответствующий contextTypes для _even get_ nextContext в своих shouldComponentUpdate .

Компоненты никоим образом не знают обо всех своих возможных потомках. По сути, если я перемещаю зависимый от контекста компонент в любое место внутри компонента с включенным PureRenderMixin , он сломается, но очень незаметно. Следовательно, если вы используете контекст, единственный способ избежать этой тонкой поломки несвязанных компонентов - это _ никогда_ реализовать shouldComponentUpdate что противоречит тому, что React говорит об этом.

Некоторые фреймворки поверх React, такие как Om от PureRenderMixin -ish shouldComponentUpdate _default_, странно так их отрезать. Это означает нарушение контекста shouldComponentUpdate в каждом компоненте для них.

Я согласен, что это не блокирует вашу работу, но я не могу согласиться с тем, что текущая ситуация является удовлетворительной, если контекст вообще будет использоваться. Реализовать shouldComponentUpdate с контекстом сейчас - это не просто _ сложно_ - это совершенно невозможно, если мы не сделаем предположение, что каждый компонент всегда знает, каким может быть его children .

Популярные библиотеки уже сильно зависят от контекста. Я понимаю, что это не поддерживаемая функция, но, на мой взгляд, она либо должна быть, по крайней мере, _возможна_, чтобы заставить ее работать с shouldComponentUpdate , либо она должна быть отключена, а библиотеки должны быть вынуждены не использовать ее, потому что она тонко сломан.

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

Я думаю, что нам нужно обойти shouldComponentUpdate если где-нибудь в родительском дереве есть новый контекст. Возможно с shouldUpdateChildContext или чем-то, что определяет, нужно ли согласовывать все поддерево.

@sebmarkbage

Я полагаю, это могло бы сработать, потому что контекст вряд ли должен часто меняться.
В этом случае shouldComponentUpdate не нуждается в третьем параметре, не так ли?

Правильно, это всегда было бесполезно.

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

В этом есть большой смысл. Изменения контекста в родительском элементе должны перекрыть ложные shouldComponentUpdate s в поддереве.

Но возникает вопрос: как узнать об изменении контекста? Для состояния и реквизита у нас есть setState и render / setProps . Похоже, нам нужно где-то хранить копию текущего контекста и сравнивать ее с результатом из getChildContext всякий раз, когда вызывается render .

Я использую контексты как способ управления ссылками на магазины. Мне это нравится, потому что я могу явно указать, какие магазины требуются, поэтому
1) Я могу смоделировать образцы данных только в тех хранилищах, которые мне нужны для тестирования компонента, избегая необходимости создавать экземпляр моей более крупной структуры, и
2) передать в простых хранилищах только для чтения известные данные для рендеринга на стороне сервера, что упрощает случай на стороне сервера, поскольку основная часть кода приложения не используется.

Итак, глядя на пример,

В моей реализации доступ к данным хранилища осуществляется только через геттеры. Мои элементы не кэшируют данные хранилища. Я хочу единственную версию правды. В качестве простого неасинхронного примера мой render () обычно имеет что-то вроде

var peopleStore = this.context.peopleStore;
var person = peopleStore.get(this.props.personId);
return <div>person.fullName</div>;

Компоненты прикрепляют слушателей к хранилищам. Я не совсем определился с детализацией. Во всех магазинах есть событие onChange. Однако я еще не решил еще две вещи,
1) слушатели индивидуальных изменений свойств
2) если в магазинах должны быть магазины

В этом примере, если "people" было "fb users", это большое сложное асинхронное хранилище. Следует ли повторно использовать структуру магазина для PersonStore? Один для общей коллекции (getFriends, getPerson и т. Д.), Но много уникальных экземпляров типа хранилища Person для отдельного человека.

Итак, в моем примере моему компоненту требуется хранилище People в качестве параметра контекста. Затем он использует опору personId для идентификации и подписки на конкретное хранилище Person.

А теперь внесем некоторые динамические изменения. Предположим, текущий пользователь выходит из системы, а кто-то другой входит в систему. Если не учитывать тот факт, что вы, вероятно, перенаправляете / обновляете страницу в реальном приложении, как этот элемент будет обновляться? Также предположим, что этот элемент все еще находится на странице, а не просто уничтожен.

Я ожидал, что логика приложения сначала удалит / уничтожит существующий магазин People. Для этого моему компоненту нужно перестать слушать обновления. Для этого я бы порекомендовал ReactClass API. Например,

onContextChange (предыдущая, новая)

Затем элемент может сравнить два экземпляра peopleStore. Давайте проигнорируем общедоступные данные и предположим, что новый PeopleStore имеет значение null. Элемент откажется от подписки на предыдущий магазин и вызовет render (). Затем рендер покажет какое-то сообщение типа «пользователь неизвестен».

Когда пользователь входит в систему как другой пользователь, создается новое хранилище, элемент повторно подключается, и render () выполняет свою работу с новыми данными.

За кулисами this.render () не может быть синхронным. Чтобы любой из моих проектов / примеров имел хоть какой-то смысл, вызовы render () должны быть собраны фреймворком и объединены вместе.

В отличие от props, прослушивание хранилищ выходит за рамки основной роли React по управлению рендерингом. Вот почему я не думаю, что в shouldComponentUpdate () следует включать изменение контекста. И несмотря на то, что мой пример включает изменение значений контекста (новый объект хранилища), я не думаю, что хранилища будут неизменными в своей высокоуровневой природе. Я думаю, что типичный дизайн приложения flux будет работать с моделью подписчика, обратными вызовами для async и т. Д. Базовый объект хранилища обычно живет в течение всего срока службы приложения.

В этом есть некоторый интерес. (См. Ссылки выше.)

Да @gaearon .

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

Смотрите также
https://github.com/yahoo/react-intl/issues/58
https://github.com/facebook/react/issues/3038

@slorber Я также принял пользовательский PureRenderMixin , он явно может сломаться с более строгим shouldComponentUpdate s в середине, как говорит @gaearon (или если третий параметр исчезнет). Однако вы всегда можете положиться на реквизит. Посмотрим, как будет развиваться эта проблема :)

@gpbl проверьте, что:
https://github.com/facebook/react/issues/3038#issuecomment -76449195

Мне кажется, что в случае, если мы хотим повторно визуализировать все приложение сверху в случае изменения контекста, мы можем размонтировать и повторно подключить узел в том же тике цикла событий, и это не вызывает мерцающих эффектов (постоянно). Это идеальный обходной путь для работы с изменениями языка пользователя.

@slorber Вызов unmountComponentAtNode ухудшает взаимодействие с пользователем, поскольку приводит к потере всего локального состояния.

Думаю, пора объявить контекст официальной функцией. Не использовать контекст - не вариант для большинства людей, потому что его использует маршрутизатор и ReactIntl.

@cody et al. Для ясности: контекст все еще может быть изменен. Мы все еще экспериментируем с ним и по-прежнему рекомендуем избегать его, пока не определимся с окончательным API. Вы используете на свой страх и риск. Это полезно: наверное. Готово: нет.

@cody для меня не проблема потерять локальное состояние, так как у меня нет локального состояния, и все мое приложение управляет неизменяемым состоянием исключительно за пределами React :) Посмотрите видео, которое я сделал для фреймворка: https: / /github.com/stample/atom-react

Я думаю, что нам нужно обойти shouldComponentUpdate, если где-то в родительском дереве есть новый контекст. Возможно, с shouldUpdateChildContext или чем-то, что определяет, нужно ли согласовывать все поддерево.

Да, shouldUpdateChildContext делает его красивым и симметричным.

Есть ли шанс, что это войдет в 0.14?

Если вы создадите хороший API и реализуете его. :)

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

Вряд ли у одной только нашей основной команды будет на это время. :(

9 апреля 2015 года в 13:32 Дэн Абрамов [email protected] написал:

Есть ли шанс, что это войдет в 0.14?

-
Ответьте на это письмо напрямую или просмотрите его на GitHub.

Конечно, я посмотрю!

Извините, ребята, я все еще не могу найти время, чтобы начать над этим работать. После ожидаемого выпуска React DnD 1.0 я буду занят подготовкой к конференции, поэтому очень маловероятно, что я смогу работать над этим в ближайшее время. :-(

@gaearon Есть планы посмотреть на это в ближайшее время? Мне интересно вникнуть в это, поскольку кажется, что это один из последних этапов создания полноценного контекста. (может быть, мне что-то еще не хватает?)

Единственное, что меня интересовало, - верно ли следующее утверждение:

Дочерний контекст компонента должен _всегда_ быть сокращением его свойств состояния и контекста.

Это гарантирует, что мы можем надежно передавать данные в рамках известного жизненного цикла. Это похоже на утверждение, что «рендеринг компонента всегда уменьшает состояние и свойства».

Вот как я сейчас это представляю - было бы хорошо посмотреть, не сбился ли я с курса !:

shouldUpdateChildContext определяет, следует ли обновлять поддерево новым контекстом. Эта часть жизненного цикла запускается при изменении свойств или контекста состояния компонента.

shouldComponentUpdate остается с 3 параметрами, поэтому компонент в вышеупомянутом поддереве может решить, действительно ли он должен что-либо обновлять (примечание: возврат false здесь НЕ влияет на контекст, перемещающийся по поддереву дальше)

Еще мне интересно, должен ли быть еще один новый метод жизненного цикла componentWillReceiveContext ? - Вызывается одновременно с componentWillReceiveProps Находится в потоке данных контекста. Здесь возможно, чтобы оба этих метода вызывались «одновременно».

@Chrisui

Пожалуйста, прыгай на это! Я сейчас слишком занят другими делами.

Это гарантирует, что мы можем надежно передавать данные в рамках известного жизненного цикла.

Также имейте в виду, что есть хук observe который может быть реализован или data / observed / как бы оно ни называлось. Но, вероятно, это пока не имеет отношения к вашей задаче.

На моей вилке есть очень начальный черновик, и вы можете видеть, что он работает по адресу examples/context/index.html

Я установил простую связь «родительский контекст» / «дочерний элемент контекста». «Родительский контекст» - это любой компонент, который может изменить его дочерний контекст (т. Е. Реализует childContextTypes ), а «дочерний элемент контекста» - это любой компонент, который зависит от контекста или может изменить его дочерний контекст (т. Е. Реализует contextTypes или childContextTypes ). Это отношение устанавливается, когда компоненты монтируются в настоящий момент.

Когда вступает в игру жизненный цикл обновления компонентов, у нас есть два случая:

  1. Если компонент говорит «do _not_ update» (т.е. shouldComponentUpdate() === false ), мы проверяем, следует ли обновлять дочерний контекст с помощью shouldUpdateChildContext . Если это true (по умолчанию), тогда, если у этого компонента есть какие-либо непосредственные «дочерние элементы контекста», мы переводим дочерний компонент в жизненный цикл обновления. Это продолжается до тех пор, пока мы не дойдем до конца контекстного дерева или не встретим shouldUpdateChildContext() === false
  2. Если компонент говорит «обновить», тогда ничего не изменится. В этом случае нет побочного направления контекстных данных, и мы позволяем нормальному потоку продолжаться до тех пор, пока компонент снова не попадет в случай №1.

Без сомнения, я пропустил некоторые важные вещи, поэтому, если бы кто-нибудь мог быстро взглянуть, было бы здорово. Приветствуются комментарии по архитектуре контекста, а также фактическое написание кода для соответствия кодовой базе React! :)

В качестве быстрого псевдо-примера рассмотрим следующее:

<Root num={1}>  // Gives context {num: this.props.num}
  <Mid>         // Ignores any context and shouldComponentUpdate() === false
    <Child>     // Requires context {num: propTypes.number}

Для первоначального рендера (монтажа) все как мы ожидали. (Предполагаемая деталь: между Root & Child создаются отношения родитель / потомок)

Мы обновляем Root чтобы значение num prop равнялось 2

<Root num={2} />

Mid также реализует метод shouldComponentUpdate() который возвращает false потому что его метод render() не заботится о context.num и больше ничего не изменилось - так зачем его обновлять?

Именно здесь в старых случаях компонент Child никогда не увидит новый контекст, так как мы отказались от обновления по дереву.

С этими изменениями мы теперь проверяем метод shouldUpdateChildContext() на Mid который может (в наших примерах мы разрешили выполнение действий по умолчанию) выполнить простую проверку равенства, чтобы увидеть, есть ли у каких-либо значений контекста изменилось. Примечание: подпись shouldUpdateChildContext() в настоящее время идентична shouldComponentUpdate() .

Если shouldUpdateChildContext() равно true (по умолчанию), мы обновляем каждый из наших ближайших «дочерних элементов контекста» (только Child в примере), если какие-либо отношения были установлены ранее. . Это поместит этот компонент в его жизненный цикл обновления (в отличие от обсуждений, которые я видел ранее, это будет _по-прежнему_ вызывать shouldComponentUpdate() как обычно, с _new_ контекстом в качестве третьего параметра, поскольку это обеспечивает компоненту детальный контроль, когда он хочет Обновить).

Надеюсь, это достаточно хорошо объясняет процесс!

@Chrisui Круто, приятно видеть некоторый прогресс в этом

  • Проверять дочерние обновления, даже если родительский shouldComponentUpdate() возвращает false.
  • Проверять дочерние обновления, даже если родительский shouldUpdateChildContext() возвращает false.
  • Убедитесь, что дочерний элемент не обновляется, если компонент shouldUpdateChildContext() возвращает false.

Кроме того, если это поддерживаемые функции, мы можем проверить:

  • Убедитесь, что shouldUpdateChildContext() не вызывается, если дедушка или бабушка изменяют предоставленный контекст, но прямой родитель переопределяет этот контекст и не изменяется.
  • Убедитесь, что shouldUpdateChildContext() не вызывается, если компонент не считывает переменную контекста, которая изменилась.
  • Убедитесь, что если свойство изменяется и переменная контекста изменяется, мы не обновляем / визуализируем дважды.

cc: @sebmarkbage за отзывы о дизайне / идее API.

@Chrisui Пока это звучит фантастически, спасибо за эту работу.

@jimfb Обязательно начну

Что касается второго набора случаев, это области, которых я избегал в пользу более простого дизайна. Например, было предложено следить за тем, какие конкретные контекстные ключи изменяются, и обновлять только их. Я считаю, что в настоящее время это может слишком усложнить отношения родитель / потомок и будет иметь меньшую симметрию с тем, как другие данные проходят через React. Например (я могу быть очень наивным и не обращать на это внимания) мы не сравниваем изменение свойств внутри и оставляем его на усмотрение пользователя для явного определения, должны ли два отдельных источника данных (т.е. текущий и следующий реквизиты) вызывать обновление или просто быть игнорируется.

Было бы неплохо увидеть еще какое-то обсуждение этой конкретной детали, если люди твердо убеждены, что она должна быть реализована.

Что касается простой реализации этого, если бы мы действительно хотели продолжить, я думаю, вы бы продолжали объединять childContextTypes компонентов по мере обновления дерева для сравнения с contextTypes чтобы найти действительные обновить цели. Или, может быть, мы можем каким-то образом сделать немного магии при монтировании и создать отношения родитель / потомок только между дочерним и ближайшим родителем с соответствующим дочерним ключом контекста. У меня такое чувство, что последнее затруднит управление порядком обновлений позже, хотя если и не сделает невозможным.

Я сделаю быстрый взлом и попробую поиздеваться над этим прямо сейчас!

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

@Chrisui @sebmarkbage Итак, меня беспокоит (если я не забываю / не пропускаю что-то очевидное) заключается в том, что каждый раз, когда провайдер контекста выполняет повторный рендеринг, все (потенциально сотни) дочерних компонентов, которые зависят от предоставленной переменной контекста повторить рендеринг, даже если контекстная переменная не изменилась. Моя интуиция заключается в том, что мы должны запускать повторное распространение контекста только в том случае, если у нас есть какой-то признак того, что доступно новое значение (например, новое значение, не являющееся тройным, равным старому, или какой-то флаг / уведомление).

Фактически эта реализация начинает выглядеть как система подписки. Поскольку context по сути является глобальным с ограниченной областью видимости, возможно, правильным решением будет потребовать от компонентов боковой подписки на переменные контекста, что позволит нам использовать тот же механизм для контекста, который мы собираемся использовать для всех других подписок на глобальные данные. См. Https://github.com/facebook/react/issues/3398 , https://github.com/facebook/react/issues/3858 и https://github.com/facebook/react/pull/3920 для релевантная информация.

Тыкаешь @sebmarkbage. Я думаю, что мои последние мысли в пользу использования нашего решения для боковой загрузки данных для решения этой проблемы, чтобы избежать области поверхности API. Мне любопытно услышать твои мысли.

@jimfb Вы правы насчет повторного рендеринга сотен компонентов, но я не уверен, что сейчас все будет иначе, если вы активируете какое-то изменение состояния и вам придется реализовать shouldComponentUpdate, чтобы предотвратить массовое обновление.

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

Из интереса я добавил функциональность в свой черновик, чтобы только создавать отношения родитель / потомок между компонентами, если у них есть соответствующий ключ контекста / дочернего контекста (предотвращает обход избыточного дерева), и предотвращение обновления, если контекст фактически не изменился, просто будет выполнено с помощью PureRenderMixin, как всегда.

@Chrisui Я думаю, разница в том, что один shouldComponentUpdate в дочернем компоненте может отрезать огромные ветви дерева, тогда как с контекстом гораздо сложнее найти / исправить все места, которые будут повторно отрисованы. Даже если вы выйдете из игры, дочерний элемент, который зависит от переменной контекста, все равно будет повторно отрисован.

Да, мне любопытно узнать, что другие думают об использовании решения для боковой загрузки данных.

Я думаю, что контекст в основном ортогонален боковой загрузке данных

В идеале мне нужен какой-то способ передать дескриптор моего бокового хранилища данных компонентам, которые не являются глобальными - в основном я использую контекст.

Тем не менее, использование контекста для DI снижает вероятность того, что он будет изменяться в процессе работы приложения.

26 мая 2015 года в 22:10 Джим [email protected] написал:

@Chrisui Я думаю, разница в том, что один shouldComponentUpdate в дочернем компоненте может отрезать огромные ветви дерева, тогда как с контекстом гораздо сложнее найти / исправить все места, которые будут повторно отрисованы. Даже если вы выйдете из игры, дочерний элемент, который зависит от переменной контекста, все равно будет повторно отрисован.

Да, мне любопытно узнать, что другие думают об использовании решения для боковой загрузки данных.

-
Ответьте на это письмо напрямую или просмотрите его на GitHub.

Похоже, что все текущие советы по использованию контекста (кроме «не делать») по-прежнему применимы, когда все, что часто изменяется, повлечет за собой довольно высокие затраты на повторный рендеринг. Предлагаемое целенаправленное увеличение этой стоимости не обязательно повлияет на то, как люди думают об этом. Кроме того, если вы хотите отрезать ветви дерева, это все еще довольно просто:

class Mid extends Component {
  shouldComponentUpdate() { return false; }
  shouldUpdateChildContext() { return false; }
  ...
}

Я доволен относительной простотой контекста в его нынешнем виде + shouldUpdateChildContext. Будем надеяться, что изменение базового механизма, чтобы он соответствовал другим глобальным подпискам, не слишком сильно снизит простоту его использования.

@jimfb
Как @eplawless указал shouldUpdateChildContext может использоваться , чтобы блокировать огромные ветви вашего дерева обновляется.

Я думаю, что api контекста (с предлагаемыми изменениями) достаточно прост, чтобы мы могли реализовать его сейчас и изменить внутреннее устройство для использования любой реакции на загрузку данных, которая может поддерживаться позже.

Другое предложение: мы также можем установить для shouldUpdateChildContext () значение по умолчанию false, чтобы предотвратить это новое поведение по умолчанию и сделать его включенным.

Ради итерации и получения чего-то для тестирования (точно так же, как всегда была контекстная функция - следовательно, не документирована), должны ли мы продолжать это предложение api для версии 0.14? (Было бы хорошо получить известие от привратников!)

Я открыл для вас пиар: https://github.com/facebook/react/pull/3973

Скорее всего, вы получите быструю обратную связь по PR, поскольку их легче проверять по сравнению с основным и получать приоритет над проблемами.

Вы знаете, я думаю, что изначально ошибался насчет shouldUpdateChildContext . Это действительно нужно? Извините за трэш.

Похоже, что shouldComponentUpdate - это стратегия, которая возлагает ответственность за определение того, изменилось ли что-либо, на ребенка. Например, он может не использовать все данные, которые вы предоставили, для определения его вывода. Вы как родитель этого не знаете. Возможно, этого достаточно.

Я также думаю, что мы могли бы ограничить соединение родитель-> потомок фиксированным набором свойств, например props. Т.е. вы не сможете иметь несколько разных контекстов, поставляемых из одного источника, где любой данный дочерний элемент использует только части контекста. Может быть отображение 1: 1. Кроме того, я всегда сомневался, чтобы пузырь контекста проходил прямо через дочерний элемент, который его потребляет, в то время как законный вариант использования обычно представляет собой «червоточину» между двумя компонентами, а не просто транслируется всем (что является анти-шаблоном).

Я подумаю еще немного и подробно прокомментирую PR ...

@sebmarkbage Я могу пойти любым путем на shouldUpdateChildContext . Я предпочитаю всегда минимизировать поверхность API. Мы всегда можем оставить его вне начального API (контекст просто перескакивает и инициирует повторную визуализацию для любого компонента, который читает переменную контекста). Если людям понадобится дополнительный аварийный люк, мы добавим его позже.

Я думаю, что более серьезная проблема, как указал

Я немного запутался в вашем последнем абзаце; можешь уточнить? Я предполагаю, что вы не предполагаете, что следующее является анти-шаблоном контекста: компонент i18n «транслирует» всем дочерним компонентам, поддерживающим i18n, предпочитаемый пользователем язык / часовой пояс / форматирование и т. Д.

Прокомментировал PR, я думаю, здесь следует воспроизвести:

@sebmarkbage @Chrisui @jimfb

Re: shouldUpdateChildContext , думаю полезное дополнение. По умолчанию shouldComponentUpdate возвращает значение true, поэтому ответственность за определение того, изменилось ли что-либо, возлагается на ребенка. Реализация shouldComponentUpdate означает, что вы снимаете с себя эту ответственность в том случае, если родитель знает лучше. То же самое и с shouldUpdateChildContext , оно существует только для того, чтобы снять ответственность со своих дочерних элементов в качестве оптимизации.

Re: не вещание на все поддерево, я думаю, есть два действительных шаблона. Червоточины, как вы предположили в № 2517, имеют смысл. Мы используем этот шаблон для группировки элементов в различных подсистемах (например, фокус и голос). Кроме того, мы хотели бы использовать контекст для передачи информации i18n целому разделу (возможно, целому) нашего приложения и принудительно выполнить повторный рендеринг всего, что его использует. Такой вид широковещательной передачи, вероятно, относительно редок по сравнению с шаблоном червоточины, но я считаю, что он все еще актуален.

Сейчас мы время от времени используем миксины для фокусных и голосовых групп из одного и того же источника. Я думаю, что есть допустимые варианты использования, чтобы не форсировать отображение 1: 1, хотя я понимаю желание ограничения.

Я действительно думал, что паттерн «червоточина» был антипаттерном, а широковещательная передача была ожидаемой. Контекст представляет интерес только в том случае, если есть много потребителей переменной (т.е. если явная передача свойства приведет к его добавлению практически к каждому компоненту и, следовательно, является большим количеством шаблонов), в противном случае, вероятно, лучше передать его явно как опора Но @sebmarkbage, похоже, сказал обратное (и он обычно прав насчет этого), так что теперь я смущен и хотел бы получить от него разъяснения.

@sebmarkbage @eplawless Какой пример шаблона червоточины, который вы считаете допустимым? Что предоставляет родитель, как он используется ребенком, почему он не может быть просто опорой для ребенка и т. Д.

<Table>
  <Cell />
  <Cell />
  <FancyCell />
</Table>
class FancyCell {
  render() {
    return <SomeWhatFancyCell>Some content</SomeWhatFancyCell>;
  }
}

class SomeWhatFancyCell {
  render() {
    return <Cell>{this.props.children}</Cell>;
  }
}

Возьмите borderWidth и передайте его всем ячейкам таблицы в виде borderLeft/Top/Right/Bottom .

Один из лучших способов - передать borderWidth в <Table /> , разделить его и передать контекст.

Он предоставляет способ для чего-то, что может отображаться в ячейке, для связи со своим концептуальным родителем, например таблицей, через скрытый канал. Это уже то, что элементы DOM делают друг с другом и, вероятно, требуется для создания макета в React.

Не зацикливайтесь на концепции «контекста». Текущая концепция не идеальна. Может быть, идеал - это что-то вроде двух разных концептов или какой-то другой канал?

Вероятный альтернативный API:

class Table {
  render() {
    var w = this.props.borderWidth;
    var borderStyle = { left: w, right: w, top: w, bottom: w };
    return <context key={someSymbol} value={borderStyle}>{this.props.children}</context>
  }
}
class Cell {
  static contextKey = someSymbol;
  render() {
    var borderStyle = this.context;
    ...
  }
}

Просто плевать здесь.

Продолжая плеваться ...

Можно использовать альтернативный синтаксис ...

<Table borderStyleChannelKey="myKey">
  <Cell borderStyle={myKey} />
  <Cell borderStyle={myKey} />
  <FancyCell borderStyle={myKey} />
</Table>

Теперь вы сделали канал связи явным, исключили любую возможность конфликтов имен и т. Д. (Https://github.com/reactjs/react-future/pull/28)

У символа тоже нет конфликтов имен, но вы сделали канал связи дорогой ценой. Вы прошли через несколько уровней косвенного обращения. Каждый раз, когда вам нужно изменить или добавить другой канал, например цвет фона, вам нужно обновить все, даже если документально оформленный контракт (ячейка в таблице) остается прежним.

Кстати, суть React - это уже скрытый канал связи: состояние. Он не проходит явно через каждого ребенка.

Назовите его styleinfo или celldata вместо borderStyle и сделайте его объектом. Тогда добавление полей / информации не требует изменения контракта API между Table и Cell .

Единственное различие между моим вариантом и вашим вариантом заключается в том, что (в моем варианте) единственный способ для дочернего элемента фактически «прочитать» значение - это передать его явно как опору от родителя. Функционально ваше последнее предложение буквально идентично моему предложению из https://github.com/reactjs/react-future/pull/28.

Чтобы было ясно, мне очень нравится ваше последнее предложение ... так что я не собираюсь жаловаться ... но оно решает несколько другую проблему. Это то, что вы сказали мне еще в марте, и я в конечном итоге пришел к выводу, что вы были правы (например, проблема с трансляцией). Если мы согласны с тем, что не решаем проблему трансляции (именно так я был убежден, что контекст действительно имеет некоторую ценность), тогда, во что бы то ни стало, давайте сделаем что-нибудь вроде этого!

Произвольный символ действительно имеет конфликты имен, если только имя ключа не всегда определяется владельцем, и в этом случае вы также можете сделать его видимым для владельца, потому что владелец в любом случае должен сообщить имя ключа дочернему элементу (в противном случае, как бы ребенок знает где смотреть?). Принуждая к участию владельца, вы, естественно, поощряете компоненты использовать символы вместо жестко закодированных ключей.

Я имею в виду глобальный символ (заглавная буква S), который должен быть организован через какой-то канал, например общий модуль. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol

Приятная часть моего нового предлагаемого API состоит в том, что ключ не является ключом «свойства объекта», который путают с потоком управления и анализом программы. В моем предложении - это динамическое значение первого класса, точно так же, как ключ, управляющий состоянием.

Ох, ic, @sebmarkbage снова вытаскивает новый модный javascript :). Я должен был знать; он сегодня на собрании TC39.

Хорошо, ты прав. Это было бы очень эффективно для предотвращения конфликтов имен и решило бы проблему трансляции. Ты меня продал. Мне это нравится!

Желаю, чтобы вы сегодня были в офисе! Я чувствую, что это был бы фантастически увлекательный разговор лично!

Лично я рад, что это не так, потому что это тоже держит нас всех в курсе ;-).

Мне очень нравится Symbol. Чрезмерная зависимость контекста от строковых ключей потенциально инопланетного дерева всегда имела неприятные ощущения.

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

Честно говоря, символы уже можно работать с текущими объектами. Даже если вы используете символы, вам, вероятно, понадобится модуль через потенциально глобальное пространство имен, такое как имя модуля верхнего уровня npm (хотя, надеюсь, оно относительное).

Просто текущий API делает очень естественным использование простого строкового имени. Он также объединяет семантику времени выполнения с определенными идентификаторами свойств, что затрудняет рассмотрение таких вещей, как расширенный режим компилятора закрытия. Также винты с оптимизацией ВМ.

@sebmarkbage. Вы можете использовать UUID (также известные как символы), да, но ваше новое предложение позволяет определять ключ во время выполнения, что означает, что родитель МОЖЕТ повлиять на него через реквизиты (хотя не уверен, что вы об этом думаете). Сказав это, я не уверен, насколько это полезно без предоставления значений в рамках jsx, мне нужно подумать об этом больше, но это добавляет некоторую классную гибкость (возможно).

Я не совсем уверен в аналогии с «червоточиной», сценарий трансляции и пример i18n - это те, где я столкнулся с проблемой, которую предлагает предлагаемый PR.

Я также не совсем понимаю, как помогает https://github.com/facebook/react/issues/2517#issuecomment -106597895 - мне это кажется семантически эквивалентным getChildContext и contextTypes.

Что мне нравится в shouldUpdateChildContext PR, так это то, что он не требует перечисления всех дочерних элементов на любой глубине для распространения изменений контекста.

AIUI Первоначальная проблема всегда заключалась в том, что, хотя shouldComponentUpdate передается context , природа контекста означает, что компоненты между «вещателем» и «получателем» могут возвращать false, потому что он не знает контекста. проходил мимо.

Кажется, что есть два способа обойти эту проблему: отслеживать, какие компоненты читают из контекста над ними в дереве, или иметь дополнительный механизм для распространения изменений контекста вниз по дереву.

Я хотел бы предложить свои 2 цента на эту тему после небольшого использования родительского контекста в наших компонентах формы . Отсутствие хорошего механизма обновления между родителем и дочерними элементами в зависимости от контекста привело к созданию параллельного, потокового механизма, в котором форма (функционирующая как экземпляр «хранилища») передает метод listen() вниз через контекст, на который Field компоненты регистрируются при монтировании. Форма вызовет изменение, которое распространяется вниз. Это все очень плавно и работает нормально, но разрушает модель компонуемости компонентов React.

Общая стратегия обертывания компонента в HoC здесь не работает, поскольку он помещает новый, оборачивающий компонент _ вне_ канала обновления, а не в его середину (как в случае с props). Большим преимуществом системы опор является то, что поток течет сверху вниз и может быть остановлен чем угодно, застрявшим между ними. В случае использования потока, подобного системе обновления, компонент-оболочка также должен будет прослушивать форму, но тогда вы столкнетесь с проблемой, когда и компонент-оболочка, и исходный (оболочка) компонент прослушивают форму, вместо этого HoC «берет на себя» и передает данные исходному компоненту.

Очевидно, что контекст должен быть в некоторой степени «защищен» от компонентов, которые не имеют отношения к конкретному контексту _ но_ он должен быть простым для перехода в поток данных, если компонент хочет. В конкретном случае наших компонентов формы большинство дочерних элементов Form должны быть изолированы от контекста, который он передает, и только компонент Field должен получать его. Однако, если потребитель хочет обернуть Field в HoC (чтобы установить значения по умолчанию или настроить поведение), должен быть какой-то простой способ сделать это.

это все, что говорит о том, что любой контекстный API действительно должен позволять промежуточным (авторизованным) компонентам действовать как редукторы для указанного контекста, точно так же, как компоненты могут уменьшать / отображать реквизиты, когда они передают их потомкам. разница в том, что в то время как реквизит - это "общедоступный" пар, контекст - это частный поток с возможностью выбора

Я немного поиграл с идеей контекста на основе подписки в контексте реакции .

Под капотом контекстные вещатели предоставляют своим потомкам интерфейс { subscribe(key, cb), getValue(key) } . Подписка на ключ контекста всплывает вверх по дереву контекста до тех пор, пока не достигнет либо широковещательной передачи этого конкретного ключа, либо вершины дерева. Радиовещательные организации отправляют изменения в контекстные ключи через broadcast({ [key]: newValue, ...otherKeys }) .

Декораторы классов @broadcasts([...keys]) и @observes([...keys]) связывают этот API с компонентами React, используя context для распространения дерева контекста через дерево компонентов и setState для постановки обновления в очередь. реакция на изменение контекста.

Приложив немного шаблонов , вы можете настроить API, эквивалентный текущему предложению shouldUpdateChildContext .

Чтобы избежать конфликтов имен, деревья контекста уникальны , поэтому вам понадобится ссылка на контекст, чтобы транслировать или наблюдать ключи в нем.

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

Например, для I18n кто-нибудь передает пары ключ-значение напрямую? Или вы просто передаете неизменную ссылку на объект, из которого компоненты могут получать значения и наблюдать?

Одно время я был заинтересован в реализации своего рода диспетчеризации всплывающих событий в контексте, я забыл, для чего именно, но идея заключалась в том, чтобы позволить родителям перехватывать и добавлять больше контекста к событиям, исходящим от детей, чтобы у детей не было иметь какие-либо знания о том, где они вписываются в глобальный контекст.

Чтобы добавить сюда свои 2 цента, я бы настоятельно предпочел, чтобы какое-либо исправление здесь было реализовано таким образом, чтобы не нарушать StaticContainer .

Я думаю, что было бы разумнее всего, чтобы StaticContainer продолжал работать так же, как и раньше, и блокировал все обновления, исходящие от родителей, как props и context .

ИМО, лучший способ справиться с этим - позволить shouldComponentUpdate потенциально обнаруживать изменения в контексте, используемом детьми, а не полностью игнорировать shouldComponentUpdate всякий раз, когда задействован контекст.

@ jedwards1211 Моим вариантом использования было бы получение сообщений перевода внутри компонента I18nProvider, а затем передача экземпляра Jed (с помощью методов gettext).

До того, как сообщения будут получены, методы gettext будут возвращать пустые строки, чтобы пользовательский интерфейс отображался оптимистично. Когда I18nProvider получил сообщения, я ожидал, что все будет повторно отрисовано, а в DOM будут обновляться только части с переводимыми строками.

Мне действительно нужно иметь вызовы gettext() в компонентах представления, чтобы я мог извлекать строки (в данном случае используя xgettext).

Немного поиграйте с @jquense на Reactiflux; было бы полезно, если бы для shouldComponentUpdate существовала концепция "super false ".

Хотя в целом кажется, что обновления контекста должны распространяться после shouldComponentUpdate возвращающего false (или, по крайней мере, это упростило бы большинство случаев использования), я также думаю, что есть особые случаи, например, <StaticContainer> которые требуют, чтобы родитель имел возможность блокировать все обновления для своих потомков, например, для замораживания поддерева, выходящего из строя, как в случае с <StaticContainer> .

Например, рассмотрим https://github.com/rackt/react-router/pull/2454 - без возможности блокировать обновления контекста для детей, мы не смогли бы легко предотвратить обновления, связывающие активное состояние при переходе - компонент маршрута.

Я согласен с тем, что поведение shouldComponentUpdate возвращающее false не должно блокировать обновления контекста, но должен быть какой-то SUPER_FALSE дозорный, чтобы держать <StaticContainer> работает как есть.

@taion

Было бы нелегко предотвратить обновления для связывания активного состояния в компоненте маршрута перехода.

Я запутался, каков желаемый вариант использования? В общем, shouldComponentUpdate НЕ следует использовать для блокировки обновлений, которые могут привести к изменениям пользовательского интерфейса. Это подсказка, предназначенная исключительно как механизм оптимизации, позволяющий избежать ненужной работы по согласованию.

@jimfb Вы уже используете <StaticContainer> чтобы делать именно это в Relay, а именно, чтобы блокировать обновления при загрузке новых данных.

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

Похоже, что в настоящее время это достигается путем постоянного возврата false в shouldComponentUpdate во время загрузки нового состояния маршрутизатора.

@taion Я поговорю с

Копия @sebmarkbage

@jimfb

Наверное, тебе проще это сделать (:

Насколько я понимаю, идея заключается в том, что при запуске загрузки новых данных по умолчанию мы хотим продолжать рендеринг старых данных до тех пор, пока не будут готовы новые данные.

Возвращая false из shouldComponentUpdate , корневой контейнер / средство визуализации Relay имеет простой способ просто сохранить отрисовку всего, что было раньше.

Он не блокирует взаимодействия как таковые - он блокирует получение обновлений данных из восходящего потока в переходном состоянии.

@taion Да, я понимаю, что он делает, но я скептически отношусь к тому, что мы хотим поддерживать этот шаблон. Я поговорю с Себастьяном и ретрансляторами, но предполагаю, что ответ будет «да, это был взлом, не делай этого».

Обычно такое поведение достигается путем помещения старых значений в состояние (до тех пор, пока не появятся / не появятся новые значения) и визуализации старых значений из состояния.

@jimfb

Этот шаблон возможен только с неизменяемыми данными или чем-то подобным. Если у вас есть (неклонируемые) объекты с сохранением состояния, возможно, вы не сможете следовать этому шаблону. Я подозреваю, что у Relay может не быть этой проблемы, но в текущей реализации React Router эта проблема есть.

Для общих библиотек асинхронных данных это кажется достаточно общим и удобным шаблоном.

@taion Подход, который вы рассматриваете, кажется мне еще более подверженным ошибкам, ОСОБЕННО в случае, когда у вас есть изменяемые данные. Если ваши данные изменчивы, тогда у вас будут условия гонки, при которых дочерние элементы иногда обновляются случайным образом (из-за обработчика событий, изменения контекста, принудительного обновления и т. Д.), И ваш код становится очень непредсказуемым. Позвольте мне синхронизироваться с некоторыми людьми и разобраться в этом. Напишите мне через пару дней, если я не опубликовал результаты этого обсуждения.

Я не думаю, что вариант использования StaticContainer - это то, что нужно решать с помощью этого шаблона. Это звучит как нестабильный способ попытаться получить эффект от побочных эффектов другой функции. Было бы гораздо лучше иметь явную поддержку того, что на самом деле запрашивается.

В этом случае кажется, что запрос будет заключаться в том, что некоторые низкоуровневые библиотеки React хотят перехватить размонтирование, отделить DOM, полученный от компонента из дерева React, отключить его соединение и отложить жизненный цикл размонтирования до его завершения.

Я думаю, что это низкоуровневая функция, которую мы должны поддерживать с помощью явного интерфейса. Какой-то способ размонтирования или перехвата размонтирования компонента, который разрывает ссылку, позволяет вызывающему на некоторое время возиться с базовым контекстом и возвращает функцию, которая возобновит размонтирование потока живого цикла при вызове.

Нам может потребоваться какая-то особая форма фрагмента или заполнителя, который этот API может использовать, чтобы явно сообщить React, что узел DOM, отключенный от React, должен храниться в определенном месте.

Этого не может быть без особого внутреннего узла. Но опять же, идея особого низкоуровневого типа узла реакции, который отображается как noscript (типичный способ реагирования на возврат null и т. Д.) И объявляет, что его nextSibling является узлом DOM, который должен храниться в этом месте. но в противном случае React игнорировал бы действительно интересный тип узла, который мог бы быть полезен для исправления множества других ошибок.

@taion Хорошо, я только что поговорил с одним человеком из команды Relay и парой других людей из React. Мы все согласны с тем, что это плохой образец. Пожалуйста, не делай этого. Использование состояния для хранения ваших данных во время ожидания обновления является официальным решением, пока мы не предложим лучший api / рекомендацию для этого варианта использования.

Я могу с этим справиться - я думаю, что мы очистили несколько вещей на стороне React Router, чтобы мы в любом случае могли отказаться от использования этого шаблона.

Благодаря!

Еще хочу добавить еще один вопрос. У меня что-то вроде этого.

var BlogPosts = React.createClass({
  getChildContext: function() {
    return {
      currentBlogPost: this.props.currentBlogPost,
      currentUser: this.props.currentUser
    };
  },

  childContextTypes: {
    currentBlogPost: React.PropTypes.object,
    currentUser: React.PropTypes.object
  },

  render: function() {
    return <BlogPosts blogPosts={this.props.blogPosts}/>
  }
});

function select(state) {
  const { blogPosts, currentUser, currentBlogId } = state;
  console.log( state.blogs[currentBlogId]); 
  // first time the above is undefined and then blogs get populated and I have the object;
  return { blogPosts, currentUser, currentBlogPost: state.blogs[currentBlogId] };
};

export default connect(select)(BlogPosts);

теперь в компоненте BlogPosts есть BlogPostText, BlogPostImage, PodCast ... в зависимости от того, является ли blogPosts [index] .type текстом, изображением или аудио.

в одном из компонентов, которые я проверил для владельца вот так,

var BlogPostText = React.createClass({
  canDeleteMemory: function(post, blog, user) {
    return user && (blog.userId == user.id || post.userId == user.id)
  },
  render: function() {
    let isOwner = this.canDeleteMemory(this.context.currentBlogPost, post, this.context.currentUser);
    return isOwner ? <a>Delete</a> : null;
  }
});

то я всегда получаю сообщение об ошибке на blog.userId, потому что блог не определен ... поэтому я меняю условие следующим образом: let isOwner = this.context.currentBlogPost && this.canDeleteMemory(this.context.currentBlogPost, post, this.context.currentUser);
но тогда значок удаления никогда не отображается ... но вместо использования contextType, если я оборачиваю компонент BlogPostText с помощью redux select и использую this.props.currentBlogPost, тогда он работает нормально ..

поэтому изменение контекста не вызывает повторного рендеринга или чего-то в этом роде ... или я использую его неправильно.

@ aghosh47 Я думаю, что изменение контекста вызывает повторную визуализацию, но, возможно, мы пропустили крайний случай. Если бы вы могли создать простой jsfiddle, демонстрирующий проблему, это помогло бы нам разобраться.

Кроме того, опубликуйте его в новом выпуске, так как мы хотим, чтобы вопросы github оставались в теме, насколько это возможно.

@jimfb хорошо, я перепроверяю свой код, посмотрю, не пропустил ли я что-то и создам новую тему, если не смогу решить ... Я только начал с реакции ... и на самом деле трудно найти качественную помощь. Итак, спасибо за отзыв .. ценю.

@jimfb где обсуждается перемонтирование потомков? Я бы никогда не ожидал такого, поэтому меня беспокоит, что неожиданные поведенческие изменения могут привести к тому, что мое приложение будет работать с будущими версиями React, если они появятся. Поэтому я хочу быть в курсе этого обсуждения.

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

@ jedwards1211 Если честно, не помню, где это обсуждалось. Вполне возможно, что это была личная дискуссия, но в Интернете много обсуждений по поводу репартинга, и, возможно, вы найдете это там.

Пинг @ aghosh47

Хахаха

@jimfb ну, проблема была решена ... в коде была некоторая ошибка, и люди, написавшие код, не использовали catch для устранения каких-либо ошибок из обещаний, поэтому состояние редуктора не обновлялось должным образом, и, следовательно, изменение не было Отражаюсь.

Итак, если свойства в родительском элементе изменяются, contextType в дочерних элементах действительно отражается ... Спасибо.

что действительно не так в использовании окна для одного объекта, хранящего эти глобальные объекты?

@cauburtin На самом деле это не работает на стороне сервера, где все созданные компоненты имеют одно и то же глобальное состояние. Контекст является глобальным по отношению ко всем компонентам, отображаемым в одном дереве, но локальным по отношению к самому дереву. Он не попадает в другие деревья, в отличие от глобального состояния.

Кстати, этот вопрос не имеет ничего общего с проблемой, не так ли? : wink:

@fatfisz Я плохо знаю ваши варианты использования, у вас также может быть require ('foo'), который указывает на свойство окна только на клиенте

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

благодаря

что не так:

// context.js
module.exports  = { // sorry for using commonjs, since most of you use import/export I guess
  // some shared variables, initialized and used by components
};

затем require ('./ context.js'); во всех файлах, которым необходим доступ к контекстным переменным и методам

инкапсуляция не соблюдается может быть, но хорошо ..

(@gaeron)

@cauburtin Context полезен, потому что:

1) Он может изменяться и запускать повторный рендеринг в отличие от экспорта модулей
2) Он может быть переопределен родителем, что является его наиболее полезной функцией.
3) Это не обязательно должен быть синглтон, что полезно для серверного рендеринга, когда вы хотите изолировать данные.

Согласовано по 2) и 3)
Для 1) мне еще не нужно было этого делать, но вы также можете передать экземпляр реакции в контексте (я часто слышал, что это плохая практика, вы, вероятно, тоже так думаете, но это даже упоминается в документации: вспомните, что вы также можете передавать целые компоненты React в реквизитах, если хотите (где реквизитами может быть этот синглтон-контекст в данном случае). Для меня React - это представление, слушатели событий и (верхний / корневой) удаленный контроль. этот общий контекст будет общим доступом к пульту дистанционного управления. Подкомпоненты могут обновляться при изменении их свойств и, возможно, получать доступ к этому глобальному контексту и использовать его. (В данном случае я не фанат синглтонов, я мог бы повторно использовать 'передачу вниз реагировать на экземпляр в способе props, если это полезно, эта `` передача вниз '' может быть более или менее автоматизирована / скрыта общим родительским классом или композицией)

хорошо, пишу это, я понимаю, что идея контекста singleton плохая :)

Что сбивает с толку, так это то, что документы React не привлекают много внимания к контексту, на самом деле они похожи на необязательные реквизиты, где вы их получаете, когда их просите?

Я реализую Drawer в android, используя react native и пытаюсь создать другой файл для кода меню ящика и кода содержимого ящика. Для этого я создал компонент React в разных файлах. Я могу выполнять всю работу с этим файлом, но мне нужна ссылка на ящик для выполнения некоторых операций с ящиком в файлах компонентов. Вот мой код, как я могу передать ссылку на ящик в другой файл компонента, чтобы использовать методы ящика, такие как openDrawer ().

'use strict';

    var React = require('react-native');
    var { View,
          StyleSheet,
          TouchableHighlight,
          } = React;

    var DrawerLayout = require('react-native-drawer-layout');
    var DrawerScreen = require('./DrawerScreen');
    var DrawerMenu = require('./DrawerMenu');

    var DrawerLayoutExample = React.createClass({

      render: function() {
        var navigationView = (
          <View >
               <DrawerMenu/>
          </View>
        );

        return (
          <DrawerLayout
            onDrawerSlide={(e) => this.setState({drawerSlideOutput: JSON.stringify(e.nativeEvent)})}
            onDrawerStateChanged={(e) => this.setState({drawerStateChangedOutput: JSON.stringify(e)})}
            drawerWidth={200}
            ref={(drawer) => { return this.drawer = drawer  }}
            keyboardDismissMode="on-drag"
            renderNavigationView={() => navigationView}>
            <View style={styles.container}>
 // Here is content component for drawer, need to refer drawer reference
            <DrawerScreen ></DrawerScreen>
            </View>
          </DrawerLayout>
        );
      }
    });

    var styles = StyleSheet.create({
      container: {
        alignItems: 'center',
        justifyContent: 'center',
        flex: 1,
        flexDirection: 'column',
      },
     });

    module.exports = DrawerLayoutExample;

DrawerScreen.js

'use strict';
var React = require('react-native');
var {
  AppRegistry,
  StyleSheet,
  Text,
  View,
  Dimensions,
  Image,
  TouchableHighlight,
  TextInput,
} = React;

var deviceWidth = Dimensions.get('window').width;

var DrawerScreen = React.createClass({


  render: function() {
    return (

        <View style={styles.container}>

          <Text style={styles.welcome}>Content!</Text>

          <TouchableHighlight onPress={() => this.state.openDrawer()}>
            <Text>Open drawer</Text>
          </TouchableHighlight>
          <TextInput style={styles.inputField} />
        </View>
    );
  },

});

var styles = StyleSheet.create({
   container: {
      alignItems: 'center',
      justifyContent: 'center',
      flex: 1,
          flexDirection: 'column',
    },
    inputField: {
      backgroundColor: '#F2F2F2',
      height: 40,
    },
});

Прямо сейчас я использую redux для подписки и хранения медиазапросов, поэтому компоненты могут решить выполнять рендеринг по-другому, например, на телефоне.

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

Я думаю, что в этом случае контекст - гораздо лучшее место для его хранения, но эта проблема не позволяет мне его использовать.

@wmertens, это то, что нужно сделать на componentDidMount, верно?

@cauburtin совсем нет, в любой момент пользователь может выбрать другой язык, и в любой момент пользователь может изменить размер браузера ...

для изменения размера вы можете слушать в componentDidMount, для языков я бы все перерисовал

Я предпочитаю не повторять всю логику изменения размера, когда у меня есть что-то крутое, например
redux actioncreator, который прослушивает весь апо и работает на стороне сервера
тоже…
У меня есть state.responsive.isPhone / isPrerender / screenwidth и т. Д. На сервере
Я отправляю на основе пользовательского агента. Аккуратно.

Пт, 29 апреля 2016 г., 16:52 Cyril Auburtin [email protected]
написал:

для изменения размера вы можете слушать в componentDidMount, для языков я бы
перерисовать все

-
Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/facebook/react/issues/2517#issuecomment -215742327

Wout.
(набрано на мобильном телефоне, извините за лаконичность)

Но да, простого ключа = {lang} наверху дерева должно хватить для
языков. Но не очень хорошо для сайтов с анимацией.

Пт, 29 апреля 2016 г., 18:07 Wout Mertens wout. [email protected] написал:

Я предпочитаю не повторять всю логику изменения размера, когда у меня есть что-то крутое, например
redux actioncreator, который прослушивает весь апо и работает на стороне сервера
тоже…
У меня есть state.responsive.isPhone / isPrerender / screenwidth и т. Д.
сервер, я отправляю на основе пользовательского агента. Аккуратно.

Пт, 29 апреля 2016 г., 16:52 Cyril Auburtin [email protected]
написал:

для изменения размера вы можете слушать в componentDidMount, для языков я бы
перерисовать все

-
Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/facebook/react/issues/2517#issuecomment -215742327

Wout.
(набрано на мобильном телефоне, извините за лаконичность)

Wout.
(набрано на мобильном телефоне, извините за лаконичность)

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

Ноно, я не сомневаюсь, что они дешевые, просто намного проще
написать this.context.isPhone, чем управлять обработчиками изменения размера.

Пт, 29 апреля 2016 г., 18:38 Сирил Обуртин [email protected]
написал:

Я был похож на вас, но слушатели событий изменения размера на самом деле дешевы, я не
Думаю, плохо иметь много однотипных слушателей

-
Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/facebook/react/issues/2517#issuecomment -215797819

Wout.
(набрано на мобильном телефоне, извините за лаконичность)

Проблема в том, что response-redux возвращает === ReactElement, чтобы сигнализировать об отсутствии обновления от метода рендеринга. Но на самом деле этот ярлык используется только в том случае, если контексты также ===, что не может управляться из компонента. Однако я не совсем уверен, почему это делается именно так. Возможно вопрос к ребятам из react-redux.

Для работы с Redux см. Https://github.com/reactjs/react-router/issues/470 и продолжение обсуждения в PR.

В какой-то момент мы действительно разрешим https://github.com/reactjs/react-router/issues/3484, чтобы упростить использование другими библиотеками, но это сложнее, чем мы думали изначально.

Обратите внимание на решение, которое в настоящее время используется реактивным маршрутизатором. Это работает, но разочаровывает. Это требует, чтобы мы изменяли контекст на месте или требовали, чтобы пользователи поддерживали ссылочную идентичность между своими объектами контекста.

Ничего хорошего в этом нет, но настоящая проблема в том, что червоточинный контекст вокруг иерархии компонентов нарушает один из самых приятных моментов в компонентной модели React для потока данных, заключающийся в том, что что-либо в середине (с полномочиями) может корректировать контекст по мере его прохождения . При передаче контекста через «внеполосные» компоненты необходимо использовать другой механизм для сопоставления контекста дочерним элементам, или просто не делать этого. По общему признанию, это не общий вариант использования, но он так или иначе описывает все использование контекста.

Между двумя подходами это, по крайней мере, _в некотором_ общем плане ... думаю, лучшее, что мы можем сделать сейчас 😛

да inded: P, просто отмечая, что это все еще неоптимально и не очень хорошая замена для исправления того, как контекст работает в целом

@jquense Не мог компонент посередине просто объявить contextTypes для прослушивания объекта контекста сверху и childContextTypes / getChildContext() чтобы предоставить измененную копию этого контекста для его потомки?

Компоненты @DarylCantrell должны принимать участие в каждой запрашиваемой части контекста. Невозможно объявить contextTypes для передачи всего контекста компоненту.

В любом случае я хочу сказать, что корректировка битов контекста _ возможна и является отличной функцией контекста, которая нарушается обходными путями к этой проблеме

@ 1000hz Конечно, но он говорил о «корректировке частей контекста по мере его прохождения». В этом сценарии вам не нужно получать «весь» контекст, только ту часть, которую вы планируете переопределить.

@DarylCantrell Упс, я неправильно понял ваши намерения.

Я отправил запрос на перенос по этой проблеме: # 7213

(из запроса на вытягивание)
Я использую контекст для распространения информации о локали и маршрутизации. Моя проблема в том, что некоторые чистые компоненты прекращают рендеринг поддерева при изменении контекста (потому что чистые компоненты проверяют только состояние и свойства). Это проблема многих людей согласно выпуску №2517.

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

Поскольку компоненты, которые не заинтересованы в контексте, обычно не используют аргумент контекста, отсутствие маскировки контекста не приведет к поломке любого существующего компонента, если только указанные компоненты явно не проверяют, что параметр контекста не определен. Однако это создаст возможность для усовершенствованного чистого компонента, скажем, ContextAwarePureComponent, который будет сравнивать состояние, свойства и контекст в shouldComponentUpdate, позволяя обновлению контекста вызывать повторную визуализацию чистых компонентов.

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

Привет @bvella , я думаю, вы имели в виду этот запрос на

@DarylCantrell , ты прав, спасибо

Я думаю, что https://github.com/facebook/react/pull/7213 / https://github.com/facebook/react/pull/7225 , хороший и должен быть адаптирован

Насколько я понимаю, контекст похож на глобальные переменные и, следовательно, его нельзя использовать;
однако это не означает, что пользователь должен сделать объявление, прежде чем он сможет получить доступ к глобальным переменным;
Глобальные переменные всегда должны быть доступны независимо от того, объявляете вы или нет;

объявление contextType должно работать только для целей проверки, как и propTypes

опять же, подумайте об этом, так как это доставит нам много проблем
(Я использую React Relay, и он скрывает все объекты контекста)

Я прокомментировал https://github.com/facebook/react/pull/7225#issuecomment -276618328, утверждая, что контекст должен просто обновлять все, а sCU служит только для того, чтобы решить, нужно ли этому компоненту повторно отрисовывать ; его дочерние элементы будут проверены независимо.

Выполнение обновлений контекста таким способом является дорогостоящим, но менее затратным, чем принудительный повторный рендеринг всего дерева, который в настоящее время является единственным способом получить обновления контекста для всех компонентов. Контекст не должен сильно меняться; есть лучшие варианты для быстро меняющихся обновлений (например, Redux).

Есть какое-нибудь решение прямо сейчас?

Как мы знаем из этой беседы, существующее решение в значительной степени сломано.
Мы не можем исправить текущий API, не сделав все приложения медленнее (чего мы бы хотели избежать :-).

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

Посмотрите обсуждение: https://github.com/reactjs/rfcs/pull/2

@acdlite только что получил PR с новым контекстным API: https://github.com/facebook/react/pull/11818.

Думаю, мы можем считать это закрытым. В новом API этой проблемы нет. Старый API нельзя исправить, и мы откажемся от него в какой-то момент после обновления основных библиотек до нового API.

Новый API будет доступен и задокументирован в одном из следующих второстепенных выпусков React 16.x.

@gaearon Не уверен, что вы слышите это достаточно часто, но: вы, ребята, отлично справляетесь, и я ценю вашу работу. 🍺

Была ли эта страница полезной?
0 / 5 - 0 рейтинги