【问题标题】:How to detect child renders in a parent component in react.js如何在 react.js 的父组件中检测子渲染
【发布时间】:2015-05-16 10:54:41
【问题描述】:

我正在尝试缓存 App 组件的渲染标记。我知道这在某种程度上“违反规则”,但我处于无服务器环境(chrome-extension)中。在页面加载时,我想将缓存的 App 标记注入 DOM。预期的结果类似于在服务器上拥有 react-component 渲染器的体验。非常像这里描述的那样:http://www.tabforacause.org/blog/2015/01/29/using-reactjs-and-application-cache-fast-synced-app/

为了说明我的用例,我更新了Thinking in react example

  • 应用程序
    • 可过滤产品表
      • 搜索栏
      • ProductTable(包含来自reflux 存储状态)
        • ProductCategoryRow
        • ProductRow

正如预期的那样,App 中既没有调用 componentDidUpdate 也没有调用 componentWillUpdate

是否有可能以理智的方式检测App 组件中更新的子组件?最好不要修改子组件类?

我想避免将 props/state 移动到 App

【问题讨论】:

  • 你想解决什么问题,App 组件需要知道子组件的变化?
  • @WiredPrairie 我已更新问题以包含更大的图景。

标签: javascript reactjs refluxjs


【解决方案1】:

您可以在 App 中定义一个回调,该回调通过 props 向下传递到其子层次结构,如果子组件的 componentDidUpdate 方法被调用,则将触发该回调。但是,如果您有很多孩子的层次结构很深,这可能会变得混乱。

【讨论】:

    【解决方案2】:

    我有一种情况,我想在单元测试中执行this.setProps(…)(当组件在没有父组件的情况下呈现时)。但是如果在有父级的情况下完成会导致错误。

    我的解决方法是简单地在单元测试中设置一个类似于<MyComponent renderingWithoutParentForTest={true} /> 的道具,并将该道具用于条件。

    不过,我承认这很丑。在这种特殊情况下,这似乎是有道理的。

    【讨论】:

      【解决方案3】:

      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 的重要组成部分。因此,您将让您的父组件订阅您的商店中的某个事件,这些事件将由子组件触发。

      【讨论】:

      • 您需要修改所有可能的子组件,这是我想避免的。
      【解决方案4】:

      如果你有更大的应用程序,事件系统是比传递道具更好的解决方案。

      按照助焊剂的建议进行思考。组件 -> 动作 -> 调度程序 -> 存储

      在商店中,您将拥有自己的状态。您将注册要存储的组件的回调。您从任何组件和任何其他组件触发操作,即监听商店的更改正在获取数据。无论您如何更改层次结构,您始终可以在需要的地方获取数据。

      dispatcher.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);
          },
      ...
      

      这段代码有点旧,但运行良好,您肯定可以了解这些代码是如何工作的

      【讨论】:

        【解决方案5】:

        我想出了一个解决方案,它可以作为解决方案的一部分(无需修改子组件,或了解整个应用程序状态,例如:Flux 模式):

        App 可以包装在一个组件中,该组件使用MutationObserver 来跟踪 DOM 中的实际更改。

        【讨论】:

          【解决方案6】:

          如果您只想知道孩子编号何时更改,或者您可以访问每个孩子的 React.Children.map/forEach,则可以使用 React.Children.count。

          查看这个示例(我在 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>
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2018-03-18
            • 2021-11-07
            • 1970-01-01
            • 1970-01-01
            • 2016-06-28
            • 2020-10-15
            • 2021-02-06
            • 1970-01-01
            相关资源
            最近更新 更多