Как обнаружить дочерние рендеры в родительском компоненте в react.js

Я пытаюсь кэшировать обработанную разметку компонента App. Я знаю, что это как-то «против правил», но я нахожусь в безсерверной среде (расширение Chrome). При загрузке страницы я хочу внедрить кэшированную разметку App в DOM. Ожидаемый результат аналогичен опыту с рендерером реактивного компонента на сервере. Примерно так, как описано здесь: http://www.tabforacause.org/blog/2015/01/29/using-reactjs-and-application-cache-fast-synced-app/.

Чтобы проиллюстрировать мой вариант использования, я обновил пример Thinking in react:

  • App
    • FilterableProductTable
      • SearchBar
      • ProductTable (containing from reflux store in state)
        • ProductCategoryRow
        • ProductRow

Как и ожидалось, ни componentDidUpdate, ни componentWillUpdate не вызываются в App.

Можно ли разумным образом обнаружить обновленные дочерние компоненты в компоненте App? Предпочтительно без изменения классов дочерних компонентов?

Я хотел бы избежать перемещения реквизита/состояния в App.


person derflocki    schedule 13.03.2015    source источник
comment
Какую проблему вы пытаетесь решить, когда компонент App должен знать об изменении дочерних компонентов?   -  person WiredPrairie    schedule 14.03.2015
comment
@WiredPrairie Я обновил вопрос, чтобы включить более широкую картину.   -  person derflocki    schedule 14.03.2015


Ответы (6)


Я придумал решение, которое работает как встраиваемое решение (без изменения дочерних компонентов или знания всего состояния приложения, например: шаблон потока):

App можно обернуть компонентом, который использует MutationObserver для отслеживания фактических изменений в DOM.

person derflocki    schedule 23.05.2016

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

person Adam Stone    schedule 13.03.2015

У меня была ситуация, когда я хотел сделать this.setProps(…) в юнит-тестах (когда компонент рендерился без родителя). Но это вызывает ошибку, если это делается, когда есть родитель.

Мой обходной путь состоял в том, чтобы просто установить свойство, подобное <MyComponent renderingWithoutParentForTest={true} />, в модульном тесте и использовать это свойство для условия.

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

person Henrik N    schedule 10.09.2015

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

Общение ребенка с родителем: https://facebook.github.io/react/tips/communicate-between-components.html

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

var GlobalEventSystem = {

  events: {},

  subscribe: function(action, fn) {
    events[action] = fn;
  },

  trigger: function(action, args) {
    events[action].call(null, args);
  }
};

var ParentComponent = React.createClass({

  componentDidMount: function() {
    GlobalEventSystem.subscribe("childAction", functionToBeCalledWhenChildTriggers);
  },

  functionToBeCalledWhenChildTriggers: function() {
    // Do things
  }
)};

var DeeplyNestedChildComponent = React.createClass({

   actionThatHappensThatShouldTrigger: function() {
     GlobalEventSystem.trigger("childAction");
   }
});

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

person garrettmaring    schedule 04.02.2016
comment
Вам потребуется изменить все возможные дочерние компоненты, чего я хотел бы избежать. - person derflocki; 23.05.2016

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

Думайте так, как рекомендует Flux. компонент -> действие -> диспетчер -> магазин

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

диспетчер.js:

var Promise = require('es6-promise').Promise;
var assign = require('object-assign');

var _callbacks = [];
var _promises = [];

var Dispatcher = function () {
};

Dispatcher.prototype = assign({}, Dispatcher.prototype, {

    /**
     * Register a Store's callback so that it may be invoked by an action.
     * @param {function} callback The callback to be registered.
     * @return {number} The index of the callback within the _callbacks array.
     */

    register: function (callback) {
        _callbacks.push(callback);
        return _callbacks.length - 1;
    },

    /**
     * dispatch
     * @param  {object} payload The data from the action.
     */

    dispatch: function (payload) {
        var resolves = [];
        var rejects = [];
        _promises = _callbacks.map(function (_, i) {
            return new Promise(function (resolve, reject) {
                resolves[i] = resolve;
                rejects[i] = reject;
            });
        });

        _callbacks.forEach(function (callback, i) {
            Promise.resolve(callback(payload)).then(function () {
                resolves[i](payload);
            }, function () {
                rejects[i](new Error('#2gf243 Dispatcher callback unsuccessful'));
            });
        });
        _promises = [];
    }
});

module.exports = Dispatcher;

некоторый образец магазина:

const AppDispatcher = require('./../dispatchers/AppDispatcher.js');
const EventEmitter = require('events').EventEmitter;
const AgentsConstants = require('./../constants/AgentsConstants.js');
const assign = require('object-assign');

const EVENT_SHOW_ADD_AGENT_FORM = 'EVENT_SHOW_ADD_AGENT_FORM';
const EVENT_SHOW_EDIT_AGENT_FORM = 'EVENT_SHOW_EDIT_AGENT_FORM';

const AgentsStore = assign({}, EventEmitter.prototype, {

    emitShowAgentsAddForm: function (data) {
        this.emit(EVENT_SHOW_ADD_AGENT_FORM, data);
    },
    addShowAgentsAddListener: function (cb) {
        this.on(EVENT_SHOW_ADD_AGENT_FORM, cb);
    },
    removeShowAgentsAddListener: function (cb) {
        this.removeListener(EVENT_SHOW_ADD_AGENT_FORM, cb);
    }

});

AppDispatcher.register(function (action) {

    switch (action.actionType) {
        case AgentsConstants.AGENTS_SHOW_FORM_EDIT:
            AgentsStore.emitShowAgentsEditForm(action.data);
            break;
        case AgentsConstants.AGENTS_SHOW_FORM_ADD:
            AgentsStore.emitShowAgentsAddForm(action.data);
            break;
    }
});


module.exports = AgentsStore;

файл действий:

var AppDispatcher = require('./../dispatchers/AppDispatcher.js');
var AgentsConstants = require('./../constants/AgentsConstants.js');

var AgentsActions = {

    show_add_agent_form: function (data) {
        AppDispatcher.dispatch({
            actionType: AgentsConstants.AGENTS_SHOW_FORM_ADD,
            data: data
        });
    },
    show_edit_agent_form: function (data) {
        AppDispatcher.dispatch({
            actionType: AgentsConstants.AGENTS_SHOW_FORM_EDIT,
            data: data
        });
    },
}

module.exports = AgentsActions;

в каком-то компоненте вы похожи:

...
    componentDidMount: function () {
        AgentsStore.addShowAgentsAddListener(this.handleChange);
    },
    componentWillUnmount: function () {
        AgentsStore.removeShowAgentsAddListener(this.handleChange);
    },
...

этот код довольно старый, но он работает хорошо, и вы определенно можете понять, как все работает.

person Lukas Liesis    schedule 22.05.2016

вы можете использовать React.Children.count, если вам интересно только знать, когда количество детей изменилось, или вы можете получить доступ к каждому ребенку React.Children.map/forEach.

см. этот пример (я использую его в хуке useEffect, но вы можете использовать его в componentDidMount или DidUpdate)

const BigBrother = props => {
   const { children } = props;
   const childrenIds = React.Children.map(children, child => {
      return child ? child.props.myId : null;
   }).filter(v => v !== null);
   useEffect(() => {
      // do something here
   }, [childrenIds.join("__")]);

  return (
    <div>
      <h2>I'm the big brother</h2>
      <div>{children}</div>
    </div>
}

тогда вы можете использовать его так (используйте динамический список!)

<BigBrother>
  <LilBrother myId="libindi" />
  <LilBrother myId="lisoko" />
  <LilBrother myId="likunza" />
</BigBrother>
person user3550446    schedule 13.11.2019