react.js 如何检测父组件中的子渲染
How to detect child renders in a parent component in react.js
我正在尝试缓存 App
组件的渲染标记。我知道这有点 "against the rules" 但我处于无服务器环境中(chrome-扩展名)。页面加载后,我想将缓存的 App
标记注入 DOM。预期结果类似于在服务器上使用 react-component rendererd 的体验。非常像这里描述的那样:http://www.tabforacause.org/blog/2015/01/29/using-reactjs-and-application-cache-fast-synced-app/.
为了说明我的用例,我更新了 Thinking in react example:
- 应用程序
- FilterableProductTable
- 搜索栏
- ProductTable(包含来自
reflux
存储的状态)
- 产品类别行
- 产品行
不出所料,componentDidUpdate
和 componentWillUpdate
都没有在 App
中调用。
是否可以以正常的方式检测 App
组件中更新的子组件?最好不修改子组件 类?
我想避免将 props/state 移动到 App
。
您可以在 App 中定义一个回调,该回调通过 props 通过其 child 层次结构向下传递,如果调用 child 的 componentDidUpdate 方法将被触发。不过,如果你的层次结构很深,有很多 children,这可能会变得混乱。
我有一种情况想在单元测试中做 this.setProps(…)
(当组件在没有父项的情况下呈现时)。但是如果在有父级的情况下执行会导致错误。
我的解决方法是在单元测试中设置一个类似 <MyComponent renderingWithoutParentForTest={true} />
的道具,并将该道具用于条件。
不过我承认这很丑。在这种特殊情况下,它似乎是有道理的。
React 文档提出了两种处理 child-to-parent 通信的方法。第一个已经提到了,它是从 parent 通过层次结构将一个函数作为 props 向下传递,然后在 child 组件中调用它们。
Child-To-Parent通讯: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 的重要组成部分。因此,您可以让 parent 组件订阅商店中的某些事件,这些事件将由 child 组件触发。
如果你有更大的应用程序,事件系统是比传递道具更好的解决方案。
按照助焊剂的建议进行思考。组件 -> 动作 -> 调度程序 -> 存储
在商店里你会有你的状态。您将注册要存储的组件的回调。您从任何组件和任何其他组件触发操作,即监听商店的更改正在获取数据。无论您如何更改层次结构,您总能在需要的地方获取数据。
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);
},
...
此代码有点旧,但运行良好,您绝对可以了解其工作原理
我想出了一个解决方案,它可以作为解决方案的一部分(无需修改子组件,或了解整个应用程序状态,例如:Flux 模式):
App
可以包装在一个组件中,该组件使用 MutationObserver
来跟踪 DOM.
中的实际变化
如果您只想知道 children 号码何时更改,您可以使用 React.Children.count,或者您可以访问每个 children 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>
我正在尝试缓存 App
组件的渲染标记。我知道这有点 "against the rules" 但我处于无服务器环境中(chrome-扩展名)。页面加载后,我想将缓存的 App
标记注入 DOM。预期结果类似于在服务器上使用 react-component rendererd 的体验。非常像这里描述的那样:http://www.tabforacause.org/blog/2015/01/29/using-reactjs-and-application-cache-fast-synced-app/.
为了说明我的用例,我更新了 Thinking in react example:
- 应用程序
- FilterableProductTable
- 搜索栏
- ProductTable(包含来自
reflux
存储的状态)- 产品类别行
- 产品行
- FilterableProductTable
不出所料,componentDidUpdate
和 componentWillUpdate
都没有在 App
中调用。
是否可以以正常的方式检测 App
组件中更新的子组件?最好不修改子组件 类?
我想避免将 props/state 移动到 App
。
您可以在 App 中定义一个回调,该回调通过 props 通过其 child 层次结构向下传递,如果调用 child 的 componentDidUpdate 方法将被触发。不过,如果你的层次结构很深,有很多 children,这可能会变得混乱。
我有一种情况想在单元测试中做 this.setProps(…)
(当组件在没有父项的情况下呈现时)。但是如果在有父级的情况下执行会导致错误。
我的解决方法是在单元测试中设置一个类似 <MyComponent renderingWithoutParentForTest={true} />
的道具,并将该道具用于条件。
不过我承认这很丑。在这种特殊情况下,它似乎是有道理的。
React 文档提出了两种处理 child-to-parent 通信的方法。第一个已经提到了,它是从 parent 通过层次结构将一个函数作为 props 向下传递,然后在 child 组件中调用它们。
Child-To-Parent通讯: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 的重要组成部分。因此,您可以让 parent 组件订阅商店中的某些事件,这些事件将由 child 组件触发。
如果你有更大的应用程序,事件系统是比传递道具更好的解决方案。
按照助焊剂的建议进行思考。组件 -> 动作 -> 调度程序 -> 存储
在商店里你会有你的状态。您将注册要存储的组件的回调。您从任何组件和任何其他组件触发操作,即监听商店的更改正在获取数据。无论您如何更改层次结构,您总能在需要的地方获取数据。
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);
},
...
此代码有点旧,但运行良好,您绝对可以了解其工作原理
我想出了一个解决方案,它可以作为解决方案的一部分(无需修改子组件,或了解整个应用程序状态,例如:Flux 模式):
App
可以包装在一个组件中,该组件使用 MutationObserver
来跟踪 DOM.
如果您只想知道 children 号码何时更改,您可以使用 React.Children.count,或者您可以访问每个 children 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>